diff --git a/clang-tools-extra/clang-tidy/ClangTidy.cpp b/clang-tools-extra/clang-tidy/ClangTidy.cpp index 69d758e9750ed..f623aa8ebb8f6 100644 --- a/clang-tools-extra/clang-tidy/ClangTidy.cpp +++ b/clang-tools-extra/clang-tidy/ClangTidy.cpp @@ -37,10 +37,6 @@ #include "clang/Rewrite/Frontend/FixItRewriter.h" #include "clang/Rewrite/Frontend/FrontendActions.h" #include "clang/Tooling/Core/Diagnostic.h" -#if CLANG_ENABLE_STATIC_ANALYZER -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" -#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" -#endif // CLANG_ENABLE_STATIC_ANALYZER #include "clang/Tooling/DiagnosticsYaml.h" #include "clang/Tooling/Refactoring.h" #include "clang/Tooling/ReplacementsYaml.h" @@ -50,6 +46,11 @@ #include #include +#if CLANG_ENABLE_STATIC_ANALYZER +#include "clang/Analysis/PathDiagnostic.h" +#include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" +#endif // CLANG_ENABLE_STATIC_ANALYZER + using namespace clang::ast_matchers; using namespace clang::driver; using namespace clang::tooling; @@ -72,7 +73,7 @@ class AnalyzerDiagnosticConsumer : public ento::PathDiagnosticConsumer { FilesMade *filesMade) override { for (const ento::PathDiagnostic *PD : Diags) { SmallString<64> CheckName(AnalyzerCheckNamePrefix); - CheckName += PD->getCheckName(); + CheckName += PD->getCheckerName(); Context.diag(CheckName, PD->getLocation().asLocation(), PD->getShortDescription()) << PD->path.back()->getRanges(); @@ -335,8 +336,8 @@ static void setStaticAnalyzerCheckerOpts(const ClangTidyOptions &Opts, typedef std::vector> CheckersList; -static CheckersList getCheckersControlList(ClangTidyContext &Context, - bool IncludeExperimental) { +static CheckersList getAnalyzerCheckersAndPackages(ClangTidyContext &Context, + bool IncludeExperimental) { CheckersList List; const auto &RegisteredCheckers = @@ -420,9 +421,9 @@ ClangTidyASTConsumerFactory::CreateASTConsumer( #if CLANG_ENABLE_STATIC_ANALYZER AnalyzerOptionsRef AnalyzerOptions = Compiler.getAnalyzerOpts(); - AnalyzerOptions->CheckersControlList = - getCheckersControlList(Context, Context.canEnableAnalyzerAlphaCheckers()); - if (!AnalyzerOptions->CheckersControlList.empty()) { + AnalyzerOptions->CheckersAndPackages = getAnalyzerCheckersAndPackages( + Context, Context.canEnableAnalyzerAlphaCheckers()); + if (!AnalyzerOptions->CheckersAndPackages.empty()) { setStaticAnalyzerCheckerOpts(Context.getOptions(), AnalyzerOptions); AnalyzerOptions->AnalysisStoreOpt = RegionStoreModel; AnalyzerOptions->AnalysisDiagOpt = PD_NONE; @@ -448,7 +449,7 @@ std::vector ClangTidyASTConsumerFactory::getCheckNames() { } #if CLANG_ENABLE_STATIC_ANALYZER - for (const auto &AnalyzerCheck : getCheckersControlList( + for (const auto &AnalyzerCheck : getAnalyzerCheckersAndPackages( Context, Context.canEnableAnalyzerAlphaCheckers())) CheckNames.push_back(AnalyzerCheckNamePrefix + AnalyzerCheck.first); #endif // CLANG_ENABLE_STATIC_ANALYZER diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst index 93b8f0cbf5c43..5c4f0dde4f049 100644 --- a/clang/docs/analyzer/checkers.rst +++ b/clang/docs/analyzer/checkers.rst @@ -260,6 +260,15 @@ Check for values stored to variables that are never read afterwards. x = 1; // warn } +The ``WarnForDeadNestedAssignments`` option enables the checker to emit +warnings for nested dead assignments. You can disable with the +``-analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false``. +*Defaults to true*. + +Would warn for this e.g.: +if ((y = make_int())) { +} + .. _nullability-checkers: nullability diff --git a/clang/include/clang/AST/ASTImporter.h b/clang/include/clang/AST/ASTImporter.h index 25f45888800e1..83003a8d9ad8c 100644 --- a/clang/include/clang/AST/ASTImporter.h +++ b/clang/include/clang/AST/ASTImporter.h @@ -86,6 +86,8 @@ class TypeSourceInfo; using NonEquivalentDeclSet = llvm::DenseSet>; using ImportedCXXBaseSpecifierMap = llvm::DenseMap; + using FileIDImportHandlerType = + std::function; enum class ODRHandlingType { Conservative, Liberal }; @@ -211,6 +213,8 @@ class TypeSourceInfo; }; private: + FileIDImportHandlerType FileIDImportHandler; + std::shared_ptr SharedState = nullptr; /// The path which we go through during the import of a given AST node. @@ -313,6 +317,14 @@ class TypeSourceInfo; virtual ~ASTImporter(); + /// Set a callback function for FileID import handling. + /// The function is invoked when a FileID is imported from the From context. + /// The imported FileID in the To context and the original FileID in the + /// From context is passed to it. + void setFileIDImportHandler(FileIDImportHandlerType H) { + FileIDImportHandler = H; + } + /// Whether the importer will perform a minimal import, creating /// to-be-completed forward declarations when possible. bool isMinimalImport() const { return Minimal; } diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index e80dd1a4ec40d..dc1db0500f85b 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -972,6 +972,9 @@ class QualType { friend bool operator!=(const QualType &LHS, const QualType &RHS) { return LHS.Value != RHS.Value; } + friend bool operator<(const QualType &LHS, const QualType &RHS) { + return LHS.Value < RHS.Value; + } static std::string getAsString(SplitQualType split, const PrintingPolicy &Policy) { diff --git a/clang/include/clang/Analysis/Analyses/Dominators.h b/clang/include/clang/Analysis/Analyses/Dominators.h index 0015b0d7fd708..bc672eb7d52f3 100644 --- a/clang/include/clang/Analysis/Analyses/Dominators.h +++ b/clang/include/clang/Analysis/Analyses/Dominators.h @@ -18,6 +18,7 @@ #include "llvm/ADT/DepthFirstIterator.h" #include "llvm/ADT/GraphTraits.h" #include "llvm/ADT/iterator.h" +#include "llvm/Support/GenericIteratedDominanceFrontier.h" #include "llvm/Support/GenericDomTree.h" #include "llvm/Support/GenericDomTreeConstruction.h" #include "llvm/Support/raw_ostream.h" @@ -36,132 +37,316 @@ namespace clang { using DomTreeNode = llvm::DomTreeNodeBase; -/// Concrete subclass of DominatorTreeBase for Clang -/// This class implements the dominators tree functionality given a Clang CFG. -/// -class DominatorTree : public ManagedAnalysis { +/// Dominator tree builder for Clang's CFG based on llvm::DominatorTreeBase. +template +class CFGDominatorTreeImpl : public ManagedAnalysis { virtual void anchor(); public: - llvm::DomTreeBase *DT; + using DominatorTreeBase = llvm::DominatorTreeBase; + + CFGDominatorTreeImpl() = default; - DominatorTree() { - DT = new llvm::DomTreeBase(); + CFGDominatorTreeImpl(CFG *cfg) { + buildDominatorTree(cfg); } - ~DominatorTree() override { delete DT; } + ~CFGDominatorTreeImpl() override = default; + + DominatorTreeBase &getBase() { return DT; } - llvm::DomTreeBase& getBase() { return *DT; } + CFG *getCFG() { return cfg; } - /// This method returns the root CFGBlock of the dominators tree. + /// \returns the root CFGBlock of the dominators tree. CFGBlock *getRoot() const { - return DT->getRoot(); + return DT.getRoot(); } - /// This method returns the root DomTreeNode, which is the wrapper - /// for CFGBlock. - DomTreeNode *getRootNode() const { - return DT->getRootNode(); + /// \returns the root DomTreeNode, which is the wrapper for CFGBlock. + DomTreeNode *getRootNode() { + return DT.getRootNode(); } - /// This method compares two dominator trees. - /// The method returns false if the other dominator tree matches this - /// dominator tree, otherwise returns true. - bool compare(DominatorTree &Other) const { + /// Compares two dominator trees. + /// \returns false if the other dominator tree matches this dominator tree, + /// false otherwise. + bool compare(CFGDominatorTreeImpl &Other) const { DomTreeNode *R = getRootNode(); DomTreeNode *OtherR = Other.getRootNode(); if (!R || !OtherR || R->getBlock() != OtherR->getBlock()) return true; - if (DT->compare(Other.getBase())) + if (DT.compare(Other.getBase())) return true; return false; } - /// This method builds the dominator tree for a given CFG - /// The CFG information is passed via AnalysisDeclContext - void buildDominatorTree(AnalysisDeclContext &AC) { - cfg = AC.getCFG(); - DT->recalculate(*cfg); + /// Builds the dominator tree for a given CFG. + void buildDominatorTree(CFG *cfg) { + assert(cfg); + this->cfg = cfg; + DT.recalculate(*cfg); } - /// This method dumps immediate dominators for each block, - /// mainly used for debug purposes. + /// Dumps immediate dominators for each block. void dump() { - llvm::errs() << "Immediate dominance tree (Node#,IDom#):\n"; + llvm::errs() << "Immediate " << (IsPostDom ? "post " : "") + << "dominance tree (Node#,IDom#):\n"; for (CFG::const_iterator I = cfg->begin(), E = cfg->end(); I != E; ++I) { - if(DT->getNode(*I)->getIDom()) + + assert(*I && + "LLVM's Dominator tree builder uses nullpointers to signify the " + "virtual root!"); + + DomTreeNode *IDom = DT.getNode(*I)->getIDom(); + if (IDom && IDom->getBlock()) llvm::errs() << "(" << (*I)->getBlockID() << "," - << DT->getNode(*I)->getIDom()->getBlock()->getBlockID() + << IDom->getBlock()->getBlockID() << ")\n"; - else llvm::errs() << "(" << (*I)->getBlockID() - << "," << (*I)->getBlockID() << ")\n"; + else { + bool IsEntryBlock = *I == &(*I)->getParent()->getEntry(); + bool IsExitBlock = *I == &(*I)->getParent()->getExit(); + + bool IsDomTreeRoot = !IDom && !IsPostDom && IsEntryBlock; + bool IsPostDomTreeRoot = + IDom && !IDom->getBlock() && IsPostDom && IsExitBlock; + + assert((IsDomTreeRoot || IsPostDomTreeRoot) && + "If the immediate dominator node is nullptr, the CFG block " + "should be the exit point (since it's the root of the dominator " + "tree), or if the CFG block it refers to is a nullpointer, it " + "must be the entry block (since it's the root of the post " + "dominator tree)"); + + (void)IsDomTreeRoot; + (void)IsPostDomTreeRoot; + + llvm::errs() << "(" << (*I)->getBlockID() + << "," << (*I)->getBlockID() << ")\n"; + } } } - /// This method tests if one CFGBlock dominates the other. - /// The method return true if A dominates B, false otherwise. + /// Tests whether \p A dominates \p B. /// Note a block always dominates itself. bool dominates(const CFGBlock *A, const CFGBlock *B) const { - return DT->dominates(A, B); + return DT.dominates(A, B); } - /// This method tests if one CFGBlock properly dominates the other. - /// The method return true if A properly dominates B, false otherwise. + /// Tests whether \p A properly dominates \p B. + /// \returns false if \p A is the same block as \p B, otherwise whether A + /// dominates B. bool properlyDominates(const CFGBlock *A, const CFGBlock *B) const { - return DT->properlyDominates(A, B); + return DT.properlyDominates(A, B); } - /// This method finds the nearest common dominator CFG block - /// for CFG block A and B. If there is no such block then return NULL. + /// \returns the nearest common dominator CFG block for CFG block \p A and \p + /// B. If there is no such block then return NULL. CFGBlock *findNearestCommonDominator(CFGBlock *A, CFGBlock *B) { - return DT->findNearestCommonDominator(A, B); + return DT.findNearestCommonDominator(A, B); } const CFGBlock *findNearestCommonDominator(const CFGBlock *A, const CFGBlock *B) { - return DT->findNearestCommonDominator(A, B); + return DT.findNearestCommonDominator(A, B); } - /// This method is used to update the dominator - /// tree information when a node's immediate dominator changes. + /// Update the dominator tree information when a node's immediate dominator + /// changes. void changeImmediateDominator(CFGBlock *N, CFGBlock *NewIDom) { - DT->changeImmediateDominator(N, NewIDom); + DT.changeImmediateDominator(N, NewIDom); } - /// This method tests if the given CFGBlock can be reachable from root. - /// Returns true if reachable, false otherwise. + /// Tests whether \p A is reachable from the entry block. bool isReachableFromEntry(const CFGBlock *A) { - return DT->isReachableFromEntry(A); + return DT.isReachableFromEntry(A); } - /// This method releases the memory held by the dominator tree. + /// Releases the memory held by the dominator tree. virtual void releaseMemory() { - DT->releaseMemory(); + DT.releaseMemory(); } - /// This method converts the dominator tree to human readable form. + /// Converts the dominator tree to human readable form. virtual void print(raw_ostream &OS, const llvm::Module* M= nullptr) const { - DT->print(OS); + DT.print(OS); } private: CFG *cfg; + DominatorTreeBase DT; +}; + +using CFGDomTree = CFGDominatorTreeImpl; +using CFGPostDomTree = CFGDominatorTreeImpl; + +} // end of namespace clang + +namespace llvm { +namespace IDFCalculatorDetail { + +/// Specialize ChildrenGetterTy to skip nullpointer successors. +template +struct ChildrenGetterTy { + using NodeRef = typename GraphTraits::NodeRef; + using ChildrenTy = SmallVector; + + ChildrenTy get(const NodeRef &N) { + using OrderedNodeTy = + typename IDFCalculatorBase::OrderedNodeTy; + + auto Children = children(N); + ChildrenTy Ret{Children.begin(), Children.end()}; + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; + } +}; + +} // end of namespace IDFCalculatorDetail +} // end of namespace llvm + +namespace clang { + +class ControlDependencyCalculator : public ManagedAnalysis { + using IDFCalculator = llvm::IDFCalculatorBase; + using CFGBlockVector = llvm::SmallVector; + using CFGBlockSet = llvm::SmallPtrSet; + + CFGPostDomTree PostDomTree; + IDFCalculator IDFCalc; + + llvm::DenseMap ControlDepenencyMap; + +public: + ControlDependencyCalculator(CFG *cfg) + : PostDomTree(cfg), IDFCalc(PostDomTree.getBase()) {} + + const CFGPostDomTree &getCFGPostDomTree() const { return PostDomTree; } + + // Lazily retrieves the set of control dependencies to \p A. + const CFGBlockVector &getControlDependencies(CFGBlock *A) { + auto It = ControlDepenencyMap.find(A); + if (It == ControlDepenencyMap.end()) { + CFGBlockSet DefiningBlock = {A}; + IDFCalc.setDefiningBlocks(DefiningBlock); + + CFGBlockVector ControlDependencies; + IDFCalc.calculate(ControlDependencies); + + It = ControlDepenencyMap.insert({A, ControlDependencies}).first; + } + + assert(It != ControlDepenencyMap.end()); + return It->second; + } + + /// Whether \p A is control dependent on \p B. + bool isControlDependent(CFGBlock *A, CFGBlock *B) { + return llvm::is_contained(getControlDependencies(A), B); + } + + // Dumps immediate control dependencies for each block. + LLVM_DUMP_METHOD void dump() { + CFG *cfg = PostDomTree.getCFG(); + llvm::errs() << "Control dependencies (Node#,Dependency#):\n"; + for (CFGBlock *BB : *cfg) { + + assert(BB && + "LLVM's Dominator tree builder uses nullpointers to signify the " + "virtual root!"); + + for (CFGBlock *isControlDependency : getControlDependencies(BB)) + llvm::errs() << "(" << BB->getBlockID() + << "," + << isControlDependency->getBlockID() + << ")\n"; + } + } }; } // namespace clang +namespace llvm { + +/// Clang's CFG contains nullpointers for unreachable succesors, e.g. when an +/// if statement's condition is always false, it's 'then' branch is represented +/// with a nullptr. This however will result in a nullpointer derefernece for +/// dominator tree calculation. +/// +/// To circumvent this, let's just crudely specialize the children getters +/// used in LLVM's dominator tree builder. +namespace DomTreeBuilder { + +using ClangCFGDomChildrenGetter = +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/false>; + +template <> +template <> +inline ClangCFGDomChildrenGetter::ResultTy ClangCFGDomChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto RChildren = reverse(children(N)); + ResultTy Ret(RChildren.begin(), RChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + +using ClangCFGDomReverseChildrenGetter = +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/true>; + +template <> +template <> +inline ClangCFGDomReverseChildrenGetter::ResultTy +ClangCFGDomReverseChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto IChildren = inverse_children(N); + ResultTy Ret(IChildren.begin(), IChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + +using ClangCFGPostDomChildrenGetter = +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/false>; + +template <> +template <> +inline ClangCFGPostDomChildrenGetter::ResultTy +ClangCFGPostDomChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto RChildren = reverse(children(N)); + ResultTy Ret(RChildren.begin(), RChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + +using ClangCFGPostDomReverseChildrenGetter = +SemiNCAInfo::ChildrenGetter< + /*Inverse=*/true>; + +template <> +template <> +inline ClangCFGPostDomReverseChildrenGetter::ResultTy +ClangCFGPostDomReverseChildrenGetter::Get( + clang::CFGBlock *N, std::integral_constant) { + auto IChildren = inverse_children(N); + ResultTy Ret(IChildren.begin(), IChildren.end()); + Ret.erase(std::remove(Ret.begin(), Ret.end(), nullptr), Ret.end()); + return Ret; +} + +} // end of namespace DomTreeBuilder + //===------------------------------------- /// DominatorTree GraphTraits specialization so the DominatorTree can be /// iterable by generic graph iterators. /// -namespace llvm { - -template <> struct GraphTraits< ::clang::DomTreeNode* > { +template <> struct GraphTraits { using NodeRef = ::clang::DomTreeNode *; using ChildIteratorType = ::clang::DomTreeNode::iterator; @@ -181,17 +366,17 @@ template <> struct GraphTraits< ::clang::DomTreeNode* > { } }; -template <> struct GraphTraits< ::clang::DominatorTree* > - : public GraphTraits< ::clang::DomTreeNode* > { - static NodeRef getEntryNode(::clang::DominatorTree *DT) { +template <> struct GraphTraits + : public GraphTraits { + static NodeRef getEntryNode(clang::CFGDomTree *DT) { return DT->getRootNode(); } - static nodes_iterator nodes_begin(::clang::DominatorTree *N) { + static nodes_iterator nodes_begin(clang::CFGDomTree *N) { return nodes_iterator(df_begin(getEntryNode(N))); } - static nodes_iterator nodes_end(::clang::DominatorTree *N) { + static nodes_iterator nodes_end(clang::CFGDomTree *N) { return nodes_iterator(df_end(getEntryNode(N))); } }; diff --git a/clang/include/clang/Analysis/AnalysisDeclContext.h b/clang/include/clang/Analysis/AnalysisDeclContext.h index 1961d571e9e12..9faa78cde89c2 100644 --- a/clang/include/clang/Analysis/AnalysisDeclContext.h +++ b/clang/include/clang/Analysis/AnalysisDeclContext.h @@ -183,9 +183,8 @@ class AnalysisDeclContext { const ImplicitParamDecl *getSelfDecl() const; const StackFrameContext *getStackFrame(LocationContext const *Parent, - const Stmt *S, - const CFGBlock *Blk, - unsigned Idx); + const Stmt *S, const CFGBlock *Blk, + unsigned BlockCount, unsigned Idx); const BlockInvocationContext * getBlockInvocationContext(const LocationContext *parent, @@ -258,7 +257,7 @@ class LocationContext : public llvm::FoldingSetNode { return getAnalysisDeclContext()->getAnalysis(); } - ParentMap &getParentMap() const { + const ParentMap &getParentMap() const { return getAnalysisDeclContext()->getParentMap(); } @@ -303,15 +302,19 @@ class StackFrameContext : public LocationContext { // The parent block of the callsite. const CFGBlock *Block; + // The number of times the 'Block' has been visited. + // It allows discriminating between stack frames of the same call that is + // called multiple times in a loop. + const unsigned BlockCount; + // The index of the callsite in the CFGBlock. - unsigned Index; + const unsigned Index; StackFrameContext(AnalysisDeclContext *ctx, const LocationContext *parent, - const Stmt *s, const CFGBlock *blk, - unsigned idx, - int64_t ID) - : LocationContext(StackFrame, ctx, parent, ID), CallSite(s), - Block(blk), Index(idx) {} + const Stmt *s, const CFGBlock *blk, unsigned blockCount, + unsigned idx, int64_t ID) + : LocationContext(StackFrame, ctx, parent, ID), CallSite(s), Block(blk), + BlockCount(blockCount), Index(idx) {} public: ~StackFrameContext() override = default; @@ -329,9 +332,10 @@ class StackFrameContext : public LocationContext { static void Profile(llvm::FoldingSetNodeID &ID, AnalysisDeclContext *ctx, const LocationContext *parent, const Stmt *s, - const CFGBlock *blk, unsigned idx) { + const CFGBlock *blk, unsigned blockCount, unsigned idx) { ProfileCommon(ID, StackFrame, ctx, parent, s); ID.AddPointer(blk); + ID.AddInteger(blockCount); ID.AddInteger(idx); } @@ -410,8 +414,8 @@ class LocationContextManager { const StackFrameContext *getStackFrame(AnalysisDeclContext *ctx, const LocationContext *parent, - const Stmt *s, - const CFGBlock *blk, unsigned idx); + const Stmt *s, const CFGBlock *blk, + unsigned blockCount, unsigned idx); const ScopeContext *getScope(AnalysisDeclContext *ctx, const LocationContext *parent, @@ -483,26 +487,25 @@ class AnalysisDeclContextManager { bool synthesizeBodies() const { return SynthesizeBodies; } const StackFrameContext *getStackFrame(AnalysisDeclContext *Ctx, - LocationContext const *Parent, - const Stmt *S, - const CFGBlock *Blk, - unsigned Idx) { - return LocContexts.getStackFrame(Ctx, Parent, S, Blk, Idx); + const LocationContext *Parent, + const Stmt *S, const CFGBlock *Blk, + unsigned BlockCount, unsigned Idx) { + return LocContexts.getStackFrame(Ctx, Parent, S, Blk, BlockCount, Idx); } // Get the top level stack frame. const StackFrameContext *getStackFrame(const Decl *D) { return LocContexts.getStackFrame(getContext(D), nullptr, nullptr, nullptr, - 0); + 0, 0); } // Get a stack frame with parent. StackFrameContext const *getStackFrame(const Decl *D, - LocationContext const *Parent, - const Stmt *S, - const CFGBlock *Blk, - unsigned Idx) { - return LocContexts.getStackFrame(getContext(D), Parent, S, Blk, Idx); + const LocationContext *Parent, + const Stmt *S, const CFGBlock *Blk, + unsigned BlockCount, unsigned Idx) { + return LocContexts.getStackFrame(getContext(D), Parent, S, Blk, BlockCount, + Idx); } /// Get a reference to {@code BodyFarm} instance. diff --git a/clang/include/clang/Analysis/CFG.h b/clang/include/clang/Analysis/CFG.h index d8b3d6ff71ede..b24e32c966777 100644 --- a/clang/include/clang/Analysis/CFG.h +++ b/clang/include/clang/Analysis/CFG.h @@ -121,6 +121,12 @@ class CFGElement { x |= Data1.getInt(); return (Kind) x; } + + void dumpToStream(llvm::raw_ostream &OS) const; + + void dump() const { + dumpToStream(llvm::errs()); + } }; class CFGStmt : public CFGElement { @@ -610,6 +616,153 @@ class CFGBlock { bool empty() const { return Impl.empty(); } }; + /// A convenience class for comparing CFGElements, since methods of CFGBlock + /// like operator[] return CFGElements by value. This is practically a wrapper + /// around a (CFGBlock, Index) pair. + template class ElementRefImpl { + + template friend class ElementRefImpl; + + using CFGBlockPtr = + typename std::conditional::type; + + using CFGElementPtr = typename std::conditional::type; + + protected: + CFGBlockPtr Parent; + size_t Index; + + public: + ElementRefImpl(CFGBlockPtr Parent, size_t Index) + : Parent(Parent), Index(Index) {} + + template + ElementRefImpl(ElementRefImpl Other) + : ElementRefImpl(Other.Parent, Other.Index) {} + + size_t getIndexInBlock() const { return Index; } + + CFGBlockPtr getParent() { return Parent; } + CFGBlockPtr getParent() const { return Parent; } + + bool operator<(ElementRefImpl Other) const { + return std::make_pair(Parent, Index) < + std::make_pair(Other.Parent, Other.Index); + } + + bool operator==(ElementRefImpl Other) const { + return Parent == Other.Parent && Index == Other.Index; + } + + bool operator!=(ElementRefImpl Other) const { return !(*this == Other); } + CFGElement operator*() const { return (*Parent)[Index]; } + CFGElementPtr operator->() const { return &*(Parent->begin() + Index); } + + void dumpToStream(llvm::raw_ostream &OS) const { + OS << getIndexInBlock() + 1 << ": "; + (*this)->dumpToStream(OS); + } + + void dump() const { + dumpToStream(llvm::errs()); + } + }; + + template class ElementRefIterator { + + template + friend class ElementRefIterator; + + using CFGBlockRef = + typename std::conditional::type; + + using UnderlayingIteratorTy = typename std::conditional< + IsConst, + typename std::conditional::type, + typename std::conditional::type>::type; + + using IteratorTraits = typename std::iterator_traits; + using ElementRef = typename CFGBlock::ElementRefImpl; + + public: + using difference_type = typename IteratorTraits::difference_type; + using value_type = ElementRef; + using pointer = ElementRef *; + using iterator_category = typename IteratorTraits::iterator_category; + + private: + CFGBlockRef Parent; + UnderlayingIteratorTy Pos; + + public: + ElementRefIterator(CFGBlockRef Parent, UnderlayingIteratorTy Pos) + : Parent(Parent), Pos(Pos) {} + + template + ElementRefIterator(ElementRefIterator E) + : ElementRefIterator(E.Parent, E.Pos.base()) {} + + template + ElementRefIterator(ElementRefIterator E) + : ElementRefIterator(E.Parent, llvm::make_reverse_iterator(E.Pos)) {} + + bool operator<(ElementRefIterator Other) const { + assert(Parent == Other.Parent); + return Pos < Other.Pos; + } + + bool operator==(ElementRefIterator Other) const { + return Parent == Other.Parent && Pos == Other.Pos; + } + + bool operator!=(ElementRefIterator Other) const { + return !(*this == Other); + } + + private: + template + static size_t + getIndexInBlock(CFGBlock::ElementRefIterator E) { + return E.Parent->size() - (E.Pos - E.Parent->rbegin()) - 1; + } + + template + static size_t + getIndexInBlock(CFGBlock::ElementRefIterator E) { + return E.Pos - E.Parent->begin(); + } + + public: + value_type operator*() { return {Parent, getIndexInBlock(*this)}; } + + difference_type operator-(ElementRefIterator Other) const { + return Pos - Other.Pos; + } + + ElementRefIterator operator++() { + ++this->Pos; + return *this; + } + ElementRefIterator operator++(int) { + ElementRefIterator Ret = *this; + ++*this; + return Ret; + } + ElementRefIterator operator+(size_t count) { + this->Pos += count; + return *this; + } + ElementRefIterator operator-(size_t count) { + this->Pos -= count; + return *this; + } + }; + +public: /// The set of statements in the basic block. ElementList Elements; @@ -715,6 +868,8 @@ class CFGBlock { using reverse_iterator = ElementList::reverse_iterator; using const_reverse_iterator = ElementList::const_reverse_iterator; + size_t getIndexInCFG() const; + CFGElement front() const { return Elements.front(); } CFGElement back() const { return Elements.back(); } @@ -728,6 +883,38 @@ class CFGBlock { const_reverse_iterator rbegin() const { return Elements.rbegin(); } const_reverse_iterator rend() const { return Elements.rend(); } + using CFGElementRef = ElementRefImpl; + using ConstCFGElementRef = ElementRefImpl; + + using ref_iterator = ElementRefIterator; + using ref_iterator_range = llvm::iterator_range; + using const_ref_iterator = ElementRefIterator; + using const_ref_iterator_range = llvm::iterator_range; + + using reverse_ref_iterator = ElementRefIterator; + using reverse_ref_iterator_range = llvm::iterator_range; + + using const_reverse_ref_iterator = ElementRefIterator; + using const_reverse_ref_iterator_range = + llvm::iterator_range; + + ref_iterator ref_begin() { return {this, begin()}; } + ref_iterator ref_end() { return {this, end()}; } + const_ref_iterator ref_begin() const { return {this, begin()}; } + const_ref_iterator ref_end() const { return {this, end()}; } + + reverse_ref_iterator rref_begin() { return {this, rbegin()}; } + reverse_ref_iterator rref_end() { return {this, rend()}; } + const_reverse_ref_iterator rref_begin() const { return {this, rbegin()}; } + const_reverse_ref_iterator rref_end() const { return {this, rend()}; } + + ref_iterator_range refs() { return {ref_begin(), ref_end()}; } + const_ref_iterator_range refs() const { return {ref_begin(), ref_end()}; } + reverse_ref_iterator_range rrefs() { return {rref_begin(), rref_end()}; } + const_reverse_ref_iterator_range rrefs() const { + return {rref_begin(), rref_end()}; + } + unsigned size() const { return Elements.size(); } bool empty() const { return Elements.empty(); } @@ -855,11 +1042,23 @@ class CFGBlock { void setLoopTarget(const Stmt *loopTarget) { LoopTarget = loopTarget; } void setHasNoReturnElement() { HasNoReturnElement = true; } + /// Returns true if the block would eventually end with a sink (a noreturn + /// node). + bool isInevitablySinking() const; + CFGTerminator getTerminator() const { return Terminator; } Stmt *getTerminatorStmt() { return Terminator.getStmt(); } const Stmt *getTerminatorStmt() const { return Terminator.getStmt(); } + /// \returns the last (\c rbegin()) condition, e.g. observe the following code + /// snippet: + /// if (A && B && C) + /// A block would be created for \c A, \c B, and \c C. For the latter, + /// \c getTerminatorStmt() would retrieve the entire condition, rather than + /// C itself, while this method would only return C. + const Expr *getLastCondition() const; + Stmt *getTerminatorCondition(bool StripParens = true); const Stmt *getTerminatorCondition(bool StripParens = true) const { @@ -886,7 +1085,7 @@ class CFGBlock { void printTerminator(raw_ostream &OS, const LangOptions &LO) const; void printTerminatorJson(raw_ostream &Out, const LangOptions &LO, bool AddQuotes) const; - + void printAsOperand(raw_ostream &OS, bool /*PrintType*/) { OS << "BB#" << getBlockID(); } @@ -1002,7 +1201,6 @@ class CFGBlock { *I = CFGScopeEnd(VD, S); return ++I; } - }; /// CFGCallback defines methods that should be called when a logical @@ -1277,6 +1475,9 @@ template <> struct GraphTraits< ::clang::CFGBlock *> { static ChildIteratorType child_end(NodeRef N) { return N->succ_end(); } }; +template <> struct GraphTraits + : GraphTraits {}; + template <> struct GraphTraits< const ::clang::CFGBlock *> { using NodeRef = const ::clang::CFGBlock *; using ChildIteratorType = ::clang::CFGBlock::const_succ_iterator; @@ -1286,6 +1487,9 @@ template <> struct GraphTraits< const ::clang::CFGBlock *> { static ChildIteratorType child_end(NodeRef N) { return N->succ_end(); } }; +template <> struct GraphTraits + : GraphTraits {}; + template <> struct GraphTraits> { using NodeRef = ::clang::CFGBlock *; using ChildIteratorType = ::clang::CFGBlock::const_pred_iterator; @@ -1298,6 +1502,9 @@ template <> struct GraphTraits> { static ChildIteratorType child_end(NodeRef N) { return N->pred_end(); } }; +template <> struct GraphTraits> + : GraphTraits {}; + template <> struct GraphTraits> { using NodeRef = const ::clang::CFGBlock *; using ChildIteratorType = ::clang::CFGBlock::const_pred_iterator; @@ -1310,6 +1517,9 @@ template <> struct GraphTraits> { static ChildIteratorType child_end(NodeRef N) { return N->pred_end(); } }; +template <> struct GraphTraits> + : GraphTraits {}; + // Traits for: CFG template <> struct GraphTraits< ::clang::CFG* > diff --git a/clang/include/clang/Analysis/CallGraph.h b/clang/include/clang/Analysis/CallGraph.h index 49c04490fed2c..dae2b58ffc102 100644 --- a/clang/include/clang/Analysis/CallGraph.h +++ b/clang/include/clang/Analysis/CallGraph.h @@ -131,6 +131,7 @@ class CallGraph : public RecursiveASTVisitor { bool shouldWalkTypesOfTypeLocs() const { return false; } bool shouldVisitTemplateInstantiations() const { return true; } + bool shouldVisitImplicitCode() const { return true; } private: /// Add the given declaration to the call graph. diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h b/clang/include/clang/Analysis/PathDiagnostic.h similarity index 88% rename from clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h rename to clang/include/clang/Analysis/PathDiagnostic.h index 5230742a4aa43..6730057cf0adc 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h +++ b/clang/include/clang/Analysis/PathDiagnostic.h @@ -52,11 +52,6 @@ class SourceManager; namespace ento { -class ExplodedNode; -class SymExpr; - -using SymbolRef = const SymExpr *; - //===----------------------------------------------------------------------===// // High-level interface for handlers of path-sensitive diagnostics. //===----------------------------------------------------------------------===// @@ -125,6 +120,13 @@ class PathDiagnosticConsumer { }; virtual PathGenerationScheme getGenerationScheme() const { return Minimal; } + + bool shouldGenerateDiagnostics() const { + return getGenerationScheme() != None; + } + + bool shouldAddPathEdges() const { return getGenerationScheme() == Extensive; } + virtual bool supportsLogicalOpControlFlow() const { return false; } /// Return true if the PathDiagnosticConsumer supports individual @@ -269,19 +271,21 @@ class PathDiagnosticLocation { static PathDiagnosticLocation createDeclEnd(const LocationContext *LC, const SourceManager &SM); - /// Create a location corresponding to the given valid ExplodedNode. + /// Create a location corresponding to the given valid ProgramPoint. static PathDiagnosticLocation create(const ProgramPoint &P, const SourceManager &SMng); - /// Create a location corresponding to the next valid ExplodedNode as end - /// of path location. - static PathDiagnosticLocation createEndOfPath(const ExplodedNode* N, - const SourceManager &SM); - /// Convert the given location into a single kind location. static PathDiagnosticLocation createSingleLocation( const PathDiagnosticLocation &PDL); + /// Construct a source location that corresponds to either the beginning + /// or the end of the given statement, or a nearby valid source location + /// if the statement does not have a valid source location of its own. + static SourceLocation + getValidSourceLocation(const Stmt *S, LocationOrAnalysisDeclContext LAC, + bool UseEndOfStatement = false); + bool operator==(const PathDiagnosticLocation &X) const { return K == X.K && Loc == X.Loc && Range == X.Range; } @@ -326,13 +330,6 @@ class PathDiagnosticLocation { void Profile(llvm::FoldingSetNodeID &ID) const; void dump() const; - - /// Given an exploded node, retrieve the statement that should be used - /// for the diagnostic location. - static const Stmt *getStmt(const ExplodedNode *N); - - /// Retrieve the statement corresponding to the successor node. - static const Stmt *getNextStmt(const ExplodedNode *N); }; class PathDiagnosticLocationPair { @@ -386,6 +383,7 @@ class PathDiagnosticPiece: public llvm::FoldingSetNode { StringRef Tag; std::vector ranges; + std::vector fixits; protected: PathDiagnosticPiece(StringRef s, Kind k, DisplayHint hint = Below); @@ -430,9 +428,16 @@ class PathDiagnosticPiece: public llvm::FoldingSetNode { ranges.push_back(SourceRange(B,E)); } + void addFixit(FixItHint F) { + fixits.push_back(F); + } + /// Return the SourceRanges associated with this PathDiagnosticPiece. ArrayRef getRanges() const { return ranges; } + /// Return the fix-it hints associated with this PathDiagnosticPiece. + ArrayRef getFixits() const { return fixits; } + virtual void Profile(llvm::FoldingSetNodeID &ID) const; void setAsLastInMainSourceFile() { @@ -446,7 +451,9 @@ class PathDiagnosticPiece: public llvm::FoldingSetNode { virtual void dump() const = 0; }; -class PathPieces : public std::list> { +using PathDiagnosticPieceRef = std::shared_ptr; + +class PathPieces : public std::list { void flattenTo(PathPieces &Primary, PathPieces &Current, bool ShouldFlattenMacros) const; @@ -486,65 +493,13 @@ class PathDiagnosticSpotPiece : public PathDiagnosticPiece { } }; -/// Interface for classes constructing Stack hints. -/// -/// If a PathDiagnosticEvent occurs in a different frame than the final -/// diagnostic the hints can be used to summarize the effect of the call. -class StackHintGenerator { -public: - virtual ~StackHintGenerator() = 0; - - /// Construct the Diagnostic message for the given ExplodedNode. - virtual std::string getMessage(const ExplodedNode *N) = 0; -}; - -/// Constructs a Stack hint for the given symbol. -/// -/// The class knows how to construct the stack hint message based on -/// traversing the CallExpr associated with the call and checking if the given -/// symbol is returned or is one of the arguments. -/// The hint can be customized by redefining 'getMessageForX()' methods. -class StackHintGeneratorForSymbol : public StackHintGenerator { -private: - SymbolRef Sym; - std::string Msg; - -public: - StackHintGeneratorForSymbol(SymbolRef S, StringRef M) : Sym(S), Msg(M) {} - ~StackHintGeneratorForSymbol() override = default; - - /// Search the call expression for the symbol Sym and dispatch the - /// 'getMessageForX()' methods to construct a specific message. - std::string getMessage(const ExplodedNode *N) override; - - /// Produces the message of the following form: - /// 'Msg via Nth parameter' - virtual std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex); - - virtual std::string getMessageForReturn(const CallExpr *CallExpr) { - return Msg; - } - - virtual std::string getMessageForSymbolNotFound() { - return Msg; - } -}; - class PathDiagnosticEventPiece : public PathDiagnosticSpotPiece { Optional IsPrunable; - /// If the event occurs in a different frame than the final diagnostic, - /// supply a message that will be used to construct an extra hint on the - /// returns from all the calls on the stack from this event to the final - /// diagnostic. - std::unique_ptr CallStackHint; - public: PathDiagnosticEventPiece(const PathDiagnosticLocation &pos, - StringRef s, bool addPosRange = true, - StackHintGenerator *stackHint = nullptr) - : PathDiagnosticSpotPiece(pos, s, Event, addPosRange), - CallStackHint(stackHint) {} + StringRef s, bool addPosRange = true) + : PathDiagnosticSpotPiece(pos, s, Event, addPosRange) {} ~PathDiagnosticEventPiece() override; /// Mark the diagnostic piece as being potentially prunable. This @@ -561,16 +516,6 @@ class PathDiagnosticEventPiece : public PathDiagnosticSpotPiece { return IsPrunable.hasValue() ? IsPrunable.getValue() : false; } - bool hasCallStackHint() { return (bool)CallStackHint; } - - /// Produce the hint for the given node. The node contains - /// information about the call for which the diagnostic can be generated. - std::string getCallStackMessage(const ExplodedNode *N) { - if (CallStackHint) - return CallStackHint->getMessage(N); - return {}; - } - void dump() const override; static bool classof(const PathDiagnosticPiece *P) { @@ -726,8 +671,6 @@ class PathDiagnosticMacroPiece : public PathDiagnosticSpotPiece { PathPieces subPieces; - bool containsEvent() const; - void flattenLocations() override { PathDiagnosticSpotPiece::flattenLocations(); for (const auto &I : subPieces) @@ -782,7 +725,7 @@ using FilesToLineNumsMap = std::map>; /// diagnostic. It represents an ordered-collection of PathDiagnosticPieces, /// each which represent the pieces of the path. class PathDiagnostic : public llvm::FoldingSetNode { - std::string CheckName; + std::string CheckerName; const Decl *DeclWithIssue; std::string BugType; std::string VerboseDesc; @@ -806,7 +749,7 @@ class PathDiagnostic : public llvm::FoldingSetNode { public: PathDiagnostic() = delete; - PathDiagnostic(StringRef CheckName, const Decl *DeclWithIssue, + PathDiagnostic(StringRef CheckerName, const Decl *DeclWithIssue, StringRef bugtype, StringRef verboseDesc, StringRef shortDesc, StringRef category, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, @@ -836,7 +779,7 @@ class PathDiagnostic : public llvm::FoldingSetNode { bool isWithinCall() const { return !pathStack.empty(); } - void setEndOfPath(std::shared_ptr EndPiece) { + void setEndOfPath(PathDiagnosticPieceRef EndPiece) { assert(!Loc.isValid() && "End location already set!"); Loc = EndPiece->getLocation(); assert(Loc.isValid() && "Invalid location for end-of-path piece"); @@ -849,26 +792,16 @@ class PathDiagnostic : public llvm::FoldingSetNode { VerboseDesc += S; } - /// If the last piece of the report point to the header file, resets - /// the location of the report to be the last location in the main source - /// file. - void resetDiagnosticLocationToMainFile(); - StringRef getVerboseDescription() const { return VerboseDesc; } StringRef getShortDescription() const { return ShortDesc.empty() ? VerboseDesc : ShortDesc; } - StringRef getCheckName() const { return CheckName; } + StringRef getCheckerName() const { return CheckerName; } StringRef getBugType() const { return BugType; } StringRef getCategory() const { return Category; } - /// Return the semantic context where an issue occurred. If the - /// issue occurs along a path, this represents the "central" area - /// where the bug manifests. - const Decl *getDeclWithIssue() const { return DeclWithIssue; } - using meta_iterator = std::deque::const_iterator; meta_iterator meta_begin() const { return OtherDesc.begin(); } @@ -883,10 +816,23 @@ class PathDiagnostic : public llvm::FoldingSetNode { return *ExecutedLines; } + /// Return the semantic context where an issue occurred. If the + /// issue occurs along a path, this represents the "central" area + /// where the bug manifests. + const Decl *getDeclWithIssue() const { return DeclWithIssue; } + + void setDeclWithIssue(const Decl *D) { + DeclWithIssue = D; + } + PathDiagnosticLocation getLocation() const { return Loc; } + void setLocation(PathDiagnosticLocation NewLoc) { + Loc = NewLoc; + } + /// Get the location on which the report should be uniqued. PathDiagnosticLocation getUniqueingLoc() const { return UniqueingLoc; @@ -917,7 +863,6 @@ class PathDiagnostic : public llvm::FoldingSetNode { }; } // namespace ento - } // namespace clang #endif // LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_PATHDIAGNOSTIC_H diff --git a/clang/include/clang/Basic/DiagnosticCommonKinds.td b/clang/include/clang/Basic/DiagnosticCommonKinds.td index 5713c3409b4f1..b22912c4ca2f2 100644 --- a/clang/include/clang/Basic/DiagnosticCommonKinds.td +++ b/clang/include/clang/Basic/DiagnosticCommonKinds.td @@ -311,7 +311,7 @@ def err_omp_more_one_clause : Error< "directive '#pragma omp %0' cannot contain more than one '%1' clause%select{| with '%3' name modifier| with 'source' dependence}2">; // Static Analyzer Core -def err_unknown_analyzer_checker : Error< +def err_unknown_analyzer_checker_or_package : Error< "no analyzer checkers or packages are associated with '%0'">; def note_suggest_disabling_all_checkers : Note< "use -analyzer-disable-all-checks to disable all static analyzer checkers">; diff --git a/clang/include/clang/Basic/JsonSupport.h b/clang/include/clang/Basic/JsonSupport.h index f235daa1689e7..aed4c9a6ae3de 100644 --- a/clang/include/clang/Basic/JsonSupport.h +++ b/clang/include/clang/Basic/JsonSupport.h @@ -10,6 +10,7 @@ #define LLVM_CLANG_BASIC_JSONSUPPORT_H #include "clang/Basic/LLVM.h" +#include "clang/Basic/SourceManager.h" #include "llvm/ADT/StringRef.h" #include "llvm/Support/raw_ostream.h" @@ -58,6 +59,42 @@ inline std::string JsonFormat(StringRef RawSR, bool AddQuotes) { return '\"' + Str + '\"'; } +inline void printSourceLocationAsJson(raw_ostream &Out, SourceLocation Loc, + const SourceManager &SM, + bool AddBraces = true) { + // Mostly copy-pasted from SourceLocation::print. + if (!Loc.isValid()) { + Out << "null"; + return; + } + + if (Loc.isFileID()) { + PresumedLoc PLoc = SM.getPresumedLoc(Loc); + + if (PLoc.isInvalid()) { + Out << "null"; + return; + } + // The macro expansion and spelling pos is identical for file locs. + if (AddBraces) + Out << "{ "; + Out << "\"line\": " << PLoc.getLine() + << ", \"column\": " << PLoc.getColumn() + << ", \"file\": \"" << PLoc.getFilename() << "\""; + if (AddBraces) + Out << " }"; + return; + } + + // We want 'location: { ..., spelling: { ... }}' but not + // 'location: { ... }, spelling: { ... }', hence the dance + // with braces. + Out << "{ "; + printSourceLocationAsJson(Out, SM.getExpansionLoc(Loc), SM, false); + Out << ", \"spelling\": "; + printSourceLocationAsJson(Out, SM.getSpellingLoc(Loc), SM, true); + Out << " }"; +} } // namespace clang #endif // LLVM_CLANG_BASIC_JSONSUPPORT_H diff --git a/clang/include/clang/CrossTU/CrossTranslationUnit.h b/clang/include/clang/CrossTU/CrossTranslationUnit.h index d1d38e09ef58b..3e3d2ba70b312 100644 --- a/clang/include/clang/CrossTU/CrossTranslationUnit.h +++ b/clang/include/clang/CrossTU/CrossTranslationUnit.h @@ -45,7 +45,8 @@ enum class index_error_code { failed_to_generate_usr, triple_mismatch, lang_mismatch, - lang_dialect_mismatch + lang_dialect_mismatch, + load_threshold_reached }; class IndexError : public llvm::ErrorInfo { @@ -134,7 +135,8 @@ class CrossTranslationUnitContext { /// A definition with the same declaration will be looked up in the /// index file which should be in the \p CrossTUDir directory, called /// \p IndexName. In case the declaration is found in the index the - /// corresponding AST file will be loaded. + /// corresponding AST file will be loaded. If the number of TUs imported + /// reaches \p CTULoadTreshold, no loading is performed. /// /// \return Returns a pointer to the ASTUnit that contains the definition of /// the looked up name or an Error. @@ -151,8 +153,10 @@ class CrossTranslationUnitContext { /// was passed to the constructor. /// /// \return Returns the resulting definition or an error. - llvm::Expected importDefinition(const FunctionDecl *FD); - llvm::Expected importDefinition(const VarDecl *VD); + llvm::Expected importDefinition(const FunctionDecl *FD, + ASTUnit *Unit); + llvm::Expected importDefinition(const VarDecl *VD, + ASTUnit *Unit); /// Get a name to identify a named decl. static std::string getLookupName(const NamedDecl *ND); @@ -160,9 +164,23 @@ class CrossTranslationUnitContext { /// Emit diagnostics for the user for potential configuration errors. void emitCrossTUDiagnostics(const IndexError &IE); + /// Determine the original source location in the original TU for an + /// imported source location. + /// \p ToLoc Source location in the imported-to AST. + /// \return Source location in the imported-from AST and the corresponding + /// ASTUnit object (the AST was loaded from a file using an internal ASTUnit + /// object that is returned here). + /// If any error happens (ToLoc is a non-imported source location) empty is + /// returned. + llvm::Optional> + getImportedFromSourceLocation(const clang::SourceLocation &ToLoc) const; + private: + using ImportedFileIDMap = + llvm::DenseMap>; + void lazyInitImporterSharedSt(TranslationUnitDecl *ToTU); - ASTImporter &getOrCreateASTImporter(ASTContext &From); + ASTImporter &getOrCreateASTImporter(ASTUnit *Unit); template llvm::Expected getCrossTUDefinitionImpl(const T *D, StringRef CrossTUDir, @@ -172,7 +190,7 @@ class CrossTranslationUnitContext { const T *findDefInDeclContext(const DeclContext *DC, StringRef LookupName); template - llvm::Expected importDefinitionImpl(const T *D); + llvm::Expected importDefinitionImpl(const T *D, ASTUnit *Unit); llvm::StringMap> FileASTUnitMap; llvm::StringMap NameASTUnitMap; @@ -182,6 +200,19 @@ class CrossTranslationUnitContext { CompilerInstance &CI; ASTContext &Context; std::shared_ptr ImporterSharedSt; + /// Map of imported FileID's (in "To" context) to FileID in "From" context + /// and the ASTUnit for the From context. + /// This map is used by getImportedFromSourceLocation to lookup a FileID and + /// its Preprocessor when knowing only the FileID in the 'To' context. The + /// FileID could be imported by any of multiple 'From' ASTImporter objects. + /// we do not want to loop over all ASTImporter's to find the one that + /// imported the FileID. + ImportedFileIDMap ImportedFileIDs; + + /// \p CTULoadTreshold should serve as an upper limit to the number of TUs + /// imported in order to reduce the memory footprint of CTU analysis. + const unsigned CTULoadThreshold; + unsigned NumASTLoaded{0u}; }; } // namespace cross_tu diff --git a/clang/include/clang/Driver/CC1Options.td b/clang/include/clang/Driver/CC1Options.td index 80f6d9e3b29bf..bd289ffef2839 100644 --- a/clang/include/clang/Driver/CC1Options.td +++ b/clang/include/clang/Driver/CC1Options.td @@ -140,7 +140,8 @@ def analyzer_checker_help_developer : Flag<["-"], "analyzer-checker-help-develop "and debug checkers">; def analyzer_config_help : Flag<["-"], "analyzer-config-help">, - HelpText<"Display the list of -analyzer-config options">; + HelpText<"Display the list of -analyzer-config options. These are meant for " + "development purposes only!">; def analyzer_list_enabled_checkers : Flag<["-"], "analyzer-list-enabled-checkers">, HelpText<"Display the list of enabled analyzer checkers">; diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td index bc081498ac9e2..4d52655045b3b 100644 --- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td +++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td @@ -100,6 +100,7 @@ def LLVMAlpha : Package<"llvm">, ParentPackage; // intended for API modeling that is not controlled by the target triple. def APIModeling : Package<"apiModeling">, Hidden; def GoogleAPIModeling : Package<"google">, ParentPackage, Hidden; +def LLVMAPIModeling : Package<"llvm">, ParentPackage, Hidden; def Debug : Package<"debug">, Hidden; @@ -503,6 +504,15 @@ def MoveChecker: Checker<"Move">, ]>, Documentation; +def VirtualCallModeling : Checker<"VirtualCallModeling">, + HelpText<"Auxiliary modeling for the virtual method call checkers">, + Documentation, + Hidden; + +def PureVirtualCallChecker : Checker<"PureVirtualCall">, + HelpText<"Check pure virtual function calls during construction/destruction">, + Dependencies<[VirtualCallModeling]>, + Documentation; } // end: "cplusplus" let ParentPackage = CplusplusOptIn in { @@ -551,14 +561,22 @@ def UninitializedObjectChecker: Checker<"UninitializedObject">, Documentation; def VirtualCallChecker : Checker<"VirtualCall">, - HelpText<"Check virtual function calls during construction or destruction">, + HelpText<"Check virtual function calls during construction/destruction">, CheckerOptions<[ + CmdLineOption, CmdLineOption + InAlpha> ]>, + Dependencies<[VirtualCallModeling]>, Documentation; } // end: "optin.cplusplus" @@ -635,6 +653,19 @@ let ParentPackage = DeadCode in { def DeadStoresChecker : Checker<"DeadStores">, HelpText<"Check for values stored to variables that are never read " "afterwards">, + CheckerOptions<[ + CmdLineOption, + CmdLineOption + ]>, Documentation; } // end DeadCode @@ -798,6 +829,13 @@ let ParentPackage = Taint in { def GenericTaintChecker : Checker<"TaintPropagation">, HelpText<"Generate taint information used by other checkers">, + CheckerOptions<[ + CmdLineOption, + ]>, Documentation; } // end "alpha.security.taint" @@ -1105,6 +1143,18 @@ def LLVMConventionsChecker : Checker<"Conventions">, } // end "llvm" +let ParentPackage = LLVMAPIModeling in { + +def CastValueChecker : Checker<"CastValue">, + HelpText<"Model implementation of custom RTTIs">, + Documentation; + +def ReturnValueChecker : Checker<"ReturnValue">, + HelpText<"Model the guaranteed boolean return value of function calls">, + Documentation; + +} // end "apiModeling.llvm" + //===----------------------------------------------------------------------===// // Checkers modeling Google APIs. //===----------------------------------------------------------------------===// @@ -1229,6 +1279,14 @@ def DominatorsTreeDumper : Checker<"DumpDominators">, HelpText<"Print the dominance tree for a given CFG">, Documentation; +def PostDominatorsTreeDumper : Checker<"DumpPostDominators">, + HelpText<"Print the post dominance tree for a given CFG">, + Documentation; + +def ControlDependencyTreeDumper : Checker<"DumpControlDependencies">, + HelpText<"Print the post control dependency tree for a given CFG">, + Documentation; + def LiveVariablesDumper : Checker<"DumpLiveVars">, HelpText<"Print results of live variable analysis">, Documentation; diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def index 40d5d47bbcea3..d853fb74f9c77 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.def @@ -291,10 +291,31 @@ ANALYZER_OPTION(bool, DisplayCTUProgress, "display-ctu-progress", "the analyzer's progress related to ctu.", false) +ANALYZER_OPTION(bool, ShouldTrackConditions, "track-conditions", + "Whether to track conditions that are a control dependency of " + "an already tracked variable.", + true) + +ANALYZER_OPTION(bool, ShouldTrackConditionsDebug, "track-conditions-debug", + "Whether to place an event at each tracked condition.", + false) + +ANALYZER_OPTION(bool, ShouldEmitFixItHintsAsRemarks, "fixits-as-remarks", + "Emit fix-it hints as remarks for testing purposes", + false) + //===----------------------------------------------------------------------===// -// Unsinged analyzer options. +// Unsigned analyzer options. //===----------------------------------------------------------------------===// +ANALYZER_OPTION(unsigned, CTUImportThreshold, "ctu-import-threshold", + "The maximal amount of translation units that is considered " + "for import when inlining functions during CTU analysis. " + "Lowering this threshold can alleviate the memory burder of " + "analysis with many interdependent definitions located in " + "various translation units.", + 100u) + ANALYZER_OPTION( unsigned, AlwaysInlineSize, "ipa-always-inline-size", "The size of the functions (in basic blocks), which should be considered " @@ -363,12 +384,6 @@ ANALYZER_OPTION( "Value: \"constructors\", \"destructors\", \"methods\".", "destructors") -ANALYZER_OPTION_DEPENDS_ON_USER_MODE( - StringRef, IPAMode, "ipa", - "Controls the mode of inter-procedural analysis. Value: \"none\", " - "\"basic-inlining\", \"inlining\", \"dynamic\", \"dynamic-bifurcate\".", - /* SHALLOW_VAL */ "inlining", /* DEEP_VAL */ "dynamic-bifurcate") - ANALYZER_OPTION( StringRef, ExplorationStrategy, "exploration_strategy", "Value: \"dfs\", \"bfs\", \"unexplored_first\", " @@ -376,5 +391,17 @@ ANALYZER_OPTION( "\"bfs_block_dfs_contents\".", "unexplored_first_queue") +ANALYZER_OPTION( + StringRef, RawSilencedCheckersAndPackages, "silence-checkers", + "A semicolon separated list of checker and package names to silence. " + "Silenced checkers will not emit reports, but the modeling remain enabled.", + "") + +ANALYZER_OPTION_DEPENDS_ON_USER_MODE( + StringRef, IPAMode, "ipa", + "Controls the mode of inter-procedural analysis. Value: \"none\", " + "\"basic-inlining\", \"inlining\", \"dynamic\", \"dynamic-bifurcate\".", + /* SHALLOW_VAL */ "inlining", /* DEEP_VAL */ "dynamic-bifurcate") + #undef ANALYZER_OPTION_DEPENDS_ON_USER_MODE #undef ANALYZER_OPTION diff --git a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h index 9630a229bd3bf..ce16095e10c0b 100644 --- a/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h +++ b/clang/include/clang/StaticAnalyzer/Core/AnalyzerOptions.h @@ -163,9 +163,16 @@ class AnalyzerOptions : public RefCountedBase { public: using ConfigTable = llvm::StringMap; + /// Retrieves the list of checkers generated from Checkers.td. This doesn't + /// contain statically linked but non-generated checkers and plugin checkers! static std::vector getRegisteredCheckers(bool IncludeExperimental = false); + /// Retrieves the list of packages generated from Checkers.td. This doesn't + /// contain statically linked but non-generated packages and plugin packages! + static std::vector + getRegisteredPackages(bool IncludeExperimental = false); + /// Convenience function for printing options or checkers and their /// description in a formatted manner. If \p MinLineWidth is set to 0, no line /// breaks are introduced for the description. @@ -188,9 +195,11 @@ class AnalyzerOptions : public RefCountedBase { std::pair EntryDescPair, size_t EntryWidth, size_t InitialPad, size_t MinLineWidth = 0); + /// Pairs of checker/package name and enable/disable. + std::vector> CheckersAndPackages; - /// Pair of checker name and enable/disable. - std::vector> CheckersControlList; + /// Vector of checker/package names which will not emit warnings. + std::vector SilencedCheckersAndPackages; /// A key-value table of use-specified configuration values. // TODO: This shouldn't be public. @@ -212,12 +221,12 @@ class AnalyzerOptions : public RefCountedBase { /// The maximum number of times the analyzer visits a block. unsigned maxBlockVisitOnPath; - /// Disable all analyzer checks. + /// Disable all analyzer checkers. /// - /// This flag allows one to disable analyzer checks on the code processed by + /// This flag allows one to disable analyzer checkers on the code processed by /// the given analysis consumer. Note, the code will get parsed and the /// command-line options will get checked. - unsigned DisableAllChecks : 1; + unsigned DisableAllCheckers : 1; unsigned ShowCheckerHelp : 1; unsigned ShowCheckerHelpAlpha : 1; @@ -269,13 +278,13 @@ class AnalyzerOptions : public RefCountedBase { // Create an array of all -analyzer-config command line options. Sort it in // the constructor. - std::vector AnalyzerConfigCmdFlags = { + std::vector AnalyzerConfigCmdFlags = { #define ANALYZER_OPTION_DEPENDS_ON_USER_MODE(TYPE, NAME, CMDFLAG, DESC, \ SHALLOW_VAL, DEEP_VAL) \ ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, SHALLOW_VAL) #define ANALYZER_OPTION(TYPE, NAME, CMDFLAG, DESC, DEFAULT_VAL) \ - CMDFLAG, + llvm::StringLiteral(CMDFLAG), #include "clang/StaticAnalyzer/Core/AnalyzerOptions.def" #undef ANALYZER_OPTION @@ -292,7 +301,7 @@ class AnalyzerOptions : public RefCountedBase { } AnalyzerOptions() - : DisableAllChecks(false), ShowCheckerHelp(false), + : DisableAllCheckers(false), ShowCheckerHelp(false), ShowCheckerHelpAlpha(false), ShowCheckerHelpDeveloper(false), ShowCheckerOptionList(false), ShowCheckerOptionAlphaList(false), ShowCheckerOptionDeveloperList(false), ShowEnabledCheckerList(false), @@ -310,7 +319,7 @@ class AnalyzerOptions : public RefCountedBase { /// If an option value is not provided, returns the given \p DefaultVal. /// @param [in] CheckerName The *full name* of the checker. One may retrieve /// this from the checker object's field \c Name, or through \c - /// CheckerManager::getCurrentCheckName within the checker's registry + /// CheckerManager::getCurrentCheckerName within the checker's registry /// function. /// Checker options are retrieved in the following format: /// `-analyzer-config CheckerName:OptionName=Value. @@ -330,7 +339,7 @@ class AnalyzerOptions : public RefCountedBase { /// If an option value is not provided, returns the given \p DefaultVal. /// @param [in] CheckerName The *full name* of the checker. One may retrieve /// this from the checker object's field \c Name, or through \c - /// CheckerManager::getCurrentCheckName within the checker's registry + /// CheckerManager::getCurrentCheckerName within the checker's registry /// function. /// Checker options are retrieved in the following format: /// `-analyzer-config CheckerName:OptionName=Value. @@ -350,7 +359,7 @@ class AnalyzerOptions : public RefCountedBase { /// If an option value is not provided, returns the given \p DefaultVal. /// @param [in] CheckerName The *full name* of the checker. One may retrieve /// this from the checker object's field \c Name, or through \c - /// CheckerManager::getCurrentCheckName within the checker's registry + /// CheckerManager::getCurrentCheckerName within the checker's registry /// function. /// Checker options are retrieved in the following format: /// `-analyzer-config CheckerName:OptionName=Value. @@ -404,6 +413,43 @@ inline UserModeKind AnalyzerOptions::getUserMode() const { return K.getValue(); } +inline std::vector +AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental) { + static constexpr llvm::StringLiteral StaticAnalyzerCheckerNames[] = { +#define GET_CHECKERS +#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ + llvm::StringLiteral(FULLNAME), +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef CHECKER +#undef GET_CHECKERS + }; + std::vector Checkers; + for (StringRef CheckerName : StaticAnalyzerCheckerNames) { + if (!CheckerName.startswith("debug.") && + (IncludeExperimental || !CheckerName.startswith("alpha."))) + Checkers.push_back(CheckerName); + } + return Checkers; +} + +inline std::vector +AnalyzerOptions::getRegisteredPackages(bool IncludeExperimental) { + static constexpr llvm::StringLiteral StaticAnalyzerPackageNames[] = { +#define GET_PACKAGES +#define PACKAGE(FULLNAME) llvm::StringLiteral(FULLNAME), +#include "clang/StaticAnalyzer/Checkers/Checkers.inc" +#undef PACKAGE +#undef GET_PACKAGES + }; + std::vector Packages; + for (StringRef PackageName : StaticAnalyzerPackageNames) { + if (PackageName != "debug" && + (IncludeExperimental || PackageName != "alpha")) + Packages.push_back(PackageName); + } + return Packages; +} + } // namespace clang #endif // LLVM_CLANG_STATICANALYZER_CORE_ANALYZEROPTIONS_H diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h index 4cccb38ce24fa..fae4770c9b858 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporter.h @@ -14,10 +14,10 @@ #ifndef LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_BUGREPORTER_H #define LLVM_CLANG_STATICANALYZER_CORE_BUGREPORTER_BUGREPORTER_H +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" @@ -70,59 +70,253 @@ class SValBuilder; using DiagnosticForConsumerMapTy = llvm::DenseMap>; -/// This class provides an interface through which checkers can create -/// individual bug reports. -class BugReport : public llvm::ilist_node { +/// Interface for classes constructing Stack hints. +/// +/// If a PathDiagnosticEvent occurs in a different frame than the final +/// diagnostic the hints can be used to summarize the effect of the call. +class StackHintGenerator { public: - class NodeResolver { - virtual void anchor(); + virtual ~StackHintGenerator() = 0; - public: - virtual ~NodeResolver() = default; + /// Construct the Diagnostic message for the given ExplodedNode. + virtual std::string getMessage(const ExplodedNode *N) = 0; +}; - virtual const ExplodedNode* - getOriginalNode(const ExplodedNode *N) = 0; - }; +/// Constructs a Stack hint for the given symbol. +/// +/// The class knows how to construct the stack hint message based on +/// traversing the CallExpr associated with the call and checking if the given +/// symbol is returned or is one of the arguments. +/// The hint can be customized by redefining 'getMessageForX()' methods. +class StackHintGeneratorForSymbol : public StackHintGenerator { +private: + SymbolRef Sym; + std::string Msg; - using ranges_iterator = const SourceRange *; - using VisitorList = SmallVector, 8>; - using visitor_iterator = VisitorList::iterator; - using ExtraTextList = SmallVector; - using NoteList = SmallVector, 4>; +public: + StackHintGeneratorForSymbol(SymbolRef S, StringRef M) : Sym(S), Msg(M) {} + ~StackHintGeneratorForSymbol() override = default; + + /// Search the call expression for the symbol Sym and dispatch the + /// 'getMessageForX()' methods to construct a specific message. + std::string getMessage(const ExplodedNode *N) override; + + /// Produces the message of the following form: + /// 'Msg via Nth parameter' + virtual std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex); + + virtual std::string getMessageForReturn(const CallExpr *CallExpr) { + return Msg; + } + + virtual std::string getMessageForSymbolNotFound() { + return Msg; + } +}; + +/// This class provides an interface through which checkers can create +/// individual bug reports. +class BugReport { +public: + enum class Kind { Basic, PathSensitive }; protected: friend class BugReportEquivClass; friend class BugReporter; + Kind K; const BugType& BT; - const Decl *DeclWithIssue = nullptr; std::string ShortDescription; std::string Description; + + SmallVector Ranges; + SmallVector, 4> Notes; + SmallVector Fixits; + + BugReport(Kind kind, const BugType &bt, StringRef desc) + : K(kind), BT(bt), Description(desc) {} + + BugReport(Kind K, const BugType &BT, StringRef ShortDescription, + StringRef Description) + : K(K), BT(BT), ShortDescription(ShortDescription), + Description(Description) {} + +public: + virtual ~BugReport() = default; + + Kind getKind() const { return K; } + + const BugType& getBugType() const { return BT; } + + /// A verbose warning message that is appropriate for displaying next to + /// the source code that introduces the problem. The description should be + /// at least a full sentence starting with a capital letter. The period at + /// the end of the warning is traditionally omitted. If the description + /// consists of multiple sentences, periods between the sentences are + /// encouraged, but the period at the end of the description is still omitted. + StringRef getDescription() const { return Description; } + + /// A short general warning message that is appropriate for displaying in + /// the list of all reported bugs. It should describe what kind of bug is found + /// but does not need to try to go into details of that specific bug. + /// Grammatical conventions of getDescription() apply here as well. + StringRef getShortDescription(bool UseFallback = true) const { + if (ShortDescription.empty() && UseFallback) + return Description; + return ShortDescription; + } + + /// The primary location of the bug report that points at the undesirable + /// behavior in the code. UIs should attach the warning description to this + /// location. The warning description should describe the bad behavior + /// at this location. + virtual PathDiagnosticLocation getLocation() const = 0; + + /// The smallest declaration that contains the bug location. + /// This is purely cosmetic; the declaration can be displayed to the user + /// but it does not affect whether the report is emitted. + virtual const Decl *getDeclWithIssue() const = 0; + + /// Get the location on which the report should be uniqued. Two warnings are + /// considered to be equivalent whenever they have the same bug types, + /// descriptions, and uniqueing locations. Out of a class of equivalent + /// warnings only one gets displayed to the user. For most warnings the + /// uniqueing location coincides with their location, but sometimes + /// it makes sense to use different locations. For example, a leak + /// checker can place the warning at the location where the last reference + /// to the leaking resource is dropped but at the same time unique the warning + /// by where that resource is acquired (allocated). + virtual PathDiagnosticLocation getUniqueingLocation() const = 0; + + /// Get the declaration that corresponds to (usually contains) the uniqueing + /// location. This is not actively used for uniqueing, i.e. otherwise + /// identical reports that have different uniqueing decls will be considered + /// equivalent. + virtual const Decl *getUniqueingDecl() const = 0; + + /// Add new item to the list of additional notes that need to be attached to + /// this report. If the report is path-sensitive, these notes will not be + /// displayed as part of the execution path explanation, but will be displayed + /// separately. Use bug visitors if you need to add an extra path note. + void addNote(StringRef Msg, const PathDiagnosticLocation &Pos, + ArrayRef Ranges = {}) { + auto P = std::make_shared(Pos, Msg); + + for (const auto &R : Ranges) + P->addRange(R); + + Notes.push_back(std::move(P)); + } + + ArrayRef> getNotes() { + return Notes; + } + + /// Add a range to a bug report. + /// + /// Ranges are used to highlight regions of interest in the source code. + /// They should be at the same source code line as the BugReport location. + /// By default, the source range of the statement corresponding to the error + /// node will be used; add a single invalid range to specify absence of + /// ranges. + void addRange(SourceRange R) { + assert((R.isValid() || Ranges.empty()) && "Invalid range can only be used " + "to specify that the report does not have a range."); + Ranges.push_back(R); + } + + /// Get the SourceRanges associated with the report. + virtual ArrayRef getRanges() const { + return Ranges; + } + + /// Add a fix-it hint to the bug report. + /// + /// Fix-it hints are the suggested edits to the code that would resolve + /// the problem explained by the bug report. Fix-it hints should be + /// as conservative as possible because it is not uncommon for the user + /// to blindly apply all fixits to their project. Note that it is very hard + /// to produce a good fix-it hint for most path-sensitive warnings. + void addFixItHint(const FixItHint &F) { + Fixits.push_back(F); + } + + llvm::ArrayRef getFixits() const { return Fixits; } + + /// Reports are uniqued to ensure that we do not emit multiple diagnostics + /// for each bug. + virtual void Profile(llvm::FoldingSetNodeID& hash) const = 0; +}; + +class BasicBugReport : public BugReport { PathDiagnosticLocation Location; - PathDiagnosticLocation UniqueingLocation; - const Decl *UniqueingDecl; + const Decl *DeclWithIssue = nullptr; + +public: + BasicBugReport(const BugType &bt, StringRef desc, PathDiagnosticLocation l) + : BugReport(Kind::Basic, bt, desc), Location(l) {} + + static bool classof(const BugReport *R) { + return R->getKind() == Kind::Basic; + } + + PathDiagnosticLocation getLocation() const override { + assert(Location.isValid()); + return Location; + } + + const Decl *getDeclWithIssue() const override { + return DeclWithIssue; + } + + PathDiagnosticLocation getUniqueingLocation() const override { + return getLocation(); + } + + const Decl *getUniqueingDecl() const override { + return getDeclWithIssue(); + } + + /// Specifically set the Decl where an issue occurred. This isn't necessary + /// for BugReports that cover a path as it will be automatically inferred. + void setDeclWithIssue(const Decl *declWithIssue) { + DeclWithIssue = declWithIssue; + } + void Profile(llvm::FoldingSetNodeID& hash) const override; +}; + +class PathSensitiveBugReport : public BugReport { +public: + using VisitorList = SmallVector, 8>; + using visitor_iterator = VisitorList::iterator; + using visitor_range = llvm::iterator_range; + +protected: + /// The ExplodedGraph node against which the report was thrown. It corresponds + /// to the end of the execution path that demonstrates the bug. const ExplodedNode *ErrorNode = nullptr; - SmallVector Ranges; - ExtraTextList ExtraText; - NoteList Notes; - using Symbols = llvm::DenseSet; - using Regions = llvm::DenseSet; + /// The range that corresponds to ErrorNode's program point. It is usually + /// highlighted in the report. + const SourceRange ErrorNodeRange; + + /// Profile to identify equivalent bug reports for error report coalescing. /// A (stack of) a set of symbols that are registered with this /// report as being "interesting", and thus used to help decide which /// diagnostics to include when constructing the final path diagnostic. /// The stack is largely used by BugReporter when generating PathDiagnostics /// for multiple PathDiagnosticConsumers. - SmallVector interestingSymbols; + llvm::DenseMap InterestingSymbols; /// A (stack of) set of regions that are registered with this report as being /// "interesting", and thus used to help decide which diagnostics /// to include when constructing the final path diagnostic. /// The stack is largely used by BugReporter when generating PathDiagnostics /// for multiple PathDiagnosticConsumers. - SmallVector interestingRegions; + llvm::DenseMap + InterestingRegions; /// A set of location contexts that correspoind to call sites which should be /// considered "interesting". @@ -153,66 +347,61 @@ class BugReport : public llvm::ilist_node { /// \sa removeInvalidation llvm::SmallSet Invalidations; -private: - // Used internally by BugReporter. - Symbols &getInterestingSymbols(); - Regions &getInterestingRegions(); - - void lazyInitializeInterestingSets(); - void pushInterestingSymbolsAndRegions(); - void popInterestingSymbolsAndRegions(); + /// Conditions we're already tracking. + llvm::SmallSet TrackedConditions; -public: - BugReport(const BugType& bt, StringRef desc, const ExplodedNode *errornode) - : BT(bt), Description(desc), ErrorNode(errornode) {} + /// Reports with different uniqueing locations are considered to be different + /// for the purposes of deduplication. + PathDiagnosticLocation UniqueingLocation; + const Decl *UniqueingDecl; - BugReport(const BugType& bt, StringRef shortDesc, StringRef desc, - const ExplodedNode *errornode) - : BT(bt), ShortDescription(shortDesc), Description(desc), - ErrorNode(errornode) {} + const Stmt *getStmt() const; - BugReport(const BugType &bt, StringRef desc, PathDiagnosticLocation l) - : BT(bt), Description(desc), Location(l) {} + /// If an event occurs in a different frame than the final diagnostic, + /// supply a message that will be used to construct an extra hint on the + /// returns from all the calls on the stack from this event to the final + /// diagnostic. + // FIXME: Allow shared_ptr keys in DenseMap? + std::map> + StackHints; - /// Create a BugReport with a custom uniqueing location. +public: + PathSensitiveBugReport(const BugType &bt, StringRef desc, + const ExplodedNode *errorNode) + : BugReport(Kind::PathSensitive, bt, desc), ErrorNode(errorNode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() + : SourceRange()) {} + + PathSensitiveBugReport(const BugType &bt, StringRef shortDesc, StringRef desc, + const ExplodedNode *errorNode) + : BugReport(Kind::PathSensitive, bt, shortDesc, desc), + ErrorNode(errorNode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() + : SourceRange()) {} + + /// Create a PathSensitiveBugReport with a custom uniqueing location. /// /// The reports that have the same report location, description, bug type, and /// ranges are uniqued - only one of the equivalent reports will be presented /// to the user. This method allows to rest the location which should be used /// for uniquing reports. For example, memory leaks checker, could set this to /// the allocation site, rather then the location where the bug is reported. - BugReport(BugType& bt, StringRef desc, const ExplodedNode *errornode, - PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique) - : BT(bt), Description(desc), UniqueingLocation(LocationToUnique), - UniqueingDecl(DeclToUnique), ErrorNode(errornode) {} - - virtual ~BugReport(); - - const BugType& getBugType() const { return BT; } - //BugType& getBugType() { return BT; } + PathSensitiveBugReport(BugType &bt, StringRef desc, + const ExplodedNode *errorNode, + PathDiagnosticLocation LocationToUnique, + const Decl *DeclToUnique) + : BugReport(Kind::PathSensitive, bt, desc), ErrorNode(errorNode), + ErrorNodeRange(getStmt() ? getStmt()->getSourceRange() : SourceRange()), + UniqueingLocation(LocationToUnique), UniqueingDecl(DeclToUnique) { + assert(errorNode); + } - /// True when the report has an execution path associated with it. - /// - /// A report is said to be path-sensitive if it was thrown against a - /// particular exploded node in the path-sensitive analysis graph. - /// Path-sensitive reports have their intermediate path diagnostics - /// auto-generated, perhaps with the help of checker-defined visitors, - /// and may contain extra notes. - /// Path-insensitive reports consist only of a single warning message - /// in a specific location, and perhaps extra notes. - /// Path-sensitive checkers are allowed to throw path-insensitive reports. - bool isPathSensitive() const { return ErrorNode != nullptr; } + static bool classof(const BugReport *R) { + return R->getKind() == Kind::PathSensitive; + } const ExplodedNode *getErrorNode() const { return ErrorNode; } - StringRef getDescription() const { return Description; } - - StringRef getShortDescription(bool UseFallback = true) const { - if (ShortDescription.empty() && UseFallback) - return Description; - return ShortDescription; - } - /// Indicates whether or not any path pruning should take place /// when generating a PathDiagnostic from this BugReport. bool shouldPrunePath() const { return !DoNotPrunePath; } @@ -220,15 +409,54 @@ class BugReport : public llvm::ilist_node { /// Disable all path pruning when generating a PathDiagnostic. void disablePathPruning() { DoNotPrunePath = true; } - void markInteresting(SymbolRef sym); - void markInteresting(const MemRegion *R); - void markInteresting(SVal V); + /// Get the location on which the report should be uniqued. + PathDiagnosticLocation getUniqueingLocation() const override { + return UniqueingLocation; + } + + /// Get the declaration containing the uniqueing location. + const Decl *getUniqueingDecl() const override { + return UniqueingDecl; + } + + const Decl *getDeclWithIssue() const override; + + ArrayRef getRanges() const override; + + PathDiagnosticLocation getLocation() const override; + + /// Marks a symbol as interesting. Different kinds of interestingness will + /// be processed differently by visitors (e.g. if the tracking kind is + /// condition, will append "will be used as a condition" to the message). + void markInteresting(SymbolRef sym, bugreporter::TrackingKind TKind = + bugreporter::TrackingKind::Thorough); + + /// Marks a region as interesting. Different kinds of interestingness will + /// be processed differently by visitors (e.g. if the tracking kind is + /// condition, will append "will be used as a condition" to the message). + void markInteresting( + const MemRegion *R, + bugreporter::TrackingKind TKind = bugreporter::TrackingKind::Thorough); + + /// Marks a symbolic value as interesting. Different kinds of interestingness + /// will be processed differently by visitors (e.g. if the tracking kind is + /// condition, will append "will be used as a condition" to the message). + void markInteresting(SVal V, bugreporter::TrackingKind TKind = + bugreporter::TrackingKind::Thorough); void markInteresting(const LocationContext *LC); - bool isInteresting(SymbolRef sym); - bool isInteresting(const MemRegion *R); - bool isInteresting(SVal V); - bool isInteresting(const LocationContext *LC); + bool isInteresting(SymbolRef sym) const; + bool isInteresting(const MemRegion *R) const; + bool isInteresting(SVal V) const; + bool isInteresting(const LocationContext *LC) const; + + Optional + getInterestingnessKind(SymbolRef sym) const; + + Optional + getInterestingnessKind(const MemRegion *R) const; + + Optional getInterestingnessKind(SVal V) const; /// Returns whether or not this report should be considered valid. /// @@ -251,87 +479,10 @@ class BugReport : public llvm::ilist_node { Invalidations.insert(std::make_pair(Tag, Data)); } - /// Return the canonical declaration, be it a method or class, where - /// this issue semantically occurred. - const Decl *getDeclWithIssue() const; - - /// Specifically set the Decl where an issue occurred. This isn't necessary - /// for BugReports that cover a path as it will be automatically inferred. - void setDeclWithIssue(const Decl *declWithIssue) { - DeclWithIssue = declWithIssue; - } - - /// Add new item to the list of additional notes that need to be attached to - /// this path-insensitive report. If you want to add extra notes to a - /// path-sensitive report, you need to use a BugReporterVisitor because it - /// allows you to specify where exactly in the auto-generated path diagnostic - /// the extra note should appear. - void addNote(StringRef Msg, const PathDiagnosticLocation &Pos, - ArrayRef Ranges) { - auto P = std::make_shared(Pos, Msg); - - for (const auto &R : Ranges) - P->addRange(R); - - Notes.push_back(std::move(P)); - } - - // FIXME: Instead of making an override, we could have default-initialized - // Ranges with {}, however it crashes the MSVC 2013 compiler. - void addNote(StringRef Msg, const PathDiagnosticLocation &Pos) { - std::vector Ranges; - addNote(Msg, Pos, Ranges); - } - - virtual const NoteList &getNotes() { - return Notes; - } - - /// This allows for addition of meta data to the diagnostic. - /// - /// Currently, only the HTMLDiagnosticClient knows how to display it. - void addExtraText(StringRef S) { - ExtraText.push_back(S); - } - - virtual const ExtraTextList &getExtraText() { - return ExtraText; - } - - /// Return the "definitive" location of the reported bug. - /// - /// While a bug can span an entire path, usually there is a specific - /// location that can be used to identify where the key issue occurred. - /// This location is used by clients rendering diagnostics. - virtual PathDiagnosticLocation getLocation(const SourceManager &SM) const; - - /// Get the location on which the report should be uniqued. - PathDiagnosticLocation getUniqueingLocation() const { - return UniqueingLocation; - } - - /// Get the declaration containing the uniqueing location. - const Decl *getUniqueingDecl() const { - return UniqueingDecl; - } - - const Stmt *getStmt() const; - - /// Add a range to a bug report. - /// - /// Ranges are used to highlight regions of interest in the source code. - /// They should be at the same source code line as the BugReport location. - /// By default, the source range of the statement corresponding to the error - /// node will be used; add a single invalid range to specify absence of - /// ranges. - void addRange(SourceRange R) { - assert((R.isValid() || Ranges.empty()) && "Invalid range can only be used " - "to specify that the report does not have a range."); - Ranges.push_back(R); - } - - /// Get the SourceRanges associated with the report. - virtual llvm::iterator_range getRanges(); + /// Profile to identify equivalent bug reports for error report coalescing. + /// Reports are uniqued to ensure that we do not emit multiple diagnostics + /// for each bug. + void Profile(llvm::FoldingSetNodeID &hash) const override; /// Add custom or predefined bug report visitors to this report. /// @@ -348,11 +499,34 @@ class BugReport : public llvm::ilist_node { /// Iterators through the custom diagnostic visitors. visitor_iterator visitor_begin() { return Callbacks.begin(); } visitor_iterator visitor_end() { return Callbacks.end(); } + visitor_range visitors() { return {visitor_begin(), visitor_end()}; } - /// Profile to identify equivalent bug reports for error report coalescing. - /// Reports are uniqued to ensure that we do not emit multiple diagnostics - /// for each bug. - virtual void Profile(llvm::FoldingSetNodeID& hash) const; + /// Notes that the condition of the CFGBlock associated with \p Cond is + /// being tracked. + /// \returns false if the condition is already being tracked. + bool addTrackedCondition(const ExplodedNode *Cond) { + return TrackedConditions.insert(Cond).second; + } + + void addCallStackHint(PathDiagnosticPieceRef Piece, + std::unique_ptr StackHint) { + StackHints[Piece] = std::move(StackHint); + } + + bool hasCallStackHint(PathDiagnosticPieceRef Piece) const { + return StackHints.count(Piece) > 0; + } + + /// Produce the hint for the given node. The node contains + /// information about the call for which the diagnostic can be generated. + std::string + getCallStackMessage(PathDiagnosticPieceRef Piece, + const ExplodedNode *N) const { + auto I = StackHints.find(Piece); + if (I != StackHints.end()) + return I->second->getMessage(N); + return ""; + } }; //===----------------------------------------------------------------------===// @@ -363,29 +537,21 @@ class BugReportEquivClass : public llvm::FoldingSetNode { friend class BugReporter; /// List of *owned* BugReport objects. - llvm::ilist Reports; + llvm::SmallVector, 4> Reports; - void AddReport(std::unique_ptr R) { - Reports.push_back(R.release()); + void AddReport(std::unique_ptr &&R) { + Reports.push_back(std::move(R)); } public: BugReportEquivClass(std::unique_ptr R) { AddReport(std::move(R)); } - ~BugReportEquivClass(); + + ArrayRef> getReports() const { return Reports; } void Profile(llvm::FoldingSetNodeID& ID) const { assert(!Reports.empty()); - Reports.front().Profile(ID); + Reports.front()->Profile(ID); } - - using iterator = llvm::ilist::iterator; - using const_iterator = llvm::ilist::const_iterator; - - iterator begin() { return Reports.begin(); } - iterator end() { return Reports.end(); } - - const_iterator begin() const { return Reports.begin(); } - const_iterator end() const { return Reports.end(); } }; //===----------------------------------------------------------------------===// @@ -394,9 +560,8 @@ class BugReportEquivClass : public llvm::FoldingSetNode { class BugReporterData { public: - virtual ~BugReporterData(); + virtual ~BugReporterData() = default; - virtual DiagnosticsEngine& getDiagnostic() = 0; virtual ArrayRef getPathDiagnosticConsumers() = 0; virtual ASTContext &getASTContext() = 0; virtual SourceManager &getSourceManager() = 0; @@ -409,60 +574,29 @@ class BugReporterData { /// /// The base class is used for generating path-insensitive class BugReporter { -public: - enum Kind { BaseBRKind, GRBugReporterKind }; - private: - using BugTypesTy = llvm::ImmutableSet; - - BugTypesTy::Factory F; - BugTypesTy BugTypes; - - const Kind kind; BugReporterData& D; /// Generate and flush the diagnostics for the given bug report. void FlushReport(BugReportEquivClass& EQ); - /// Generate the diagnostics for the given bug report. - std::unique_ptr - generateDiagnosticForConsumerMap(BugReport *exampleReport, - ArrayRef consumers, - ArrayRef bugReports); - /// The set of bug reports tracked by the BugReporter. llvm::FoldingSet EQClasses; /// A vector of BugReports for tracking the allocated pointers and cleanup. std::vector EQClassesVector; -protected: - BugReporter(BugReporterData& d, Kind k) - : BugTypes(F.getEmptySet()), kind(k), D(d) {} - public: - BugReporter(BugReporterData& d) - : BugTypes(F.getEmptySet()), kind(BaseBRKind), D(d) {} + BugReporter(BugReporterData &d) : D(d) {} virtual ~BugReporter(); /// Generate and flush diagnostics for all bug reports. void FlushReports(); - Kind getKind() const { return kind; } - - DiagnosticsEngine& getDiagnostic() { - return D.getDiagnostic(); - } - ArrayRef getPathDiagnosticConsumers() { return D.getPathDiagnosticConsumers(); } - /// Iterator over the set of BugTypes tracked by the BugReporter. - using iterator = BugTypesTy::iterator; - iterator begin() { return BugTypes.begin(); } - iterator end() { return BugTypes.end(); } - /// Iterator over the set of BugReports tracked by the BugReporter. using EQClasses_iterator = llvm::FoldingSet::iterator; EQClasses_iterator EQClasses_begin() { return EQClasses.begin(); } @@ -470,126 +604,116 @@ class BugReporter { ASTContext &getContext() { return D.getASTContext(); } - SourceManager &getSourceManager() { return D.getSourceManager(); } - - AnalyzerOptions &getAnalyzerOptions() { return D.getAnalyzerOptions(); } - - virtual std::unique_ptr - generatePathDiagnostics(ArrayRef consumers, - ArrayRef &bugReports) { - return {}; - } + const SourceManager &getSourceManager() { return D.getSourceManager(); } - void Register(const BugType *BT); + const AnalyzerOptions &getAnalyzerOptions() { return D.getAnalyzerOptions(); } /// Add the given report to the set of reports tracked by BugReporter. /// /// The reports are usually generated by the checkers. Further, they are /// folded based on the profile value, which is done to coalesce similar /// reports. - void emitReport(std::unique_ptr R); + virtual void emitReport(std::unique_ptr R); void EmitBasicReport(const Decl *DeclWithIssue, const CheckerBase *Checker, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, - ArrayRef Ranges = None); + ArrayRef Ranges = None, + ArrayRef Fixits = None); - void EmitBasicReport(const Decl *DeclWithIssue, CheckName CheckName, + void EmitBasicReport(const Decl *DeclWithIssue, CheckerNameRef CheckerName, StringRef BugName, StringRef BugCategory, StringRef BugStr, PathDiagnosticLocation Loc, - ArrayRef Ranges = None); + ArrayRef Ranges = None, + ArrayRef Fixits = None); private: llvm::StringMap StrBugTypes; /// Returns a BugType that is associated with the given name and /// category. - BugType *getBugTypeForName(CheckName CheckName, StringRef name, + BugType *getBugTypeForName(CheckerNameRef CheckerName, StringRef name, StringRef category); + + virtual BugReport * + findReportInEquivalenceClass(BugReportEquivClass &eqClass, + SmallVectorImpl &bugReports) { + return eqClass.getReports()[0].get(); + } + +protected: + /// Generate the diagnostics for the given bug report. + virtual std::unique_ptr + generateDiagnosticForConsumerMap(BugReport *exampleReport, + ArrayRef consumers, + ArrayRef bugReports); }; /// GRBugReporter is used for generating path-sensitive reports. -class GRBugReporter : public BugReporter { +class PathSensitiveBugReporter final : public BugReporter { ExprEngine& Eng; -public: - GRBugReporter(BugReporterData& d, ExprEngine& eng) - : BugReporter(d, GRBugReporterKind), Eng(eng) {} + BugReport *findReportInEquivalenceClass( + BugReportEquivClass &eqClass, + SmallVectorImpl &bugReports) override; - ~GRBugReporter() override; + /// Generate the diagnostics for the given bug report. + std::unique_ptr + generateDiagnosticForConsumerMap(BugReport *exampleReport, + ArrayRef consumers, + ArrayRef bugReports) override; +public: + PathSensitiveBugReporter(BugReporterData& d, ExprEngine& eng) + : BugReporter(d), Eng(eng) {} /// getGraph - Get the exploded graph created by the analysis engine /// for the analyzed method or function. - ExplodedGraph &getGraph(); + const ExplodedGraph &getGraph() const; /// getStateManager - Return the state manager used by the analysis /// engine. - ProgramStateManager &getStateManager(); + ProgramStateManager &getStateManager() const; /// \p bugReports A set of bug reports within a *single* equivalence class /// /// \return A mapping from consumers to the corresponding diagnostics. /// Iterates through the bug reports within a single equivalence class, /// stops at a first non-invalidated report. - std::unique_ptr - generatePathDiagnostics(ArrayRef consumers, - ArrayRef &bugReports) override; + std::unique_ptr generatePathDiagnostics( + ArrayRef consumers, + ArrayRef &bugReports); - /// classof - Used by isa<>, cast<>, and dyn_cast<>. - static bool classof(const BugReporter* R) { - return R->getKind() == GRBugReporterKind; - } + void emitReport(std::unique_ptr R) override; }; -class NodeMapClosure : public BugReport::NodeResolver { - InterExplodedGraphMap &M; - -public: - NodeMapClosure(InterExplodedGraphMap &m) : M(m) {} - - const ExplodedNode *getOriginalNode(const ExplodedNode *N) override { - return M.lookup(N); - } -}; - class BugReporterContext { - GRBugReporter &BR; - NodeMapClosure NMC; + PathSensitiveBugReporter &BR; virtual void anchor(); public: - BugReporterContext(GRBugReporter &br, InterExplodedGraphMap &Backmap) - : BR(br), NMC(Backmap) {} + BugReporterContext(PathSensitiveBugReporter &br) : BR(br) {} virtual ~BugReporterContext() = default; - GRBugReporter& getBugReporter() { return BR; } + PathSensitiveBugReporter& getBugReporter() { return BR; } - ExplodedGraph &getGraph() { return BR.getGraph(); } - - ProgramStateManager& getStateManager() { + ProgramStateManager& getStateManager() const { return BR.getStateManager(); } - SValBuilder &getSValBuilder() { - return getStateManager().getSValBuilder(); - } - - ASTContext &getASTContext() { + ASTContext &getASTContext() const { return BR.getContext(); } - SourceManager& getSourceManager() { + const SourceManager& getSourceManager() const { return BR.getSourceManager(); } - AnalyzerOptions &getAnalyzerOptions() { + const AnalyzerOptions &getAnalyzerOptions() const { return BR.getAnalyzerOptions(); } - - NodeMapClosure& getNodeResolver() { return NMC; } }; diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h index ef5d327d39da5..9117789b74faf 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h @@ -33,11 +33,12 @@ class Stmt; namespace ento { -class BugReport; +class PathSensitiveBugReport; class BugReporterContext; class ExplodedNode; class MemRegion; class PathDiagnosticPiece; +using PathDiagnosticPieceRef = std::shared_ptr; /// BugReporterVisitors are used to add custom diagnostics along a path. class BugReporterVisitor : public llvm::FoldingSetNode { @@ -57,32 +58,68 @@ class BugReporterVisitor : public llvm::FoldingSetNode { /// /// The last parameter can be used to register a new visitor with the given /// BugReport while processing a node. - virtual std::shared_ptr - VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) = 0; + virtual PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) = 0; /// Last function called on the visitor, no further calls to VisitNode /// would follow. virtual void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR); + PathSensitiveBugReport &BR); /// Provide custom definition for the final diagnostic piece on the /// path - the piece, which is displayed before the path is expanded. /// /// NOTE that this function can be implemented on at most one used visitor, /// and otherwise it crahes at runtime. - virtual std::shared_ptr - getEndPath(BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR); + virtual PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + PathSensitiveBugReport &BR); virtual void Profile(llvm::FoldingSetNodeID &ID) const = 0; /// Generates the default final diagnostic piece. - static std::shared_ptr - getDefaultEndPath(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR); + static PathDiagnosticPieceRef + getDefaultEndPath(const BugReporterContext &BRC, const ExplodedNode *N, + const PathSensitiveBugReport &BR); }; +namespace bugreporter { + +/// Specifies the type of tracking for an expression. +enum class TrackingKind { + /// Default tracking kind -- specifies that as much information should be + /// gathered about the tracked expression value as possible. + Thorough, + /// Specifies that a more moderate tracking should be used for the expression + /// value. This will essentially make sure that functions relevant to the it + /// aren't pruned, but otherwise relies on the user reading the code or + /// following the arrows. + Condition +}; + +/// Attempts to add visitors to track expression value back to its point of +/// origin. +/// +/// \param N A node "downstream" from the evaluation of the statement. +/// \param E The expression value which we are tracking +/// \param R The bug report to which visitors should be attached. +/// \param EnableNullFPSuppression Whether we should employ false positive +/// suppression (inlined defensive checks, returned null). +/// +/// \return Whether or not the function was able to add visitors for this +/// statement. Note that returning \c true does not actually imply +/// that any visitors were added. +bool trackExpressionValue(const ExplodedNode *N, const Expr *E, + PathSensitiveBugReport &R, + TrackingKind TKind = TrackingKind::Thorough, + bool EnableNullFPSuppression = true); + +const Expr *getDerefExpr(const Stmt *S); + +} // namespace bugreporter + /// Finds last store into the given region, /// which is different from a given symbolic value. class FindLastStoreBRVisitor final : public BugReporterVisitor { @@ -94,21 +131,36 @@ class FindLastStoreBRVisitor final : public BugReporterVisitor { /// bug, we are going to employ false positive suppression. bool EnableNullFPSuppression; -public: - /// Creates a visitor for every VarDecl inside a Stmt and registers it with - /// the BugReport. - static void registerStatementVarDecls(BugReport &BR, const Stmt *S, - bool EnableNullFPSuppression); + using TrackingKind = bugreporter::TrackingKind; + TrackingKind TKind; + const StackFrameContext *OriginSFC; +public: + /// \param V We're searching for the store where \c R received this value. + /// \param R The region we're tracking. + /// \param EnableNullFPSuppression Whether we should employ false positive + /// suppression (inlined defensive checks, returned null). + /// \param TKind May limit the amount of notes added to the bug report. + /// \param OriginSFC Only adds notes when the last store happened in a + /// different stackframe to this one. Disregarded if the tracking kind + /// is thorough. + /// This is useful, because for non-tracked regions, notes about + /// changes to its value in a nested stackframe could be pruned, and + /// this visitor can prevent that without polluting the bugpath too + /// much. FindLastStoreBRVisitor(KnownSVal V, const MemRegion *R, - bool InEnableNullFPSuppression) - : R(R), V(V), EnableNullFPSuppression(InEnableNullFPSuppression) {} + bool InEnableNullFPSuppression, TrackingKind TKind, + const StackFrameContext *OriginSFC = nullptr) + : R(R), V(V), EnableNullFPSuppression(InEnableNullFPSuppression), + TKind(TKind), OriginSFC(OriginSFC) { + assert(R); + } void Profile(llvm::FoldingSetNodeID &ID) const override; - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; }; class TrackConstraintBRVisitor final : public BugReporterVisitor { @@ -132,9 +184,9 @@ class TrackConstraintBRVisitor final : public BugReporterVisitor { /// to make all PathDiagnosticPieces created by this visitor. static const char *getTag(); - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: /// Checks if the constraint is valid in the current state. @@ -150,9 +202,9 @@ class NilReceiverBRVisitor final : public BugReporterVisitor { ID.AddPointer(&x); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; /// If the statement is a message send expression with nil receiver, returns /// the receiver expression. Returns NULL otherwise. @@ -162,8 +214,10 @@ class NilReceiverBRVisitor final : public BugReporterVisitor { /// Visitor that tries to report interesting diagnostics from conditions. class ConditionBRVisitor final : public BugReporterVisitor { // FIXME: constexpr initialization isn't supported by MSVC2013. - static const char *const GenericTrueMessage; - static const char *const GenericFalseMessage; + constexpr static llvm::StringLiteral GenericTrueMessage = + "Assuming the condition is true"; + constexpr static llvm::StringLiteral GenericFalseMessage = + "Assuming the condition is false"; public: void Profile(llvm::FoldingSetNodeID &ID) const override { @@ -175,41 +229,44 @@ class ConditionBRVisitor final : public BugReporterVisitor { /// to make all PathDiagnosticPieces created by this visitor. static const char *getTag(); - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; - std::shared_ptr VisitNodeImpl(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR); + PathDiagnosticPieceRef VisitNodeImpl(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR); - std::shared_ptr + PathDiagnosticPieceRef VisitTerminator(const Stmt *Term, const ExplodedNode *N, - const CFGBlock *srcBlk, const CFGBlock *dstBlk, BugReport &R, - BugReporterContext &BRC); + const CFGBlock *SrcBlk, const CFGBlock *DstBlk, + PathSensitiveBugReport &R, BugReporterContext &BRC); - std::shared_ptr - VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, BugReport &R, - const ExplodedNode *N, bool TookTrue); + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, + BugReporterContext &BRC, + PathSensitiveBugReport &R, + const ExplodedNode *N, bool TookTrue); - std::shared_ptr - VisitTrueTest(const Expr *Cond, const DeclRefExpr *DR, - BugReporterContext &BRC, BugReport &R, const ExplodedNode *N, - bool TookTrue, bool IsAssuming); + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, const DeclRefExpr *DR, + BugReporterContext &BRC, + PathSensitiveBugReport &R, + const ExplodedNode *N, bool TookTrue, + bool IsAssuming); - std::shared_ptr + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, const BinaryOperator *BExpr, - BugReporterContext &BRC, BugReport &R, const ExplodedNode *N, - bool TookTrue, bool IsAssuming); + BugReporterContext &BRC, PathSensitiveBugReport &R, + const ExplodedNode *N, bool TookTrue, bool IsAssuming); - std::shared_ptr - VisitTrueTest(const Expr *Cond, const MemberExpr *ME, BugReporterContext &BRC, - BugReport &R, const ExplodedNode *N, bool TookTrue, - bool IsAssuming); + PathDiagnosticPieceRef VisitTrueTest(const Expr *Cond, const MemberExpr *ME, + BugReporterContext &BRC, + PathSensitiveBugReport &R, + const ExplodedNode *N, bool TookTrue, + bool IsAssuming); - std::shared_ptr + PathDiagnosticPieceRef VisitConditionVariable(StringRef LhsString, const Expr *CondVarExpr, - BugReporterContext &BRC, BugReport &R, + BugReporterContext &BRC, PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue); /// Tries to print the value of the given expression. @@ -228,7 +285,7 @@ class ConditionBRVisitor final : public BugReporterVisitor { const Expr *ParentEx, raw_ostream &Out, BugReporterContext &BRC, - BugReport &R, + PathSensitiveBugReport &R, const ExplodedNode *N, Optional &prunable, bool IsSameFieldName); @@ -251,14 +308,13 @@ class LikelyFalsePositiveSuppressionBRVisitor final ID.AddPointer(getTag()); } - std::shared_ptr VisitNode(const ExplodedNode *, - BugReporterContext &, - BugReport &) override { + PathDiagnosticPieceRef VisitNode(const ExplodedNode *, BugReporterContext &, + PathSensitiveBugReport &) override { return nullptr; } void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *N, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; /// When a region containing undefined value or '0' value is passed @@ -279,9 +335,9 @@ class UndefOrNullArgVisitor final : public BugReporterVisitor { ID.AddPointer(R); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; }; class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor { @@ -308,9 +364,9 @@ class SuppressInlineDefensiveChecksVisitor final : public BugReporterVisitor { /// to make all PathDiagnosticPieces created by this visitor. static const char *getTag(); - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; }; /// The bug visitor will walk all the nodes in a path and collect all the @@ -326,12 +382,12 @@ class FalsePositiveRefutationBRVisitor final : public BugReporterVisitor { void Profile(llvm::FoldingSetNodeID &ID) const override; - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; void finalizeVisitor(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) override; + PathSensitiveBugReport &BR) override; }; @@ -340,32 +396,11 @@ class TagVisitor : public BugReporterVisitor { public: void Profile(llvm::FoldingSetNodeID &ID) const override; - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &R) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &R) override; }; -namespace bugreporter { - -/// Attempts to add visitors to track expression value back to its point of -/// origin. -/// -/// \param N A node "downstream" from the evaluation of the statement. -/// \param E The expression value which we are tracking -/// \param R The bug report to which visitors should be attached. -/// \param EnableNullFPSuppression Whether we should employ false positive -/// suppression (inlined defensive checks, returned null). -/// -/// \return Whether or not the function was able to add visitors for this -/// statement. Note that returning \c true does not actually imply -/// that any visitors were added. -bool trackExpressionValue(const ExplodedNode *N, const Expr *E, BugReport &R, - bool EnableNullFPSuppression = true); - -const Expr *getDerefExpr(const Stmt *S); - -} // namespace bugreporter - } // namespace ento } // namespace clang diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h index 324b5312e790d..237053df7e442 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/BugType.h @@ -28,8 +28,8 @@ class ExprEngine; class BugType { private: - const CheckName Check; - const std::string Name; + const CheckerNameRef CheckerName; + const std::string Description; const std::string Category; const CheckerBase *Checker; bool SuppressOnSink; @@ -37,28 +37,27 @@ class BugType { virtual void anchor(); public: - BugType(CheckName Check, StringRef Name, StringRef Cat, - bool SuppressOnSink=false) - : Check(Check), Name(Name), Category(Cat), Checker(nullptr), - SuppressOnSink(SuppressOnSink) {} + BugType(CheckerNameRef CheckerName, StringRef Name, StringRef Cat, + bool SuppressOnSink = false) + : CheckerName(CheckerName), Description(Name), Category(Cat), + Checker(nullptr), SuppressOnSink(SuppressOnSink) {} BugType(const CheckerBase *Checker, StringRef Name, StringRef Cat, - bool SuppressOnSink=false) - : Check(Checker->getCheckName()), Name(Name), Category(Cat), - Checker(Checker), SuppressOnSink(SuppressOnSink) {} + bool SuppressOnSink = false) + : CheckerName(Checker->getCheckerName()), Description(Name), + Category(Cat), Checker(Checker), SuppressOnSink(SuppressOnSink) {} virtual ~BugType() = default; - StringRef getName() const { return Name; } + StringRef getDescription() const { return Description; } StringRef getCategory() const { return Category; } - StringRef getCheckName() const { - // FIXME: This is a workaround to ensure that the correct check name is used - // The check names are set after the constructors are run. + StringRef getCheckerName() const { + // FIXME: This is a workaround to ensure that the correct checerk name is + // used. The checker names are set after the constructors are run. // In case the BugType object is initialized in the checker's ctor - // the Check field will be empty. To circumvent this problem we use + // the CheckerName field will be empty. To circumvent this problem we use // CheckerBase whenever it is possible. - StringRef CheckName = - Checker ? Checker->getCheckName().getName() : Check.getName(); - assert(!CheckName.empty() && "Check name is not set properly."); - return CheckName; + StringRef Ret = Checker ? Checker->getCheckerName() : CheckerName; + assert(!Ret.empty() && "Checker name is not set properly."); + return Ret; } /// isSuppressOnSink - Returns true if bug reports associated with this bug @@ -71,8 +70,9 @@ class BuiltinBug : public BugType { const std::string desc; void anchor() override; public: - BuiltinBug(class CheckName check, const char *name, const char *description) - : BugType(check, name, categories::LogicError), desc(description) {} + BuiltinBug(class CheckerNameRef checker, const char *name, + const char *description) + : BugType(checker, name, categories::LogicError), desc(description) {} BuiltinBug(const CheckerBase *checker, const char *name, const char *description) diff --git a/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h b/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h index 85526eb49f0cb..22c1a7dd98ccb 100644 --- a/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h +++ b/clang/include/clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h @@ -18,6 +18,7 @@ namespace clang { extern const char * const MemoryRefCount; extern const char * const MemoryError; extern const char * const UnixAPI; + extern const char * const CXXObjectLifecycle; } } } diff --git a/clang/include/clang/StaticAnalyzer/Core/Checker.h b/clang/include/clang/StaticAnalyzer/Core/Checker.h index db3ae74f3e71c..0c7acdbc3a97e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/Checker.h +++ b/clang/include/clang/StaticAnalyzer/Core/Checker.h @@ -474,8 +474,9 @@ class Assume { class Call { template - static bool _evalCall(void *checker, const CallExpr *CE, CheckerContext &C) { - return ((const CHECKER *)checker)->evalCall(CE, C); + static bool _evalCall(void *checker, const CallEvent &Call, + CheckerContext &C) { + return ((const CHECKER *)checker)->evalCall(Call, C); } public: @@ -489,12 +490,12 @@ class Call { } // end eval namespace class CheckerBase : public ProgramPointTag { - CheckName Name; + CheckerNameRef Name; friend class ::clang::ento::CheckerManager; public: StringRef getTagDescription() const override; - CheckName getCheckName() const; + CheckerNameRef getCheckerName() const; /// See CheckerManager::runCheckersForPrintState. virtual void printState(raw_ostream &Out, ProgramStateRef State, diff --git a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h index 98c69039efd1f..38a9aaf72c27a 100644 --- a/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/CheckerManager.h @@ -90,21 +90,23 @@ enum PointerEscapeKind { PSK_EscapeOther }; -// This wrapper is used to ensure that only StringRefs originating from the -// CheckerRegistry are used as check names. We want to make sure all check -// name strings have a lifetime that keeps them alive at least until the path -// diagnostics have been processed. -class CheckName { +/// This wrapper is used to ensure that only StringRefs originating from the +/// CheckerRegistry are used as check names. We want to make sure all checker +/// name strings have a lifetime that keeps them alive at least until the path +/// diagnostics have been processed, since they are expected to be constexpr +/// string literals (most likely generated by TblGen). +class CheckerNameRef { friend class ::clang::ento::CheckerRegistry; StringRef Name; - explicit CheckName(StringRef Name) : Name(Name) {} + explicit CheckerNameRef(StringRef Name) : Name(Name) {} public: - CheckName() = default; + CheckerNameRef() = default; StringRef getName() const { return Name; } + operator StringRef() const { return Name; } }; enum class ObjCMessageVisitKind { @@ -117,7 +119,7 @@ class CheckerManager { ASTContext &Context; const LangOptions LangOpts; AnalyzerOptions &AOptions; - CheckName CurrentCheckName; + CheckerNameRef CurrentCheckerName; public: CheckerManager(ASTContext &Context, AnalyzerOptions &AOptions) @@ -125,8 +127,8 @@ class CheckerManager { ~CheckerManager(); - void setCurrentCheckName(CheckName name) { CurrentCheckName = name; } - CheckName getCurrentCheckName() const { return CurrentCheckName; } + void setCurrentCheckerName(CheckerNameRef name) { CurrentCheckerName = name; } + CheckerNameRef getCurrentCheckerName() const { return CurrentCheckerName; } bool hasPathSensitiveCheckers() const; @@ -162,7 +164,7 @@ class CheckerManager { assert(!ref && "Checker already registered, use getChecker!"); CHECKER *checker = new CHECKER(std::forward(Args)...); - checker->Name = CurrentCheckName; + checker->Name = CurrentCheckerName; CheckerDtors.push_back(CheckerDtor(checker, destruct)); CHECKER::_register(checker, *this); ref = checker; @@ -490,7 +492,7 @@ class CheckerManager { CheckerFn; - using EvalCallFunc = CheckerFn; + using EvalCallFunc = CheckerFn; using CheckEndOfTranslationUnit = CheckerFn PathDiagnosticConsumers; -#define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN)\ -void CREATEFN(AnalyzerOptions &AnalyzerOpts,\ - PathDiagnosticConsumers &C,\ - const std::string &Prefix,\ - const Preprocessor &PP); +#define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN) \ + void CREATEFN(AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, \ + const std::string &Prefix, const Preprocessor &PP, \ + const cross_tu::CrossTranslationUnitContext &CTU); #include "clang/StaticAnalyzer/Core/Analyses.def" } // end 'ento' namespace diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h index b0dda78a00a98..d605a6a667f6b 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h @@ -15,9 +15,9 @@ #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_ANALYSISMANAGER_H #include "clang/Analysis/AnalysisDeclContext.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" namespace clang { @@ -32,7 +32,6 @@ class AnalysisManager : public BugReporterData { AnalysisDeclContextManager AnaCtxMgr; ASTContext &Ctx; - DiagnosticsEngine &Diags; const LangOptions &LangOpts; PathDiagnosticConsumers PathConsumers; @@ -45,7 +44,7 @@ class AnalysisManager : public BugReporterData { public: AnalyzerOptions &options; - AnalysisManager(ASTContext &ctx, DiagnosticsEngine &diags, + AnalysisManager(ASTContext &ctx, const PathDiagnosticConsumers &Consumers, StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, @@ -84,10 +83,6 @@ class AnalysisManager : public BugReporterData { return getASTContext().getSourceManager(); } - DiagnosticsEngine &getDiagnostic() override { - return Diags; - } - const LangOptions &getLangOpts() const { return LangOpts; } diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h index 19996cf9a1b11..fc1cc91388266 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h @@ -71,39 +71,7 @@ enum CallEventKind { }; class CallEvent; - -/// This class represents a description of a function call using the number of -/// arguments and the name of the function. -class CallDescription { - friend CallEvent; - - mutable IdentifierInfo *II = nullptr; - mutable bool IsLookupDone = false; - // The list of the qualified names used to identify the specified CallEvent, - // e.g. "{a, b}" represent the qualified names, like "a::b". - std::vector QualifiedName; - unsigned RequiredArgs; - -public: - const static unsigned NoArgRequirement = std::numeric_limits::max(); - - /// Constructs a CallDescription object. - /// - /// @param QualifiedName The list of the name qualifiers of the function that - /// will be matched. The user is allowed to skip any of the qualifiers. - /// For example, {"std", "basic_string", "c_str"} would match both - /// std::basic_string<...>::c_str() and std::__1::basic_string<...>::c_str(). - /// - /// @param RequiredArgs The number of arguments that is expected to match a - /// call. Omit this parameter to match every occurrence of call with a given - /// name regardless the number of arguments. - CallDescription(ArrayRef QualifiedName, - unsigned RequiredArgs = NoArgRequirement) - : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs) {} - - /// Get the name of the function that this object matches. - StringRef getFunctionName() const { return QualifiedName.back(); } -}; +class CallDescription; template class CallEventRef : public IntrusiveRefCntPtr { @@ -379,7 +347,7 @@ class CallEvent { ProgramStateRef invalidateRegions(unsigned BlockCount, ProgramStateRef Orig = nullptr) const; - using FrameBindingTy = std::pair; + using FrameBindingTy = std::pair; using BindingsTy = SmallVectorImpl; /// Populates the given SmallVector with the bindings in the callee's stack @@ -418,11 +386,12 @@ class CallEvent { /// during analysis if the call is inlined, but it may still be useful /// in intermediate calculations even if the call isn't inlined. /// May fail; returns null on failure. - const StackFrameContext *getCalleeStackFrame() const; + const StackFrameContext *getCalleeStackFrame(unsigned BlockCount) const; /// Returns memory location for a parameter variable within the callee stack /// frame. May fail; returns null on failure. - const VarRegion *getParameterLocation(unsigned Index) const; + const VarRegion *getParameterLocation(unsigned Index, + unsigned BlockCount) const; /// Returns true if on the current path, the argument was constructed by /// calling a C++ constructor over it. This is an internal detail of the @@ -1076,6 +1045,99 @@ class ObjCMethodCall : public CallEvent { } }; +enum CallDescriptionFlags : int { + /// Describes a C standard function that is sometimes implemented as a macro + /// that expands to a compiler builtin with some __builtin prefix. + /// The builtin may as well have a few extra arguments on top of the requested + /// number of arguments. + CDF_MaybeBuiltin = 1 << 0, +}; + +/// This class represents a description of a function call using the number of +/// arguments and the name of the function. +class CallDescription { + friend CallEvent; + + mutable IdentifierInfo *II = nullptr; + mutable bool IsLookupDone = false; + // The list of the qualified names used to identify the specified CallEvent, + // e.g. "{a, b}" represent the qualified names, like "a::b". + std::vector QualifiedName; + Optional RequiredArgs; + Optional RequiredParams; + int Flags; + + // A constructor helper. + static Optional readRequiredParams(Optional RequiredArgs, + Optional RequiredParams) { + if (RequiredParams) + return RequiredParams; + if (RequiredArgs) + return static_cast(*RequiredArgs); + return None; + } + +public: + /// Constructs a CallDescription object. + /// + /// @param QualifiedName The list of the name qualifiers of the function that + /// will be matched. The user is allowed to skip any of the qualifiers. + /// For example, {"std", "basic_string", "c_str"} would match both + /// std::basic_string<...>::c_str() and std::__1::basic_string<...>::c_str(). + /// + /// @param RequiredArgs The number of arguments that is expected to match a + /// call. Omit this parameter to match every occurrence of call with a given + /// name regardless the number of arguments. + CallDescription(int Flags, ArrayRef QualifiedName, + Optional RequiredArgs = None, + Optional RequiredParams = None) + : QualifiedName(QualifiedName), RequiredArgs(RequiredArgs), + RequiredParams(readRequiredParams(RequiredArgs, RequiredParams)), + Flags(Flags) {} + + /// Construct a CallDescription with default flags. + CallDescription(ArrayRef QualifiedName, + Optional RequiredArgs = None, + Optional RequiredParams = None) + : CallDescription(0, QualifiedName, RequiredArgs, RequiredParams) {} + + /// Get the name of the function that this object matches. + StringRef getFunctionName() const { return QualifiedName.back(); } +}; + +/// An immutable map from CallDescriptions to arbitrary data. Provides a unified +/// way for checkers to react on function calls. +template class CallDescriptionMap { + // Some call descriptions aren't easily hashable (eg., the ones with qualified + // names in which some sections are omitted), so let's put them + // in a simple vector and use linear lookup. + // TODO: Implement an actual map for fast lookup for "hashable" call + // descriptions (eg., the ones for C functions that just match the name). + std::vector> LinearMap; + +public: + CallDescriptionMap( + std::initializer_list> &&List) + : LinearMap(List) {} + + ~CallDescriptionMap() = default; + + // These maps are usually stored once per checker, so let's make sure + // we don't do redundant copies. + CallDescriptionMap(const CallDescriptionMap &) = delete; + CallDescriptionMap &operator=(const CallDescription &) = delete; + + const T *lookup(const CallEvent &Call) const { + // Slow path: linear lookup. + // TODO: Implement some sort of fast path. + for (const std::pair &I : LinearMap) + if (Call.isCalled(I.first)) + return &I.second; + + return nullptr; + } +}; + /// Manages the lifetime of CallEvent objects. /// /// CallEventManager provides a way to create arbitrary CallEvents "on the diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h index 6a49aebc14ce4..7f4df0d88def6 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h @@ -103,7 +103,7 @@ class CheckerContext { return Eng.getBugReporter(); } - SourceManager &getSourceManager() { + const SourceManager &getSourceManager() { return getBugReporter().getSourceManager(); } @@ -219,23 +219,57 @@ class CheckerContext { Eng.getBugReporter().emitReport(std::move(R)); } - /// Produce a program point tag that displays an additional path note /// to the user. This is a lightweight alternative to the /// BugReporterVisitor mechanism: instead of visiting the bug report /// node-by-node to restore the sequence of events that led to discovering /// a bug, you can add notes as you add your transitions. - const NoteTag *getNoteTag(NoteTag::Callback &&Cb) { - return Eng.getNoteTags().makeNoteTag(std::move(Cb)); + /// + /// @param Cb Callback with 'BugReporterContext &, BugReport &' parameters. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(NoteTag::Callback &&Cb, bool IsPrunable = false) { + return Eng.getNoteTags().makeNoteTag(std::move(Cb), IsPrunable); } /// A shorthand version of getNoteTag that doesn't require you to accept - /// the BugReporterContext arguments when you don't need it. - const NoteTag *getNoteTag(std::function &&Cb) { + /// the 'BugReporterContext' argument when you don't need it. + /// + /// @param Cb Callback only with 'BugReport &' parameter. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(std::function &&Cb, + bool IsPrunable = false) { return getNoteTag( - [Cb](BugReporterContext &, BugReport &BR) { return Cb(BR); }); + [Cb](BugReporterContext &, BugReport &BR) { return Cb(BR); }, + IsPrunable); + } + + /// A shorthand version of getNoteTag that doesn't require you to accept + /// the arguments when you don't need it. + /// + /// @param Cb Callback without parameters. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(std::function &&Cb, + bool IsPrunable = false) { + return getNoteTag([Cb](BugReporterContext &, BugReport &) { return Cb(); }, + IsPrunable); } + /// A shorthand version of getNoteTag that accepts a plain note. + /// + /// @param Note The note. + /// @param IsPrunable Whether the note is prunable. It allows BugReporter + /// to omit the note from the report if it would make the displayed + /// bug path significantly shorter. + const NoteTag *getNoteTag(StringRef Note, bool IsPrunable = false) { + return getNoteTag( + [Note](BugReporterContext &, BugReport &) { return Note; }, IsPrunable); + } /// Returns the word that should be used to refer to the declaration /// in the report. diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h new file mode 100644 index 0000000000000..f5a710c77a6ad --- /dev/null +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h @@ -0,0 +1,55 @@ +//===- DynamicCastInfo.h - Runtime cast information -------------*- 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_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H +#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H + +#include "clang/AST/Type.h" + +namespace clang { +namespace ento { + +class DynamicCastInfo { +public: + enum CastResult { Success, Failure }; + + DynamicCastInfo(QualType from, QualType to, CastResult resultKind) + : From(from), To(to), ResultKind(resultKind) {} + + QualType from() const { return From; } + QualType to() const { return To; } + + bool equals(QualType from, QualType to) const { + return From == from && To == to; + } + + bool succeeds() const { return ResultKind == CastResult::Success; } + bool fails() const { return ResultKind == CastResult::Failure; } + + bool operator==(const DynamicCastInfo &RHS) const { + return From == RHS.From && To == RHS.To; + } + bool operator<(const DynamicCastInfo &RHS) const { + return From < RHS.From && To < RHS.To; + } + + void Profile(llvm::FoldingSetNodeID &ID) const { + ID.Add(From); + ID.Add(To); + ID.AddInteger(ResultKind); + } + +private: + QualType From, To; + CastResult ResultKind; +}; + +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICCASTINFO_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h new file mode 100644 index 0000000000000..356401d77561c --- /dev/null +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h @@ -0,0 +1,73 @@ +//===- DynamicType.h - Dynamic type related APIs ----------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs that track and query dynamic type information. This +// information can be used to devirtualize calls during the symbolic execution +// or do type checking. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPE_H +#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPE_H + +#include "clang/AST/Type.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicCastInfo.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymbolManager.h" +#include "llvm/ADT/ImmutableMap.h" +#include "llvm/ADT/Optional.h" +#include + +namespace clang { +namespace ento { + +/// Get dynamic type information for the region \p MR. +DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR); + +/// Get raw dynamic type information for the region \p MR. +const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, + const MemRegion *MR); + +/// Get dynamic cast information from \p CastFromTy to \p CastToTy of \p MR. +const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy); + +/// Set dynamic type information of the region; return the new state. +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + DynamicTypeInfo NewTy); + +/// Set dynamic type information of the region; return the new state. +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + QualType NewTy, bool CanBeSubClassed = true); + +/// Set dynamic type and cast information of the region; return the new state. +ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy, + bool IsCastSucceeds); + +/// Removes the dead type informations from \p State. +ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR); + +/// Removes the dead cast informations from \p State. +ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR); + +void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, + const char *NL = "\n", unsigned int Space = 0, + bool IsDot = false); + +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPE_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h index 9bb1e21375666..6262c4a1ce378 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h @@ -1,10 +1,11 @@ -//== DynamicTypeInfo.h - Runtime type information ----------------*- C++ -*--=// +//===- DynamicTypeInfo.h - Runtime type information -------------*- 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_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEINFO_H #define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEINFO_H @@ -16,36 +17,37 @@ namespace ento { /// Stores the currently inferred strictest bound on the runtime type /// of a region in a given state along the analysis path. class DynamicTypeInfo { -private: - QualType T; - bool CanBeASubClass; - public: + DynamicTypeInfo() : DynTy(QualType()) {} - DynamicTypeInfo() : T(QualType()) {} - DynamicTypeInfo(QualType WithType, bool CanBeSub = true) - : T(WithType), CanBeASubClass(CanBeSub) {} + DynamicTypeInfo(QualType Ty, bool CanBeSub = true) + : DynTy(Ty), CanBeASubClass(CanBeSub) {} + + /// Returns false if the type information is precise (the type 'DynTy' is + /// the only type in the lattice), true otherwise. + bool canBeASubClass() const { return CanBeASubClass; } - /// Return false if no dynamic type info is available. - bool isValid() const { return !T.isNull(); } + /// Returns true if the dynamic type info is available. + bool isValid() const { return !DynTy.isNull(); } /// Returns the currently inferred upper bound on the runtime type. - QualType getType() const { return T; } + QualType getType() const { return DynTy; } - /// Returns false if the type information is precise (the type T is - /// the only type in the lattice), true otherwise. - bool canBeASubClass() const { return CanBeASubClass; } + bool operator==(const DynamicTypeInfo &RHS) const { + return DynTy == RHS.DynTy && CanBeASubClass == RHS.CanBeASubClass; + } void Profile(llvm::FoldingSetNodeID &ID) const { - ID.Add(T); - ID.AddInteger((unsigned)CanBeASubClass); - } - bool operator==(const DynamicTypeInfo &X) const { - return T == X.T && CanBeASubClass == X.CanBeASubClass; + ID.Add(DynTy); + ID.AddBoolean(CanBeASubClass); } + +private: + QualType DynTy; + bool CanBeASubClass; }; -} // end ento -} // end clang +} // namespace ento +} // namespace clang -#endif +#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEINFO_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h deleted file mode 100644 index a84b248720618..0000000000000 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h +++ /dev/null @@ -1,63 +0,0 @@ -//===- DynamicTypeMap.h - Dynamic type map ----------------------*- 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 -// -//===----------------------------------------------------------------------===// -// -// This file provides APIs for tracking dynamic type information. -// -//===----------------------------------------------------------------------===// - -#ifndef LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEMAP_H -#define LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEMAP_H - -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include "llvm/ADT/ImmutableMap.h" -#include "clang/AST/Type.h" - -namespace clang { -namespace ento { - -class MemRegion; - -/// The GDM component containing the dynamic type info. This is a map from a -/// symbol to its most likely type. -struct DynamicTypeMap {}; - -using DynamicTypeMapTy = llvm::ImmutableMap; - -template <> -struct ProgramStateTrait - : public ProgramStatePartialTrait { - static void *GDMIndex(); -}; - -/// Get dynamic type information for a region. -DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, - const MemRegion *Reg); - -/// Set dynamic type information of the region; return the new state. -ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *Reg, - DynamicTypeInfo NewTy); - -/// Set dynamic type information of the region; return the new state. -inline ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, - const MemRegion *Reg, QualType NewTy, - bool CanBeSubClassed = true) { - return setDynamicTypeInfo(State, Reg, - DynamicTypeInfo(NewTy, CanBeSubClassed)); -} - -void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, - const char *NL = "\n", unsigned int Space = 0, - bool IsDot = false); - -} // namespace ento -} // namespace clang - -#endif // LLVM_CLANG_STATICANALYZER_CORE_PATHSENSITIVE_DYNAMICTYPEMAP_H diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h index 727d04cba2784..2c9b0e5b9968e 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h @@ -131,10 +131,12 @@ class ExplodedNode : public llvm::FoldingSetNode { /// Succs - The successors of this node. NodeGroup Succs; + int64_t Id; + public: explicit ExplodedNode(const ProgramPoint &loc, ProgramStateRef state, - bool IsSink) - : Location(loc), State(std::move(state)), Succs(IsSink) { + int64_t Id, bool IsSink) + : Location(loc), State(std::move(state)), Succs(IsSink), Id(Id) { assert(isSink() == IsSink); } @@ -153,7 +155,11 @@ class ExplodedNode : public llvm::FoldingSetNode { CFG &getCFG() const { return *getLocationContext()->getCFG(); } - ParentMap &getParentMap() const {return getLocationContext()->getParentMap();} + const CFGBlock *getCFGBlock() const; + + const ParentMap &getParentMap() const { + return getLocationContext()->getParentMap(); + } template T &getAnalysis() const { @@ -219,12 +225,20 @@ class ExplodedNode : public llvm::FoldingSetNode { // Iterators over successor and predecessor vertices. using succ_iterator = ExplodedNode * const *; + using succ_range = llvm::iterator_range; + using const_succ_iterator = const ExplodedNode * const *; + using const_succ_range = llvm::iterator_range; + using pred_iterator = ExplodedNode * const *; + using pred_range = llvm::iterator_range; + using const_pred_iterator = const ExplodedNode * const *; + using const_pred_range = llvm::iterator_range; pred_iterator pred_begin() { return Preds.begin(); } pred_iterator pred_end() { return Preds.end(); } + pred_range preds() { return {Preds.begin(), Preds.end()}; } const_pred_iterator pred_begin() const { return const_cast(this)->pred_begin(); @@ -232,9 +246,11 @@ class ExplodedNode : public llvm::FoldingSetNode { const_pred_iterator pred_end() const { return const_cast(this)->pred_end(); } + const_pred_range preds() const { return {Preds.begin(), Preds.end()}; } succ_iterator succ_begin() { return Succs.begin(); } succ_iterator succ_end() { return Succs.end(); } + succ_range succs() { return {Succs.begin(), Succs.end()}; } const_succ_iterator succ_begin() const { return const_cast(this)->succ_begin(); @@ -242,8 +258,9 @@ class ExplodedNode : public llvm::FoldingSetNode { const_succ_iterator succ_end() const { return const_cast(this)->succ_end(); } + const_succ_range succs() const { return {Succs.begin(), Succs.end()}; } - int64_t getID(ExplodedGraph *G) const; + int64_t getID() const { return Id; } /// The node is trivial if it has only one successor, only one predecessor, /// it's predecessor has only one successor, @@ -252,6 +269,30 @@ class ExplodedNode : public llvm::FoldingSetNode { /// Trivial nodes may be skipped while printing exploded graph. bool isTrivial() const; + /// If the node's program point corresponds to a statement, retrieve that + /// statement. Useful for figuring out where to put a warning or a note. + /// If the statement belongs to a body-farmed definition, + /// retrieve the call site for that definition. + const Stmt *getStmtForDiagnostics() const; + + /// Find the next statement that was executed on this node's execution path. + /// Useful for explaining control flow that follows the current node. + /// If the statement belongs to a body-farmed definition, retrieve the + /// call site for that definition. + const Stmt *getNextStmtForDiagnostics() const; + + /// Find the statement that was executed immediately before this node. + /// Useful when the node corresponds to a CFG block entrance. + /// If the statement belongs to a body-farmed definition, retrieve the + /// call site for that definition. + const Stmt *getPreviousStmtForDiagnostics() const; + + /// Find the statement that was executed at or immediately before this node. + /// Useful when any nearby statement will do. + /// If the statement belongs to a body-farmed definition, retrieve the + /// call site for that definition. + const Stmt *getCurrentOrPreviousStmtForDiagnostics() const; + private: void replaceSuccessor(ExplodedNode *node) { Succs.replaceNode(node); } void replacePredecessor(ExplodedNode *node) { Preds.replaceNode(node); } @@ -285,7 +326,7 @@ class ExplodedGraph { BumpVectorContext BVC; /// NumNodes - The number of nodes in the graph. - unsigned NumNodes = 0; + int64_t NumNodes = 0; /// A list of recently allocated nodes that can potentially be recycled. NodeVector ChangedNodes; @@ -319,6 +360,7 @@ class ExplodedGraph { /// ExplodedGraph for further processing. ExplodedNode *createUncachedNode(const ProgramPoint &L, ProgramStateRef State, + int64_t Id, bool IsSink = false); std::unique_ptr MakeEmptyGraph() const { diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h index 6daaa129fc7cb..2d0967616ff20 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h @@ -145,9 +145,9 @@ class ExprEngine : public SubEngine { ObjCNoReturn ObjCNoRet; /// The BugReporter associated with this engine. It is important that - /// this object be placed at the very end of member variables so that its - /// destructor is called before the rest of the ExprEngine is destroyed. - GRBugReporter BR; + /// this object be placed at the very end of member variables so that its + /// destructor is called before the rest of the ExprEngine is destroyed. + PathSensitiveBugReporter BR; /// The functions which have been analyzed through inlining. This is owned by /// AnalysisConsumer. It can be null. @@ -161,7 +161,7 @@ class ExprEngine : public SubEngine { SetOfConstDecls *VisitedCalleesIn, FunctionSummariesTy *FS, InliningModes HowToInlineIn); - ~ExprEngine() override; + ~ExprEngine() override = default; /// Returns true if there is still simulation state on the worklist. bool ExecuteWorkList(const LocationContext *L, unsigned Steps = 150000) { @@ -530,7 +530,7 @@ class ExprEngine : public SubEngine { void VisitCXXDestructor(QualType ObjectType, const MemRegion *Dest, const Stmt *S, bool IsBaseDtor, ExplodedNode *Pred, ExplodedNodeSet &Dst, - const EvalCallOptions &Options); + EvalCallOptions &Options); void VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, ExplodedNode *Pred, @@ -666,7 +666,7 @@ class ExprEngine : public SubEngine { const LocationContext *LCtx, ProgramStateRef State); - /// Evaluate a call, running pre- and post-call checks and allowing checkers + /// Evaluate a call, running pre- and post-call checkers and allowing checkers /// to be responsible for handling the evaluation of the call itself. void evalCall(ExplodedNodeSet &Dst, ExplodedNode *Pred, const CallEvent &Call); diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h index 071e35085a5f9..71cbbe28fc258 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h @@ -169,6 +169,7 @@ class MemRegion : public llvm::FoldingSetNode { Kind getKind() const { return kind; } template const RegionTy* getAs() const; + template const RegionTy* castAs() const; virtual bool isBoundable() const { return false; } @@ -1231,6 +1232,11 @@ const RegionTy* MemRegion::getAs() const { return nullptr; } +template +const RegionTy* MemRegion::castAs() const { + return cast(this); +} + //===----------------------------------------------------------------------===// // MemRegionManager - Factory object for creating regions. //===----------------------------------------------------------------------===// diff --git a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h index d38058f9af56d..07920790c80aa 100644 --- a/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h +++ b/clang/include/clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h @@ -507,6 +507,10 @@ class ProgramStateManager { return *svalBuilder; } + const SValBuilder &getSValBuilder() const { + return *svalBuilder; + } + SymbolManager &getSymbolManager() { return svalBuilder->getSymbolManager(); } diff --git a/clang/lib/AST/ASTImporter.cpp b/clang/lib/AST/ASTImporter.cpp index e4bb0a6db361a..eef42033d5127 100644 --- a/clang/lib/AST/ASTImporter.cpp +++ b/clang/lib/AST/ASTImporter.cpp @@ -8496,6 +8496,10 @@ Expected ASTImporter::Import(FileID FromID, bool IsBuiltin) { assert(ToID.isValid() && "Unexpected invalid fileID was created."); ImportedFileIDs[FromID] = ToID; + + if (FileIDImportHandler) + FileIDImportHandler(ToID, FromID); + return ToID; } diff --git a/clang/lib/Analysis/AnalysisDeclContext.cpp b/clang/lib/Analysis/AnalysisDeclContext.cpp index ba1f8375124ea..5ecb3ff470ad4 100644 --- a/clang/lib/Analysis/AnalysisDeclContext.cpp +++ b/clang/lib/Analysis/AnalysisDeclContext.cpp @@ -310,8 +310,10 @@ BodyFarm &AnalysisDeclContextManager::getBodyFarm() { return FunctionBodyFarm; } const StackFrameContext * AnalysisDeclContext::getStackFrame(LocationContext const *Parent, const Stmt *S, - const CFGBlock *Blk, unsigned Idx) { - return getLocationContextManager().getStackFrame(this, Parent, S, Blk, Idx); + const CFGBlock *Blk, unsigned BlockCount, + unsigned Idx) { + return getLocationContextManager().getStackFrame(this, Parent, S, Blk, + BlockCount, Idx); } const BlockInvocationContext * @@ -359,7 +361,8 @@ void LocationContext::ProfileCommon(llvm::FoldingSetNodeID &ID, } void StackFrameContext::Profile(llvm::FoldingSetNodeID &ID) { - Profile(ID, getAnalysisDeclContext(), getParent(), CallSite, Block, Index); + Profile(ID, getAnalysisDeclContext(), getParent(), CallSite, Block, + BlockCount, Index); } void ScopeContext::Profile(llvm::FoldingSetNodeID &ID) { @@ -392,18 +395,16 @@ LocationContextManager::getLocationContext(AnalysisDeclContext *ctx, return L; } -const StackFrameContext* -LocationContextManager::getStackFrame(AnalysisDeclContext *ctx, - const LocationContext *parent, - const Stmt *s, - const CFGBlock *blk, unsigned idx) { +const StackFrameContext *LocationContextManager::getStackFrame( + AnalysisDeclContext *ctx, const LocationContext *parent, const Stmt *s, + const CFGBlock *blk, unsigned blockCount, unsigned idx) { llvm::FoldingSetNodeID ID; - StackFrameContext::Profile(ID, ctx, parent, s, blk, idx); + StackFrameContext::Profile(ID, ctx, parent, s, blk, blockCount, idx); void *InsertPos; auto *L = cast_or_null(Contexts.FindNodeOrInsertPos(ID, InsertPos)); if (!L) { - L = new StackFrameContext(ctx, parent, s, blk, idx, ++NewID); + L = new StackFrameContext(ctx, parent, s, blk, blockCount, idx, ++NewID); Contexts.InsertNode(L, InsertPos); } return L; @@ -527,7 +528,8 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, unsigned Frame = 0; for (const LocationContext *LCtx = this; LCtx; LCtx = LCtx->getParent()) { - Indent(Out, Space, IsDot) << "{ \"location_context\": \""; + Indent(Out, Space, IsDot) + << "{ \"lctx_id\": " << LCtx->getID() << ", \"location_context\": \""; switch (LCtx->getKind()) { case StackFrame: Out << '#' << Frame << " Call\", \"calling\": \""; @@ -537,11 +539,9 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, else Out << "anonymous code"; - Out << "\", \"call_line\": "; + Out << "\", \"location\": "; if (const Stmt *S = cast(LCtx)->getCallSite()) { - Out << '\"'; - printLocation(Out, SM, S->getBeginLoc()); - Out << '\"'; + printSourceLocationAsJson(Out, S->getBeginLoc(), SM); } else { Out << "null"; } @@ -554,8 +554,8 @@ void LocationContext::printJson(raw_ostream &Out, const char *NL, case Block: Out << "Invoking block\" "; if (const Decl *D = cast(LCtx)->getDecl()) { - Out << ", \"decl_line\": "; - printLocation(Out, SM, D->getBeginLoc()); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, D->getBeginLoc(), SM); Out << ' '; } break; diff --git a/clang/lib/Analysis/BodyFarm.cpp b/clang/lib/Analysis/BodyFarm.cpp index 7e636ed1f5889..a0ad130b09f0f 100644 --- a/clang/lib/Analysis/BodyFarm.cpp +++ b/clang/lib/Analysis/BodyFarm.cpp @@ -408,8 +408,8 @@ static Stmt *create_call_once(ASTContext &C, const FunctionDecl *D) { // reference. for (unsigned int ParamIdx = 2; ParamIdx < D->getNumParams(); ParamIdx++) { const ParmVarDecl *PDecl = D->getParamDecl(ParamIdx); - if (PDecl && - CallbackFunctionType->getParamType(ParamIdx - 2) + assert(PDecl); + if (CallbackFunctionType->getParamType(ParamIdx - 2) .getNonReferenceType() .getCanonicalType() != PDecl->getType().getNonReferenceType().getCanonicalType()) { diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index b53bfcca37cd4..21fa9bee25ef3 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -525,7 +525,7 @@ class CFGBuilder { CFGBlock *VisitCallExpr(CallExpr *C, AddStmtChoice asc); CFGBlock *VisitCaseStmt(CaseStmt *C); CFGBlock *VisitChooseExpr(ChooseExpr *C, AddStmtChoice asc); - CFGBlock *VisitCompoundStmt(CompoundStmt *C); + CFGBlock *VisitCompoundStmt(CompoundStmt *C, bool ExternallyDestructed); CFGBlock *VisitConditionalOperator(AbstractConditionalOperator *C, AddStmtChoice asc); CFGBlock *VisitContinueStmt(ContinueStmt *C); @@ -546,7 +546,8 @@ class CFGBuilder { CFGBlock *VisitDeclSubExpr(DeclStmt *DS); CFGBlock *VisitDefaultStmt(DefaultStmt *D); CFGBlock *VisitDoStmt(DoStmt *D); - CFGBlock *VisitExprWithCleanups(ExprWithCleanups *E, AddStmtChoice asc); + CFGBlock *VisitExprWithCleanups(ExprWithCleanups *E, + AddStmtChoice asc, bool ExternallyDestructed); CFGBlock *VisitForStmt(ForStmt *F); CFGBlock *VisitGotoStmt(GotoStmt *G); CFGBlock *VisitGCCAsmStmt(GCCAsmStmt *G, AddStmtChoice asc); @@ -585,7 +586,8 @@ class CFGBuilder { CFGBlock *VisitUnaryOperator(UnaryOperator *U, AddStmtChoice asc); CFGBlock *VisitWhileStmt(WhileStmt *W); - CFGBlock *Visit(Stmt *S, AddStmtChoice asc = AddStmtChoice::NotAlwaysAdd); + CFGBlock *Visit(Stmt *S, AddStmtChoice asc = AddStmtChoice::NotAlwaysAdd, + bool ExternallyDestructed = false); CFGBlock *VisitStmt(Stmt *S, AddStmtChoice asc); CFGBlock *VisitChildren(Stmt *S); CFGBlock *VisitNoRecurse(Expr *E, AddStmtChoice asc); @@ -654,15 +656,17 @@ class CFGBuilder { // Visitors to walk an AST and generate destructors of temporaries in // full expression. - CFGBlock *VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, + CFGBlock *VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed, TempDtorContext &Context); - CFGBlock *VisitChildrenForTemporaryDtors(Stmt *E, TempDtorContext &Context); + CFGBlock *VisitChildrenForTemporaryDtors(Stmt *E, bool ExternallyDestructed, + TempDtorContext &Context); CFGBlock *VisitBinaryOperatorForTemporaryDtors(BinaryOperator *E, + bool ExternallyDestructed, TempDtorContext &Context); CFGBlock *VisitCXXBindTemporaryExprForTemporaryDtors( - CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context); + CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context); CFGBlock *VisitConditionalOperatorForTemporaryDtors( - AbstractConditionalOperator *E, bool BindToTemporary, + AbstractConditionalOperator *E, bool ExternallyDestructed, TempDtorContext &Context); void InsertTempDtorDecisionBlock(const TempDtorContext &Context, CFGBlock *FalseSucc = nullptr); @@ -1573,7 +1577,7 @@ CFGBlock *CFGBuilder::addInitializer(CXXCtorInitializer *I) { // Generate destructors for temporaries in initialization expression. TempDtorContext Context; VisitForTemporaryDtors(cast(Init)->getSubExpr(), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/false, Context); } } @@ -2049,7 +2053,8 @@ CFGBuilder::prependAutomaticObjScopeEndWithTerminator( /// Visit - Walk the subtree of a statement and add extra /// blocks for ternary operators, &&, and ||. We also process "," and /// DeclStmts (which may contain nested control-flow). -CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) { +CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc, + bool ExternallyDestructed) { if (!S) { badCFG = true; return nullptr; @@ -2090,7 +2095,7 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) { return VisitChooseExpr(cast(S), asc); case Stmt::CompoundStmtClass: - return VisitCompoundStmt(cast(S)); + return VisitCompoundStmt(cast(S), ExternallyDestructed); case Stmt::ConditionalOperatorClass: return VisitConditionalOperator(cast(S), asc); @@ -2102,7 +2107,8 @@ CFGBlock *CFGBuilder::Visit(Stmt * S, AddStmtChoice asc) { return VisitCXXCatchStmt(cast(S)); case Stmt::ExprWithCleanupsClass: - return VisitExprWithCleanups(cast(S), asc); + return VisitExprWithCleanups(cast(S), + asc, ExternallyDestructed); case Stmt::CXXDefaultArgExprClass: case Stmt::CXXDefaultInitExprClass: @@ -2468,10 +2474,8 @@ CFGBlock *CFGBuilder::VisitBreakStmt(BreakStmt *B) { static bool CanThrow(Expr *E, ASTContext &Ctx) { QualType Ty = E->getType(); - if (Ty->isFunctionPointerType()) - Ty = Ty->getAs()->getPointeeType(); - else if (Ty->isBlockPointerType()) - Ty = Ty->getAs()->getPointeeType(); + if (Ty->isFunctionPointerType() || Ty->isBlockPointerType()) + Ty = Ty->getPointeeType(); const FunctionType *FT = Ty->getAs(); if (FT) { @@ -2597,7 +2601,7 @@ CFGBlock *CFGBuilder::VisitChooseExpr(ChooseExpr *C, return addStmt(C->getCond()); } -CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C) { +CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C, bool ExternallyDestructed) { LocalScope::const_iterator scopeBeginPos = ScopePos; addLocalScopeForStmt(C); @@ -2613,11 +2617,16 @@ CFGBlock *CFGBuilder::VisitCompoundStmt(CompoundStmt *C) { I != E; ++I ) { // If we hit a segment of code just containing ';' (NullStmts), we can // get a null block back. In such cases, just use the LastBlock - if (CFGBlock *newBlock = addStmt(*I)) + CFGBlock *newBlock = Visit(*I, AddStmtChoice::AlwaysAdd, + ExternallyDestructed); + + if (newBlock) LastBlock = newBlock; if (badCFG) return nullptr; + + ExternallyDestructed = false; } return LastBlock; @@ -2760,7 +2769,7 @@ CFGBlock *CFGBuilder::VisitDeclSubExpr(DeclStmt *DS) { // Generate destructors for temporaries in initialization expression. TempDtorContext Context; VisitForTemporaryDtors(cast(Init)->getSubExpr(), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/true, Context); } } @@ -2974,9 +2983,17 @@ CFGBlock *CFGBuilder::VisitReturnStmt(Stmt *S) { if (!Block->hasNoReturnElement()) addSuccessor(Block, &cfg->getExit()); - // Add the return statement to the block. This may create new blocks if R - // contains control-flow (short-circuit operations). - return VisitStmt(S, AddStmtChoice::AlwaysAdd); + // Add the return statement to the block. + appendStmt(Block, S); + + // Visit children + if (ReturnStmt *RS = dyn_cast(S)) { + if (Expr *O = RS->getRetValue()) + return Visit(O, AddStmtChoice::AlwaysAdd, /*ExternallyDestructed=*/true); + return Block; + } else { // co_return + return VisitChildren(S); + } } CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) { @@ -3008,7 +3025,7 @@ CFGBlock *CFGBuilder::VisitSEHExceptStmt(SEHExceptStmt *ES) { } CFGBlock *CFGBuilder::VisitSEHFinallyStmt(SEHFinallyStmt *FS) { - return VisitCompoundStmt(FS->getBlock()); + return VisitCompoundStmt(FS->getBlock(), /*ExternallyDestructed=*/false); } CFGBlock *CFGBuilder::VisitSEHLeaveStmt(SEHLeaveStmt *LS) { @@ -3892,7 +3909,7 @@ CFGBlock *CFGBuilder::VisitStmtExpr(StmtExpr *SE, AddStmtChoice asc) { autoCreateBlock(); appendStmt(Block, SE); } - return VisitCompoundStmt(SE->getSubStmt()); + return VisitCompoundStmt(SE->getSubStmt(), /*ExternallyDestructed=*/true); } CFGBlock *CFGBuilder::VisitSwitchStmt(SwitchStmt *Terminator) { @@ -4357,12 +4374,12 @@ CFGBlock *CFGBuilder::VisitCXXForRangeStmt(CXXForRangeStmt *S) { } CFGBlock *CFGBuilder::VisitExprWithCleanups(ExprWithCleanups *E, - AddStmtChoice asc) { + AddStmtChoice asc, bool ExternallyDestructed) { if (BuildOpts.AddTemporaryDtors) { // If adding implicit destructors visit the full expression for adding // destructors of temporaries. TempDtorContext Context; - VisitForTemporaryDtors(E->getSubExpr(), false, Context); + VisitForTemporaryDtors(E->getSubExpr(), ExternallyDestructed, Context); // Full expression has to be added as CFGStmt so it will be sequenced // before destructors of it's temporaries. @@ -4498,7 +4515,7 @@ CFGBlock *CFGBuilder::VisitIndirectGotoStmt(IndirectGotoStmt *I) { return addStmt(I->getTarget()); } -CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, +CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool ExternallyDestructed, TempDtorContext &Context) { assert(BuildOpts.AddImplicitDtors && BuildOpts.AddTemporaryDtors); @@ -4509,28 +4526,32 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, } switch (E->getStmtClass()) { default: - return VisitChildrenForTemporaryDtors(E, Context); + return VisitChildrenForTemporaryDtors(E, false, Context); + + case Stmt::InitListExprClass: + return VisitChildrenForTemporaryDtors(E, ExternallyDestructed, Context); case Stmt::BinaryOperatorClass: return VisitBinaryOperatorForTemporaryDtors(cast(E), + ExternallyDestructed, Context); case Stmt::CXXBindTemporaryExprClass: return VisitCXXBindTemporaryExprForTemporaryDtors( - cast(E), BindToTemporary, Context); + cast(E), ExternallyDestructed, Context); case Stmt::BinaryConditionalOperatorClass: case Stmt::ConditionalOperatorClass: return VisitConditionalOperatorForTemporaryDtors( - cast(E), BindToTemporary, Context); + cast(E), ExternallyDestructed, Context); case Stmt::ImplicitCastExprClass: - // For implicit cast we want BindToTemporary to be passed further. + // For implicit cast we want ExternallyDestructed to be passed further. E = cast(E)->getSubExpr(); goto tryAgain; case Stmt::CXXFunctionalCastExprClass: - // For functional cast we want BindToTemporary to be passed further. + // For functional cast we want ExternallyDestructed to be passed further. E = cast(E)->getSubExpr(); goto tryAgain; @@ -4544,7 +4565,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, case Stmt::MaterializeTemporaryExprClass: { const MaterializeTemporaryExpr* MTE = cast(E); - BindToTemporary = (MTE->getStorageDuration() != SD_FullExpression); + ExternallyDestructed = (MTE->getStorageDuration() != SD_FullExpression); SmallVector CommaLHSs; SmallVector Adjustments; // Find the expression whose lifetime needs to be extended. @@ -4555,7 +4576,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, // Visit the skipped comma operator left-hand sides for other temporaries. for (const Expr *CommaLHS : CommaLHSs) { VisitForTemporaryDtors(const_cast(CommaLHS), - /*BindToTemporary=*/false, Context); + /*ExternallyDestructed=*/false, Context); } goto tryAgain; } @@ -4573,13 +4594,18 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, for (Expr *Init : LE->capture_inits()) { if (Init) { if (CFGBlock *R = VisitForTemporaryDtors( - Init, /*BindToTemporary=*/false, Context)) + Init, /*ExternallyDestructed=*/true, Context)) B = R; } } return B; } + case Stmt::StmtExprClass: + // Don't recurse into statement expressions; any cleanups inside them + // will be wrapped in their own ExprWithCleanups. + return Block; + case Stmt::CXXDefaultArgExprClass: E = cast(E)->getExpr(); goto tryAgain; @@ -4591,6 +4617,7 @@ CFGBlock *CFGBuilder::VisitForTemporaryDtors(Stmt *E, bool BindToTemporary, } CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E, + bool ExternallyDestructed, TempDtorContext &Context) { if (isa(E)) { // Do not visit the children of lambdas; they have their own CFGs. @@ -4604,14 +4631,22 @@ CFGBlock *CFGBuilder::VisitChildrenForTemporaryDtors(Stmt *E, CFGBlock *B = Block; for (Stmt *Child : E->children()) if (Child) - if (CFGBlock *R = VisitForTemporaryDtors(Child, false, Context)) + if (CFGBlock *R = VisitForTemporaryDtors(Child, ExternallyDestructed, Context)) B = R; return B; } CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors( - BinaryOperator *E, TempDtorContext &Context) { + BinaryOperator *E, bool ExternallyDestructed, TempDtorContext &Context) { + if (E->isCommaOp()) { + // For comma operator LHS expression is visited + // before RHS expression. For destructors visit them in reverse order. + CFGBlock *RHSBlock = VisitForTemporaryDtors(E->getRHS(), ExternallyDestructed, Context); + CFGBlock *LHSBlock = VisitForTemporaryDtors(E->getLHS(), false, Context); + return LHSBlock ? LHSBlock : RHSBlock; + } + if (E->isLogicalOp()) { VisitForTemporaryDtors(E->getLHS(), false, Context); TryResult RHSExecuted = tryEvaluateBool(E->getLHS()); @@ -4646,10 +4681,11 @@ CFGBlock *CFGBuilder::VisitBinaryOperatorForTemporaryDtors( } CFGBlock *CFGBuilder::VisitCXXBindTemporaryExprForTemporaryDtors( - CXXBindTemporaryExpr *E, bool BindToTemporary, TempDtorContext &Context) { + CXXBindTemporaryExpr *E, bool ExternallyDestructed, TempDtorContext &Context) { // First add destructors for temporaries in subexpression. - CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), false, Context); - if (!BindToTemporary) { + // Because VisitCXXBindTemporaryExpr calls setDestructed: + CFGBlock *B = VisitForTemporaryDtors(E->getSubExpr(), true, Context); + if (!ExternallyDestructed) { // If lifetime of temporary is not prolonged (by assigning to constant // reference) add destructor for it. @@ -4697,7 +4733,7 @@ void CFGBuilder::InsertTempDtorDecisionBlock(const TempDtorContext &Context, } CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors( - AbstractConditionalOperator *E, bool BindToTemporary, + AbstractConditionalOperator *E, bool ExternallyDestructed, TempDtorContext &Context) { VisitForTemporaryDtors(E->getCond(), false, Context); CFGBlock *ConditionBlock = Block; @@ -4708,14 +4744,14 @@ CFGBlock *CFGBuilder::VisitConditionalOperatorForTemporaryDtors( TempDtorContext TrueContext( bothKnownTrue(Context.KnownExecuted, ConditionVal)); - VisitForTemporaryDtors(E->getTrueExpr(), BindToTemporary, TrueContext); + VisitForTemporaryDtors(E->getTrueExpr(), ExternallyDestructed, TrueContext); CFGBlock *TrueBlock = Block; Block = ConditionBlock; Succ = ConditionSucc; TempDtorContext FalseContext( bothKnownTrue(Context.KnownExecuted, NegatedVal)); - VisitForTemporaryDtors(E->getFalseExpr(), BindToTemporary, FalseContext); + VisitForTemporaryDtors(E->getFalseExpr(), ExternallyDestructed, FalseContext); if (TrueContext.TerminatorExpr && FalseContext.TerminatorExpr) { InsertTempDtorDecisionBlock(FalseContext, TrueBlock); @@ -4830,9 +4866,13 @@ CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const { while (const ArrayType *arrayType = astContext.getAsArrayType(ty)) { ty = arrayType->getElementType(); } - const RecordType *recordType = ty->getAs(); - const CXXRecordDecl *classDecl = - cast(recordType->getDecl()); + + // The situation when the type of the lifetime-extending reference + // does not correspond to the type of the object is supposed + // to be handled by now. In particular, 'ty' is now the unwrapped + // record type. + const CXXRecordDecl *classDecl = ty->getAsCXXRecordDecl(); + assert(classDecl); return classDecl->getDestructor(); } case CFGElement::DeleteDtor: { @@ -4857,12 +4897,6 @@ CFGImplicitDtor::getDestructorDecl(ASTContext &astContext) const { llvm_unreachable("getKind() returned bogus value"); } -bool CFGImplicitDtor::isNoReturn(ASTContext &astContext) const { - if (const CXXDestructorDecl *DD = getDestructorDecl(astContext)) - return DD->isNoReturn(); - return false; -} - //===----------------------------------------------------------------------===// // CFGBlock operations. //===----------------------------------------------------------------------===// @@ -4928,6 +4962,8 @@ class StmtPrinterHelper : public PrinterHelper { public: StmtPrinterHelper(const CFG* cfg, const LangOptions &LO) : LangOpts(LO) { + if (!cfg) + return; for (CFG::const_iterator I = cfg->begin(), E = cfg->end(); I != E; ++I ) { unsigned j = 1; for (CFGBlock::const_iterator BI = (*I)->begin(), BEnd = (*I)->end() ; @@ -5249,10 +5285,22 @@ static void print_construction_context(raw_ostream &OS, } } +static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, + const CFGElement &E); + +void CFGElement::dumpToStream(llvm::raw_ostream &OS) const { + StmtPrinterHelper Helper(nullptr, {}); + print_elem(OS, Helper, *this); +} + static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, const CFGElement &E) { - if (Optional CS = E.getAs()) { - const Stmt *S = CS->getStmt(); + switch (E.getKind()) { + case CFGElement::Kind::Statement: + case CFGElement::Kind::CXXRecordTypedCall: + case CFGElement::Kind::Constructor: { + CFGStmt CS = E.castAs(); + const Stmt *S = CS.getStmt(); assert(S != nullptr && "Expecting non-null Stmt"); // special printing for statement-expressions. @@ -5304,70 +5352,96 @@ static void print_elem(raw_ostream &OS, StmtPrinterHelper &Helper, // Expressions need a newline. if (isa(S)) OS << '\n'; - } else if (Optional IE = E.getAs()) { - print_initializer(OS, Helper, IE->getInitializer()); + + break; + } + + case CFGElement::Kind::Initializer: + print_initializer(OS, Helper, E.castAs().getInitializer()); OS << '\n'; - } else if (Optional DE = - E.getAs()) { - const VarDecl *VD = DE->getVarDecl(); + break; + + case CFGElement::Kind::AutomaticObjectDtor: { + CFGAutomaticObjDtor DE = E.castAs(); + const VarDecl *VD = DE.getVarDecl(); Helper.handleDecl(VD, OS); - ASTContext &ACtx = VD->getASTContext(); QualType T = VD->getType(); if (T->isReferenceType()) T = getReferenceInitTemporaryType(VD->getInit(), nullptr); - if (const ArrayType *AT = ACtx.getAsArrayType(T)) - T = ACtx.getBaseElementType(AT); - OS << ".~" << T->getAsCXXRecordDecl()->getName().str() << "()"; - OS << " (Implicit destructor)\n"; - } else if (Optional DE = E.getAs()) { - const VarDecl *VD = DE->getVarDecl(); - Helper.handleDecl(VD, OS); + OS << ".~"; + T.getUnqualifiedType().print(OS, PrintingPolicy(Helper.getLangOpts())); + OS << "() (Implicit destructor)\n"; + break; + } + case CFGElement::Kind::LifetimeEnds: + Helper.handleDecl(E.castAs().getVarDecl(), OS); OS << " (Lifetime ends)\n"; - } else if (Optional LE = E.getAs()) { - const Stmt *LoopStmt = LE->getLoopStmt(); - OS << LoopStmt->getStmtClassName() << " (LoopExit)\n"; - } else if (Optional SB = E.getAs()) { + break; + + case CFGElement::Kind::LoopExit: + OS << E.castAs().getLoopStmt()->getStmtClassName() << " (LoopExit)\n"; + break; + + case CFGElement::Kind::ScopeBegin: OS << "CFGScopeBegin("; - if (const VarDecl *VD = SB->getVarDecl()) + if (const VarDecl *VD = E.castAs().getVarDecl()) OS << VD->getQualifiedNameAsString(); OS << ")\n"; - } else if (Optional SE = E.getAs()) { + break; + + case CFGElement::Kind::ScopeEnd: OS << "CFGScopeEnd("; - if (const VarDecl *VD = SE->getVarDecl()) + if (const VarDecl *VD = E.castAs().getVarDecl()) OS << VD->getQualifiedNameAsString(); OS << ")\n"; - } else if (Optional NE = E.getAs()) { + break; + + case CFGElement::Kind::NewAllocator: OS << "CFGNewAllocator("; - if (const CXXNewExpr *AllocExpr = NE->getAllocatorExpr()) + if (const CXXNewExpr *AllocExpr = E.castAs().getAllocatorExpr()) AllocExpr->getType().print(OS, PrintingPolicy(Helper.getLangOpts())); OS << ")\n"; - } else if (Optional DE = E.getAs()) { - const CXXRecordDecl *RD = DE->getCXXRecordDecl(); + break; + + case CFGElement::Kind::DeleteDtor: { + CFGDeleteDtor DE = E.castAs(); + const CXXRecordDecl *RD = DE.getCXXRecordDecl(); if (!RD) return; CXXDeleteExpr *DelExpr = - const_cast(DE->getDeleteExpr()); + const_cast(DE.getDeleteExpr()); Helper.handledStmt(cast(DelExpr->getArgument()), OS); OS << "->~" << RD->getName().str() << "()"; OS << " (Implicit destructor)\n"; - } else if (Optional BE = E.getAs()) { - const CXXBaseSpecifier *BS = BE->getBaseSpecifier(); + break; + } + + case CFGElement::Kind::BaseDtor: { + const CXXBaseSpecifier *BS = E.castAs().getBaseSpecifier(); OS << "~" << BS->getType()->getAsCXXRecordDecl()->getName() << "()"; OS << " (Base object destructor)\n"; - } else if (Optional ME = E.getAs()) { - const FieldDecl *FD = ME->getFieldDecl(); + break; + } + + case CFGElement::Kind::MemberDtor: { + const FieldDecl *FD = E.castAs().getFieldDecl(); const Type *T = FD->getType()->getBaseElementTypeUnsafe(); OS << "this->" << FD->getName(); OS << ".~" << T->getAsCXXRecordDecl()->getName() << "()"; OS << " (Member object destructor)\n"; - } else if (Optional TE = E.getAs()) { - const CXXBindTemporaryExpr *BT = TE->getBindTemporaryExpr(); + break; + } + + case CFGElement::Kind::TemporaryDtor: { + const CXXBindTemporaryExpr *BT = E.castAs().getBindTemporaryExpr(); OS << "~"; BT->getType().print(OS, PrintingPolicy(Helper.getLangOpts())); OS << "() (Temporary object destructor)\n"; + break; + } } } @@ -5578,6 +5652,10 @@ void CFG::print(raw_ostream &OS, const LangOptions &LO, bool ShowColors) const { OS.flush(); } +size_t CFGBlock::getIndexInCFG() const { + return llvm::find(*getParent(), this) - getParent()->begin(); +} + /// dump - A simply pretty printer of a CFGBlock that outputs to stderr. void CFGBlock::dump(const CFG* cfg, const LangOptions &LO, bool ShowColors) const { @@ -5615,6 +5693,95 @@ void CFGBlock::printTerminatorJson(raw_ostream &Out, const LangOptions &LO, Out << JsonFormat(TempOut.str(), AddQuotes); } +// Returns true if by simply looking at the block, we can be sure that it +// results in a sink during analysis. This is useful to know when the analysis +// was interrupted, and we try to figure out if it would sink eventually. +// There may be many more reasons why a sink would appear during analysis +// (eg. checkers may generate sinks arbitrarily), but here we only consider +// sinks that would be obvious by looking at the CFG. +static bool isImmediateSinkBlock(const CFGBlock *Blk) { + if (Blk->hasNoReturnElement()) + return true; + + // FIXME: Throw-expressions are currently generating sinks during analysis: + // they're not supported yet, and also often used for actually terminating + // the program. So we should treat them as sinks in this analysis as well, + // at least for now, but once we have better support for exceptions, + // we'd need to carefully handle the case when the throw is being + // immediately caught. + if (std::any_of(Blk->begin(), Blk->end(), [](const CFGElement &Elm) { + if (Optional StmtElm = Elm.getAs()) + if (isa(StmtElm->getStmt())) + return true; + return false; + })) + return true; + + return false; +} + +bool CFGBlock::isInevitablySinking() const { + const CFG &Cfg = *getParent(); + + const CFGBlock *StartBlk = this; + if (isImmediateSinkBlock(StartBlk)) + return true; + + llvm::SmallVector DFSWorkList; + llvm::SmallPtrSet Visited; + + DFSWorkList.push_back(StartBlk); + while (!DFSWorkList.empty()) { + const CFGBlock *Blk = DFSWorkList.back(); + DFSWorkList.pop_back(); + Visited.insert(Blk); + + // If at least one path reaches the CFG exit, it means that control is + // returned to the caller. For now, say that we are not sure what + // happens next. If necessary, this can be improved to analyze + // the parent StackFrameContext's call site in a similar manner. + if (Blk == &Cfg.getExit()) + return false; + + for (const auto &Succ : Blk->succs()) { + if (const CFGBlock *SuccBlk = Succ.getReachableBlock()) { + if (!isImmediateSinkBlock(SuccBlk) && !Visited.count(SuccBlk)) { + // If the block has reachable child blocks that aren't no-return, + // add them to the worklist. + DFSWorkList.push_back(SuccBlk); + } + } + } + } + + // Nothing reached the exit. It can only mean one thing: there's no return. + return true; +} + +const Expr *CFGBlock::getLastCondition() const { + // If the terminator is a temporary dtor or a virtual base, etc, we can't + // retrieve a meaningful condition, bail out. + if (Terminator.getKind() != CFGTerminator::StmtBranch) + return nullptr; + + // Also, if this method was called on a block that doesn't have 2 successors, + // this block doesn't have retrievable condition. + if (succ_size() < 2) + return nullptr; + + auto StmtElem = rbegin()->getAs(); + if (!StmtElem) + return nullptr; + + const Stmt *Cond = StmtElem->getStmt(); + if (isa(Cond)) + return nullptr; + + // Only ObjCForCollectionStmt is known not to be a non-Expr terminator, hence + // the cast<>. + return cast(Cond)->IgnoreParens(); +} + Stmt *CFGBlock::getTerminatorCondition(bool StripParens) { Stmt *Terminator = getTerminatorStmt(); if (!Terminator) diff --git a/clang/lib/Analysis/CMakeLists.txt b/clang/lib/Analysis/CMakeLists.txt index 92717143467d5..c1d0d6ed62c50 100644 --- a/clang/lib/Analysis/CMakeLists.txt +++ b/clang/lib/Analysis/CMakeLists.txt @@ -18,6 +18,7 @@ add_clang_library(clangAnalysis ExprMutationAnalyzer.cpp LiveVariables.cpp ObjCNoReturn.cpp + PathDiagnostic.cpp PostOrderCFGView.cpp ProgramPoint.cpp ReachableCode.cpp diff --git a/clang/lib/Analysis/CallGraph.cpp b/clang/lib/Analysis/CallGraph.cpp index 7eda80ea0505e..b6542cad120e5 100644 --- a/clang/lib/Analysis/CallGraph.cpp +++ b/clang/lib/Analysis/CallGraph.cpp @@ -79,6 +79,34 @@ class CGBuilder : public StmtVisitor { VisitChildren(CE); } + void VisitLambdaExpr(LambdaExpr *LE) { + if (CXXMethodDecl *MD = LE->getCallOperator()) + G->VisitFunctionDecl(MD); + } + + void VisitCXXNewExpr(CXXNewExpr *E) { + if (FunctionDecl *FD = E->getOperatorNew()) + addCalledDecl(FD); + VisitChildren(E); + } + + void VisitCXXConstructExpr(CXXConstructExpr *E) { + CXXConstructorDecl *Ctor = E->getConstructor(); + if (FunctionDecl *Def = Ctor->getDefinition()) + addCalledDecl(Def); + VisitChildren(E); + } + + // Include the evaluation of the default argument. + void VisitCXXDefaultArgExpr(CXXDefaultArgExpr *E) { + Visit(E->getExpr()); + } + + // Include the evaluation of the default initializers in a class. + void VisitCXXDefaultInitExpr(CXXDefaultInitExpr *E) { + Visit(E->getExpr()); + } + // Adds may-call edges for the ObjC message sends. void VisitObjCMessageExpr(ObjCMessageExpr *ME) { if (ObjCInterfaceDecl *IDecl = ME->getReceiverInterface()) { @@ -143,13 +171,20 @@ bool CallGraph::includeInGraph(const Decl *D) { void CallGraph::addNodeForDecl(Decl* D, bool IsGlobal) { assert(D); - // Allocate a new node, mark it as root, and process it's calls. + // Allocate a new node, mark it as root, and process its calls. CallGraphNode *Node = getOrInsertNode(D); // Process all the calls by this function as well. CGBuilder builder(this, Node); if (Stmt *Body = D->getBody()) builder.Visit(Body); + + // Include C++ constructor member initializers. + if (auto constructor = dyn_cast(D)) { + for (CXXCtorInitializer *init : constructor->inits()) { + builder.Visit(init->getInit()); + } + } } CallGraphNode *CallGraph::getNode(const Decl *F) const { diff --git a/clang/lib/Analysis/CocoaConventions.cpp b/clang/lib/Analysis/CocoaConventions.cpp index b2ef426dead28..571d72e1a8416 100644 --- a/clang/lib/Analysis/CocoaConventions.cpp +++ b/clang/lib/Analysis/CocoaConventions.cpp @@ -38,8 +38,8 @@ bool cocoa::isRefType(QualType RetTy, StringRef Prefix, return false; // Is the type void*? - const PointerType* PT = RetTy->getAs(); - if (!(PT->getPointeeType().getUnqualifiedType()->isVoidType())) + const PointerType* PT = RetTy->castAs(); + if (!PT || !PT->getPointeeType().getUnqualifiedType()->isVoidType()) return false; // Does the name start with the prefix? diff --git a/clang/lib/Analysis/Dominators.cpp b/clang/lib/Analysis/Dominators.cpp index b872869f8cf81..f7ad68673d0f2 100644 --- a/clang/lib/Analysis/Dominators.cpp +++ b/clang/lib/Analysis/Dominators.cpp @@ -8,6 +8,12 @@ #include "clang/Analysis/Analyses/Dominators.h" -using namespace clang; +namespace clang { -void DominatorTree::anchor() {} +template <> +void CFGDominatorTreeImpl::anchor() {} + +template <> +void CFGDominatorTreeImpl::anchor() {} + +} // end of namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp b/clang/lib/Analysis/PathDiagnostic.cpp similarity index 81% rename from clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp rename to clang/lib/Analysis/PathDiagnostic.cpp index 1f642064827d5..53235ba076994 100644 --- a/clang/lib/StaticAnalyzer/Core/PathDiagnostic.cpp +++ b/clang/lib/Analysis/PathDiagnostic.cpp @@ -10,7 +10,7 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/DeclCXX.h" @@ -29,9 +29,6 @@ #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SVals.h" #include "llvm/ADT/ArrayRef.h" #include "llvm/ADT/FoldingSet.h" #include "llvm/ADT/None.h" @@ -53,17 +50,6 @@ using namespace clang; using namespace ento; -bool PathDiagnosticMacroPiece::containsEvent() const { - for (const auto &P : subPieces) { - if (isa(*P)) - return true; - if (const auto *MP = dyn_cast(P.get())) - if (MP->containsEvent()) - return true; - } - return false; -} - static StringRef StripTrailingDots(StringRef s) { for (StringRef::size_type i = s.size(); i != 0; --i) if (s[i - 1] != '.') @@ -131,11 +117,11 @@ void PathPieces::flattenTo(PathPieces &Primary, PathPieces &Current, PathDiagnostic::~PathDiagnostic() = default; PathDiagnostic::PathDiagnostic( - StringRef CheckName, const Decl *declWithIssue, StringRef bugtype, + StringRef CheckerName, const Decl *declWithIssue, StringRef bugtype, StringRef verboseDesc, StringRef shortDesc, StringRef category, PathDiagnosticLocation LocationToUnique, const Decl *DeclToUnique, std::unique_ptr ExecutedLines) - : CheckName(CheckName), DeclWithIssue(declWithIssue), + : CheckerName(CheckerName), DeclWithIssue(declWithIssue), BugType(StripTrailingDots(bugtype)), VerboseDesc(StripTrailingDots(verboseDesc)), ShortDesc(StripTrailingDots(shortDesc)), @@ -143,69 +129,6 @@ PathDiagnostic::PathDiagnostic( UniqueingDecl(DeclToUnique), ExecutedLines(std::move(ExecutedLines)), path(pathImpl) {} -static PathDiagnosticCallPiece * -getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, - const SourceManager &SMgr) { - SourceLocation CallLoc = CP->callEnter.asLocation(); - - // If the call is within a macro, don't do anything (for now). - if (CallLoc.isMacroID()) - return nullptr; - - assert(AnalysisManager::isInCodeFile(CallLoc, SMgr) && - "The call piece should not be in a header file."); - - // Check if CP represents a path through a function outside of the main file. - if (!AnalysisManager::isInCodeFile(CP->callEnterWithin.asLocation(), SMgr)) - return CP; - - const PathPieces &Path = CP->path; - if (Path.empty()) - return nullptr; - - // Check if the last piece in the callee path is a call to a function outside - // of the main file. - if (auto *CPInner = dyn_cast(Path.back().get())) - return getFirstStackedCallToHeaderFile(CPInner, SMgr); - - // Otherwise, the last piece is in the main file. - return nullptr; -} - -void PathDiagnostic::resetDiagnosticLocationToMainFile() { - if (path.empty()) - return; - - PathDiagnosticPiece *LastP = path.back().get(); - assert(LastP); - const SourceManager &SMgr = LastP->getLocation().getManager(); - - // We only need to check if the report ends inside headers, if the last piece - // is a call piece. - if (auto *CP = dyn_cast(LastP)) { - CP = getFirstStackedCallToHeaderFile(CP, SMgr); - if (CP) { - // Mark the piece. - CP->setAsLastInMainSourceFile(); - - // Update the path diagnostic message. - const auto *ND = dyn_cast(CP->getCallee()); - if (ND) { - SmallString<200> buf; - llvm::raw_svector_ostream os(buf); - os << " (within a call to '" << ND->getDeclName() << "')"; - appendToDesc(os.str()); - } - - // Reset the report containing declaration and location. - DeclWithIssue = CP->getCaller(); - Loc = CP->getLocation(); - - return; - } - } -} - void PathDiagnosticConsumer::anchor() {} PathDiagnosticConsumer::~PathDiagnosticConsumer() { @@ -536,12 +459,12 @@ PathDiagnosticConsumer::FilesMade::getFiles(const PathDiagnostic &PD) { // PathDiagnosticLocation methods. //===----------------------------------------------------------------------===// -static SourceLocation getValidSourceLocation(const Stmt* S, - LocationOrAnalysisDeclContext LAC, - bool UseEnd = false) { - SourceLocation L = UseEnd ? S->getEndLoc() : S->getBeginLoc(); - assert(!LAC.isNull() && "A valid LocationContext or AnalysisDeclContext should " - "be passed to PathDiagnosticLocation upon creation."); +SourceLocation PathDiagnosticLocation::getValidSourceLocation( + const Stmt *S, LocationOrAnalysisDeclContext LAC, bool UseEndOfStatement) { + SourceLocation L = UseEndOfStatement ? S->getEndLoc() : S->getBeginLoc(); + assert(!LAC.isNull() && + "A valid LocationContext or AnalysisDeclContext should be passed to " + "PathDiagnosticLocation upon creation."); // S might be a temporary statement that does not have a location in the // source code, so find an enclosing statement and use its location. @@ -571,7 +494,7 @@ static SourceLocation getValidSourceLocation(const Stmt* S, break; } - L = UseEnd ? Parent->getEndLoc() : Parent->getBeginLoc(); + L = UseEndOfStatement ? Parent->getEndLoc() : Parent->getBeginLoc(); } while (!L.isValid()); } @@ -772,14 +695,21 @@ PathDiagnosticLocation::create(const ProgramPoint& P, return PathDiagnosticLocation( CEB->getLocationContext()->getDecl()->getSourceRange().getEnd(), SMng); } else if (Optional BE = P.getAs()) { - CFGElement BlockFront = BE->getBlock()->front(); - if (auto StmtElt = BlockFront.getAs()) { - return PathDiagnosticLocation(StmtElt->getStmt()->getBeginLoc(), SMng); - } else if (auto NewAllocElt = BlockFront.getAs()) { - return PathDiagnosticLocation( - NewAllocElt->getAllocatorExpr()->getBeginLoc(), SMng); + if (Optional BlockFront = BE->getFirstElement()) { + if (auto StmtElt = BlockFront->getAs()) { + return PathDiagnosticLocation(StmtElt->getStmt()->getBeginLoc(), SMng); + } else if (auto NewAllocElt = BlockFront->getAs()) { + return PathDiagnosticLocation( + NewAllocElt->getAllocatorExpr()->getBeginLoc(), SMng); + } + llvm_unreachable("Unexpected CFG element at front of block"); } - llvm_unreachable("Unexpected CFG element at front of block"); + + return PathDiagnosticLocation( + BE->getBlock()->getTerminatorStmt()->getBeginLoc(), SMng); + } else if (Optional FE = P.getAs()) { + return PathDiagnosticLocation(FE->getStmt(), SMng, + FE->getLocationContext()); } else { llvm_unreachable("Unexpected ProgramPoint"); } @@ -787,116 +717,6 @@ PathDiagnosticLocation::create(const ProgramPoint& P, return PathDiagnosticLocation(S, SMng, P.getLocationContext()); } -static const LocationContext * -findTopAutosynthesizedParentContext(const LocationContext *LC) { - assert(LC->getAnalysisDeclContext()->isBodyAutosynthesized()); - const LocationContext *ParentLC = LC->getParent(); - assert(ParentLC && "We don't start analysis from autosynthesized code"); - while (ParentLC->getAnalysisDeclContext()->isBodyAutosynthesized()) { - LC = ParentLC; - ParentLC = LC->getParent(); - assert(ParentLC && "We don't start analysis from autosynthesized code"); - } - return LC; -} - -const Stmt *PathDiagnosticLocation::getStmt(const ExplodedNode *N) { - // We cannot place diagnostics on autosynthesized code. - // Put them onto the call site through which we jumped into autosynthesized - // code for the first time. - const LocationContext *LC = N->getLocationContext(); - if (LC->getAnalysisDeclContext()->isBodyAutosynthesized()) { - // It must be a stack frame because we only autosynthesize functions. - return cast(findTopAutosynthesizedParentContext(LC)) - ->getCallSite(); - } - // Otherwise, see if the node's program point directly points to a statement. - ProgramPoint P = N->getLocation(); - if (auto SP = P.getAs()) - return SP->getStmt(); - if (auto BE = P.getAs()) - return BE->getSrc()->getTerminatorStmt(); - if (auto CE = P.getAs()) - return CE->getCallExpr(); - if (auto CEE = P.getAs()) - return CEE->getCalleeContext()->getCallSite(); - if (auto PIPP = P.getAs()) - return PIPP->getInitializer()->getInit(); - if (auto CEB = P.getAs()) - return CEB->getReturnStmt(); - if (auto FEP = P.getAs()) - return FEP->getStmt(); - - return nullptr; -} - -const Stmt *PathDiagnosticLocation::getNextStmt(const ExplodedNode *N) { - for (N = N->getFirstSucc(); N; N = N->getFirstSucc()) { - if (const Stmt *S = getStmt(N)) { - // Check if the statement is '?' or '&&'/'||'. These are "merges", - // not actual statement points. - switch (S->getStmtClass()) { - case Stmt::ChooseExprClass: - case Stmt::BinaryConditionalOperatorClass: - case Stmt::ConditionalOperatorClass: - continue; - case Stmt::BinaryOperatorClass: { - BinaryOperatorKind Op = cast(S)->getOpcode(); - if (Op == BO_LAnd || Op == BO_LOr) - continue; - break; - } - default: - break; - } - // We found the statement, so return it. - return S; - } - } - - return nullptr; -} - -PathDiagnosticLocation - PathDiagnosticLocation::createEndOfPath(const ExplodedNode *N, - const SourceManager &SM) { - assert(N && "Cannot create a location with a null node."); - const Stmt *S = getStmt(N); - const LocationContext *LC = N->getLocationContext(); - - if (!S) { - // If this is an implicit call, return the implicit call point location. - if (Optional PIE = N->getLocationAs()) - return PathDiagnosticLocation(PIE->getLocation(), SM); - if (auto FE = N->getLocationAs()) { - if (const ReturnStmt *RS = FE->getStmt()) - return PathDiagnosticLocation::createBegin(RS, SM, LC); - } - S = getNextStmt(N); - } - - if (S) { - ProgramPoint P = N->getLocation(); - - // For member expressions, return the location of the '.' or '->'. - if (const auto *ME = dyn_cast(S)) - return PathDiagnosticLocation::createMemberLoc(ME, SM); - - // For binary operators, return the location of the operator. - if (const auto *B = dyn_cast(S)) - return PathDiagnosticLocation::createOperatorLoc(B, SM); - - if (P.getAs()) - return PathDiagnosticLocation::createEnd(S, SM, LC); - - if (S->getBeginLoc().isValid()) - return PathDiagnosticLocation(S, SM, LC); - return PathDiagnosticLocation(getValidSourceLocation(S, LC), SM); - } - - return createDeclEnd(N->getLocationContext(), SM); -} - PathDiagnosticLocation PathDiagnosticLocation::createSingleLocation( const PathDiagnosticLocation &PDL) { FullSourceLoc L = PDL.asLocation(); @@ -1310,70 +1130,6 @@ void PathDiagnostic::FullProfile(llvm::FoldingSetNodeID &ID) const { ID.AddString(*I); } -StackHintGenerator::~StackHintGenerator() = default; - -std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ - if (!N) - return getMessageForSymbolNotFound(); - - ProgramPoint P = N->getLocation(); - CallExitEnd CExit = P.castAs(); - - // FIXME: Use CallEvent to abstract this over all calls. - const Stmt *CallSite = CExit.getCalleeContext()->getCallSite(); - const auto *CE = dyn_cast_or_null(CallSite); - if (!CE) - return {}; - - // Check if one of the parameters are set to the interesting symbol. - unsigned ArgIndex = 0; - for (CallExpr::const_arg_iterator I = CE->arg_begin(), - E = CE->arg_end(); I != E; ++I, ++ArgIndex){ - SVal SV = N->getSVal(*I); - - // Check if the variable corresponding to the symbol is passed by value. - SymbolRef AS = SV.getAsLocSymbol(); - if (AS == Sym) { - return getMessageForArg(*I, ArgIndex); - } - - // Check if the parameter is a pointer to the symbol. - if (Optional Reg = SV.getAs()) { - // Do not attempt to dereference void*. - if ((*I)->getType()->isVoidPointerType()) - continue; - SVal PSV = N->getState()->getSVal(Reg->getRegion()); - SymbolRef AS = PSV.getAsLocSymbol(); - if (AS == Sym) { - return getMessageForArg(*I, ArgIndex); - } - } - } - - // Check if we are returning the interesting symbol. - SVal SV = N->getSVal(CE); - SymbolRef RetSym = SV.getAsLocSymbol(); - if (RetSym == Sym) { - return getMessageForReturn(CE); - } - - return getMessageForSymbolNotFound(); -} - -std::string StackHintGeneratorForSymbol::getMessageForArg(const Expr *ArgE, - unsigned ArgIndex) { - // Printed parameters start at 1, not 0. - ++ArgIndex; - - SmallString<200> buf; - llvm::raw_svector_ostream os(buf); - - os << Msg << " via " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) - << " parameter"; - - return os.str(); -} - LLVM_DUMP_METHOD void PathPieces::dump() const { unsigned index = 0; for (PathPieces::const_iterator I = begin(), E = end(); I != E; ++I) { diff --git a/clang/lib/Analysis/ProgramPoint.cpp b/clang/lib/Analysis/ProgramPoint.cpp index 45f165677f99d..0783fbed53155 100644 --- a/clang/lib/Analysis/ProgramPoint.cpp +++ b/clang/lib/Analysis/ProgramPoint.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "clang/Analysis/ProgramPoint.h" +#include "clang/Basic/JsonSupport.h" using namespace clang; @@ -46,18 +47,6 @@ LLVM_DUMP_METHOD void ProgramPoint::dump() const { return printJson(llvm::errs()); } -static void printLocJson(raw_ostream &Out, SourceLocation Loc, - const SourceManager &SM) { - Out << "\"location\": "; - if (!Loc.isFileID()) { - Out << "null"; - return; - } - - Out << "{ \"line\": " << SM.getExpansionLineNumber(Loc) - << ", \"column\": " << SM.getExpansionColumnNumber(Loc) << " }"; -} - void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { const ASTContext &Context = getLocationContext()->getAnalysisDeclContext()->getASTContext(); @@ -111,16 +100,18 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { case ProgramPoint::PreImplicitCallKind: { ImplicitCallPoint PC = castAs(); Out << "PreCall\", \"decl\": \"" - << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() << "\", "; - printLocJson(Out, PC.getLocation(), SM); + << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() + << "\", \"location\": "; + printSourceLocationAsJson(Out, PC.getLocation(), SM); break; } case ProgramPoint::PostImplicitCallKind: { ImplicitCallPoint PC = castAs(); Out << "PostCall\", \"decl\": \"" - << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() << "\", "; - printLocJson(Out, PC.getLocation(), SM); + << PC.getDecl()->getAsFunction()->getQualifiedNameAsString() + << "\", \"location\": "; + printSourceLocationAsJson(Out, PC.getLocation(), SM); break; } @@ -152,8 +143,8 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { E.getSrc()->printTerminatorJson(Out, Context.getLangOpts(), /*AddQuotes=*/true); - Out << ", "; - printLocJson(Out, T->getBeginLoc(), SM); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, T->getBeginLoc(), SM); Out << ", \"term_kind\": \""; if (isa(T)) { @@ -197,12 +188,16 @@ void ProgramPoint::printJson(llvm::raw_ostream &Out, const char *NL) const { Out << "Statement\", \"stmt_kind\": \"" << S->getStmtClassName() << "\", \"stmt_id\": " << S->getID(Context) - << ", \"pointer\": \"" << (const void *)S << "\", \"pretty\": "; + << ", \"pointer\": \"" << (const void *)S << "\", "; + if (const auto *CS = dyn_cast(S)) + Out << "\"cast_kind\": \"" << CS->getCastKindName() << "\", "; + + Out << "\"pretty\": "; S->printJson(Out, nullptr, PP, AddQuotes); - Out << ", "; - printLocJson(Out, S->getBeginLoc(), SM); + Out << ", \"location\": "; + printSourceLocationAsJson(Out, S->getBeginLoc(), SM); Out << ", \"stmt_point_kind\": \""; if (getAs()) diff --git a/clang/lib/Analysis/RetainSummaryManager.cpp b/clang/lib/Analysis/RetainSummaryManager.cpp index 4f0fced60bfea..6f46917b2dfc6 100644 --- a/clang/lib/Analysis/RetainSummaryManager.cpp +++ b/clang/lib/Analysis/RetainSummaryManager.cpp @@ -152,6 +152,10 @@ static bool isOSObjectDynamicCast(StringRef S) { return S == "safeMetaCast"; } +static bool isOSObjectRequiredCast(StringRef S) { + return S == "requiredMetaCast"; +} + static bool isOSObjectThisCast(StringRef S) { return S == "metaCast"; } @@ -234,7 +238,8 @@ RetainSummaryManager::getSummaryForOSObject(const FunctionDecl *FD, if (RetTy->isPointerType()) { const CXXRecordDecl *PD = RetTy->getPointeeType()->getAsCXXRecordDecl(); if (PD && isOSObjectSubclass(PD)) { - if (isOSObjectDynamicCast(FName) || isOSObjectThisCast(FName)) + if (isOSObjectDynamicCast(FName) || isOSObjectRequiredCast(FName) || + isOSObjectThisCast(FName)) return getDefaultSummary(); // TODO: Add support for the slightly common *Matching(table) idiom. @@ -499,7 +504,7 @@ RetainSummaryManager::generateSummary(const FunctionDecl *FD, FName = FName.substr(FName.find_first_not_of('_')); // Inspect the result type. Strip away any typedefs. - const auto *FT = FD->getType()->getAs(); + const auto *FT = FD->getType()->castAs(); QualType RetTy = FT->getReturnType(); if (TrackOSObjects) @@ -745,6 +750,8 @@ RetainSummaryManager::canEval(const CallExpr *CE, const FunctionDecl *FD, if (TrackOSObjects) { if (isOSObjectDynamicCast(FName) && FD->param_size() >= 1) { return BehaviorSummary::IdentityOrZero; + } else if (isOSObjectRequiredCast(FName) && FD->param_size() >= 1) { + return BehaviorSummary::Identity; } else if (isOSObjectThisCast(FName) && isa(FD) && !cast(FD)->isStatic()) { return BehaviorSummary::IdentityThis; diff --git a/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt b/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt index 0a8ff48755f17..b8a04e32835c1 100644 --- a/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt +++ b/clang/lib/Analysis/plugins/CheckerDependencyHandling/CMakeLists.txt @@ -1,5 +1,5 @@ set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CheckerDependencyHandlingAnalyzerPlugin.exports) -add_llvm_library(CheckerDependencyHandlingAnalyzerPlugin MODULE CheckerDependencyHandling.cpp PLUGIN_TOOL clang) +add_llvm_library(CheckerDependencyHandlingAnalyzerPlugin MODULE BUILDTREE_ONLY CheckerDependencyHandling.cpp PLUGIN_TOOL clang) target_link_libraries(CheckerDependencyHandlingAnalyzerPlugin PRIVATE clangAnalysis diff --git a/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt b/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt index 6e289933c2dd4..a7a8666f88d95 100644 --- a/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt +++ b/clang/lib/Analysis/plugins/CheckerOptionHandling/CMakeLists.txt @@ -1,5 +1,5 @@ set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/CheckerOptionHandlingAnalyzerPlugin.exports) -add_llvm_library(CheckerOptionHandlingAnalyzerPlugin MODULE CheckerOptionHandling.cpp PLUGIN_TOOL clang) +add_llvm_library(CheckerOptionHandlingAnalyzerPlugin MODULE BUILDTREE_ONLY CheckerOptionHandling.cpp PLUGIN_TOOL clang) target_link_libraries(CheckerOptionHandlingAnalyzerPlugin PRIVATE clangAnalysis diff --git a/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt b/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt index 639a97f253112..0e97acfa646ae 100644 --- a/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt +++ b/clang/lib/Analysis/plugins/SampleAnalyzer/CMakeLists.txt @@ -1,5 +1,5 @@ set(LLVM_EXPORTED_SYMBOL_FILE ${CMAKE_CURRENT_SOURCE_DIR}/SampleAnalyzerPlugin.exports) -add_llvm_library(SampleAnalyzerPlugin MODULE MainCallChecker.cpp PLUGIN_TOOL clang) +add_llvm_library(SampleAnalyzerPlugin MODULE BUILDTREE_ONLY MainCallChecker.cpp PLUGIN_TOOL clang) target_link_libraries(SampleAnalyzerPlugin PRIVATE clangAnalysis diff --git a/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp b/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp index 8bd4085108e9a..57fa316a4db49 100644 --- a/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp +++ b/clang/lib/Analysis/plugins/SampleAnalyzer/MainCallChecker.cpp @@ -36,8 +36,8 @@ void MainCallChecker::checkPreStmt(const CallExpr *CE, if (!BT) BT.reset(new BugType(this, "call to main", "example analyzer plugin")); - std::unique_ptr report = - llvm::make_unique(*BT, BT->getName(), N); + auto report = + llvm::make_unique(*BT, BT->getDescription(), N); report->addRange(Callee->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/clang/lib/CrossTU/CrossTranslationUnit.cpp b/clang/lib/CrossTU/CrossTranslationUnit.cpp index 4c00bda17f52a..880129d23151a 100644 --- a/clang/lib/CrossTU/CrossTranslationUnit.cpp +++ b/clang/lib/CrossTU/CrossTranslationUnit.cpp @@ -43,6 +43,8 @@ STATISTIC(NumGetCTUSuccess, STATISTIC(NumTripleMismatch, "The # of triple mismatches"); STATISTIC(NumLangMismatch, "The # of language mismatches"); STATISTIC(NumLangDialectMismatch, "The # of language dialect mismatches"); +STATISTIC(NumASTLoadThresholdReached, + "The # of ASTs not loaded because of threshold"); // Same as Triple's equality operator, but we check a field only if that is // known in both instances. @@ -102,6 +104,8 @@ class IndexErrorCategory : public std::error_category { return "Language mismatch"; case index_error_code::lang_dialect_mismatch: return "Language dialect mismatch"; + case index_error_code::load_threshold_reached: + return "Load threshold reached"; } llvm_unreachable("Unrecognized index_error_code."); } @@ -180,7 +184,8 @@ template static bool hasBodyOrInit(const T *D) { } CrossTranslationUnitContext::CrossTranslationUnitContext(CompilerInstance &CI) - : CI(CI), Context(CI.getASTContext()) {} + : CI(CI), Context(CI.getASTContext()), + CTULoadThreshold(CI.getAnalyzerOpts()->CTUImportThreshold) {} CrossTranslationUnitContext::~CrossTranslationUnitContext() {} @@ -228,8 +233,8 @@ llvm::Expected CrossTranslationUnitContext::getCrossTUDefinitionImpl( if (LookupName.empty()) return llvm::make_error( index_error_code::failed_to_generate_usr); - llvm::Expected ASTUnitOrError = - loadExternalAST(LookupName, CrossTUDir, IndexName, DisplayCTUProgress); + llvm::Expected ASTUnitOrError = loadExternalAST( + LookupName, CrossTUDir, IndexName, DisplayCTUProgress); if (!ASTUnitOrError) return ASTUnitOrError.takeError(); ASTUnit *Unit = *ASTUnitOrError; @@ -286,7 +291,7 @@ llvm::Expected CrossTranslationUnitContext::getCrossTUDefinitionImpl( TranslationUnitDecl *TU = Unit->getASTContext().getTranslationUnitDecl(); if (const T *ResultDecl = findDefInDeclContext(TU, LookupName)) - return importDefinition(ResultDecl); + return importDefinition(ResultDecl, Unit); return llvm::make_error(index_error_code::failed_import); } @@ -338,6 +343,13 @@ llvm::Expected CrossTranslationUnitContext::loadExternalAST( // a lookup name from a single translation unit. If multiple // translation units contains decls with the same lookup name an // error will be returned. + + if (NumASTLoaded >= CTULoadThreshold) { + ++NumASTLoadThresholdReached; + return llvm::make_error( + index_error_code::load_threshold_reached); + } + ASTUnit *Unit = nullptr; auto NameUnitCacheEntry = NameASTUnitMap.find(LookupName); if (NameUnitCacheEntry == NameASTUnitMap.end()) { @@ -375,6 +387,7 @@ llvm::Expected CrossTranslationUnitContext::loadExternalAST( ASTUnit::LoadEverything, Diags, CI.getFileSystemOpts())); Unit = LoadedUnit.get(); FileASTUnitMap[ASTFileName] = std::move(LoadedUnit); + ++NumASTLoaded; if (DisplayCTUProgress) { llvm::errs() << "CTU loaded AST file: " << ASTFileName << "\n"; @@ -394,10 +407,13 @@ llvm::Expected CrossTranslationUnitContext::loadExternalAST( template llvm::Expected -CrossTranslationUnitContext::importDefinitionImpl(const T *D) { +CrossTranslationUnitContext::importDefinitionImpl(const T *D, ASTUnit *Unit) { assert(hasBodyOrInit(D) && "Decls to be imported should have body or init."); - ASTImporter &Importer = getOrCreateASTImporter(D->getASTContext()); + assert(&D->getASTContext() == &Unit->getASTContext() && + "ASTContext of Decl and the unit should match."); + ASTImporter &Importer = getOrCreateASTImporter(Unit); + auto ToDeclOrError = Importer.Import(D); if (!ToDeclOrError) { handleAllErrors(ToDeclOrError.takeError(), @@ -424,13 +440,15 @@ CrossTranslationUnitContext::importDefinitionImpl(const T *D) { } llvm::Expected -CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD) { - return importDefinitionImpl(FD); +CrossTranslationUnitContext::importDefinition(const FunctionDecl *FD, + ASTUnit *Unit) { + return importDefinitionImpl(FD, Unit); } llvm::Expected -CrossTranslationUnitContext::importDefinition(const VarDecl *VD) { - return importDefinitionImpl(VD); +CrossTranslationUnitContext::importDefinition(const VarDecl *VD, + ASTUnit *Unit) { + return importDefinitionImpl(VD, Unit); } void CrossTranslationUnitContext::lazyInitImporterSharedSt( @@ -440,7 +458,9 @@ void CrossTranslationUnitContext::lazyInitImporterSharedSt( } ASTImporter & -CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) { +CrossTranslationUnitContext::getOrCreateASTImporter(ASTUnit *Unit) { + ASTContext &From = Unit->getASTContext(); + auto I = ASTUnitImporterMap.find(From.getTranslationUnitDecl()); if (I != ASTUnitImporterMap.end()) return *I->second; @@ -448,9 +468,32 @@ CrossTranslationUnitContext::getOrCreateASTImporter(ASTContext &From) { ASTImporter *NewImporter = new ASTImporter( Context, Context.getSourceManager().getFileManager(), From, From.getSourceManager().getFileManager(), false, ImporterSharedSt); + NewImporter->setFileIDImportHandler([this, Unit](FileID ToID, FileID FromID) { + assert(ImportedFileIDs.find(ToID) == ImportedFileIDs.end() && + "FileID already imported, should not happen."); + ImportedFileIDs[ToID] = std::make_pair(FromID, Unit); + }); ASTUnitImporterMap[From.getTranslationUnitDecl()].reset(NewImporter); return *NewImporter; } +llvm::Optional> +CrossTranslationUnitContext::getImportedFromSourceLocation( + const clang::SourceLocation &ToLoc) const { + const SourceManager &SM = Context.getSourceManager(); + auto DecToLoc = SM.getDecomposedLoc(ToLoc); + + auto I = ImportedFileIDs.find(DecToLoc.first); + if (I == ImportedFileIDs.end()) + return {}; + + FileID FromID = I->second.first; + clang::ASTUnit *Unit = I->second.second; + SourceLocation FromLoc = + Unit->getSourceManager().getComposedLoc(FromID, DecToLoc.second); + + return std::make_pair(FromLoc, Unit); +} + } // namespace cross_tu } // namespace clang diff --git a/clang/lib/Frontend/CompilerInvocation.cpp b/clang/lib/Frontend/CompilerInvocation.cpp index dfc74e7ffb09f..8241c3e906d12 100644 --- a/clang/lib/Frontend/CompilerInvocation.cpp +++ b/clang/lib/Frontend/CompilerInvocation.cpp @@ -303,7 +303,7 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args, .Case("true", true) .Case("false", false) .Default(false); - Opts.DisableAllChecks = Args.hasArg(OPT_analyzer_disable_all_checks); + Opts.DisableAllCheckers = Args.hasArg(OPT_analyzer_disable_all_checks); Opts.visualizeExplodedGraphWithGraphViz = Args.hasArg(OPT_analyzer_viz_egraph_graphviz); @@ -324,18 +324,18 @@ static bool ParseAnalyzerArgs(AnalyzerOptions &Opts, ArgList &Args, getLastArgIntValue(Args, OPT_analyzer_inline_max_stack_depth, Opts.InlineMaxStackDepth, Diags); - Opts.CheckersControlList.clear(); + Opts.CheckersAndPackages.clear(); for (const Arg *A : Args.filtered(OPT_analyzer_checker, OPT_analyzer_disable_checker)) { A->claim(); - bool enable = (A->getOption().getID() == OPT_analyzer_checker); + bool IsEnabled = A->getOption().getID() == OPT_analyzer_checker; // We can have a list of comma separated checker names, e.g: // '-analyzer-checker=cocoa,unix' - StringRef checkerList = A->getValue(); - SmallVector checkers; - checkerList.split(checkers, ","); - for (auto checker : checkers) - Opts.CheckersControlList.emplace_back(checker, enable); + StringRef CheckerAndPackageList = A->getValue(); + SmallVector CheckersAndPackages; + CheckerAndPackageList.split(CheckersAndPackages, ","); + for (const StringRef CheckerOrPackage : CheckersAndPackages) + Opts.CheckersAndPackages.emplace_back(CheckerOrPackage, IsEnabled); } // Go through the analyzer configuration options. @@ -464,9 +464,42 @@ static void parseAnalyzerConfigs(AnalyzerOptions &AnOpts, // At this point, AnalyzerOptions is configured. Let's validate some options. + // FIXME: Here we try to validate the silenced checkers or packages are valid. + // The current approach only validates the registered checkers which does not + // contain the runtime enabled checkers and optimally we would validate both. + if (!AnOpts.RawSilencedCheckersAndPackages.empty()) { + std::vector Checkers = + AnOpts.getRegisteredCheckers(/*IncludeExperimental=*/true); + std::vector Packages = + AnOpts.getRegisteredPackages(/*IncludeExperimental=*/true); + + SmallVector CheckersAndPackages; + AnOpts.RawSilencedCheckersAndPackages.split(CheckersAndPackages, ";"); + + for (const StringRef CheckerOrPackage : CheckersAndPackages) { + if (Diags) { + bool IsChecker = CheckerOrPackage.contains('.'); + bool IsValidName = + IsChecker + ? llvm::find(Checkers, CheckerOrPackage) != Checkers.end() + : llvm::find(Packages, CheckerOrPackage) != Packages.end(); + + if (!IsValidName) + Diags->Report(diag::err_unknown_analyzer_checker_or_package) + << CheckerOrPackage; + } + + AnOpts.SilencedCheckersAndPackages.emplace_back(CheckerOrPackage); + } + } + if (!Diags) return; + if (AnOpts.ShouldTrackConditionsDebug && !AnOpts.ShouldTrackConditions) + Diags->Report(diag::err_analyzer_config_invalid_input) + << "track-conditions-debug" << "'track-conditions' to also be enabled"; + if (!AnOpts.CTUDir.empty() && !llvm::sys::fs::is_directory(AnOpts.CTUDir)) Diags->Report(diag::err_analyzer_config_invalid_input) << "ctu-dir" << "a filename"; diff --git a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp index c5225d1873360..25db5a67474c0 100644 --- a/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp +++ b/clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp @@ -274,6 +274,7 @@ bool ExecuteCompilerInvocation(CompilerInstance *Clang) { AnOpts, Clang->getDiagnostics(), Clang->getLangOpts()); + return true; } // Honor -analyzer-config-help. diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp index 58017acb4a246..40baa279dee64 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundChecker.cpp @@ -75,7 +75,8 @@ void ArrayBoundChecker::checkLocation(SVal l, bool isLoad, const Stmt* LoadS, // reference is outside the range. // Generate a report for this bug. - auto report = llvm::make_unique(*BT, BT->getDescription(), N); + auto report = + llvm::make_unique(*BT, BT->getDescription(), N); report->addRange(LoadS->getSourceRange()); C.emitReport(std::move(report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp index 3bf8a1836b19b..56b68689c6987 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ArrayBoundCheckerV2.cpp @@ -256,7 +256,7 @@ void ArrayBoundCheckerV2::reportOOB( break; } - auto BR = llvm::make_unique(*BT, os.str(), errorNode); + auto BR = llvm::make_unique(*BT, os.str(), errorNode); BR->addVisitor(std::move(Visitor)); checkerContext.emitReport(std::move(BR)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp b/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp index e3fb4c3eb523f..24b2b021657f2 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BasicObjCFoundationChecks.cpp @@ -211,7 +211,7 @@ void NilArgChecker::generateBugReport(ExplodedNode *N, if (!BT) BT.reset(new APIMisuse(this, "nil argument")); - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); R->addRange(Range); bugreporter::trackExpressionValue(N, E, *R); C.emitReport(std::move(R)); @@ -520,7 +520,7 @@ void CFNumberChecker::checkPreStmt(const CallExpr *CE, if (!BT) BT.reset(new APIMisuse(this, "Bad use of CFNumber APIs")); - auto report = llvm::make_unique(*BT, os.str(), N); + auto report = llvm::make_unique(*BT, os.str(), N); report->addRange(CE->getArg(2)->getSourceRange()); C.emitReport(std::move(report)); } @@ -575,7 +575,7 @@ void CFRetainReleaseChecker::checkPreCall(const CallEvent &Call, OS << "Null pointer argument in call to " << cast(Call.getDecl())->getName(); - auto report = llvm::make_unique(BT, OS.str(), N); + auto report = llvm::make_unique(BT, OS.str(), N); report->addRange(Call.getArgSourceRange(0)); bugreporter::trackExpressionValue(N, Call.getArgExpr(0), *report); C.emitReport(std::move(report)); @@ -635,7 +635,7 @@ void ClassReleaseChecker::checkPreObjCMessage(const ObjCMethodCall &msg, "of class '" << Class->getName() << "' and not the class directly"; - auto report = llvm::make_unique(*BT, os.str(), N); + auto report = llvm::make_unique(*BT, os.str(), N); report->addRange(msg.getSourceRange()); C.emitReport(std::move(report)); } @@ -788,7 +788,8 @@ void VariadicMethodTypeChecker::checkPreObjCMessage(const ObjCMethodCall &msg, ArgTy.print(os, C.getLangOpts()); os << "'"; - auto R = llvm::make_unique(*BT, os.str(), errorNode.getValue()); + auto R = llvm::make_unique(*BT, os.str(), + errorNode.getValue()); R->addRange(msg.getArgSourceRange(I)); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp index 009160fc98157..2a20d86a6875e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BlockInCriticalSectionChecker.cpp @@ -126,7 +126,7 @@ bool BlockInCriticalSectionChecker::isLockFunction(const CallEvent &Call) const bool BlockInCriticalSectionChecker::isUnlockFunction(const CallEvent &Call) const { if (const auto *Dtor = dyn_cast(&Call)) { - const auto *DRecordDecl = dyn_cast(Dtor->getDecl()->getParent()); + const auto *DRecordDecl = cast(Dtor->getDecl()->getParent()); auto IdentifierInfo = DRecordDecl->getIdentifier(); if (IdentifierInfo == IILockGuard || IdentifierInfo == IIUniqueLock) return true; @@ -173,7 +173,8 @@ void BlockInCriticalSectionChecker::reportBlockInCritSection( llvm::raw_string_ostream os(msg); os << "Call to blocking function '" << Call.getCalleeIdentifier()->getName() << "' inside of critical section"; - auto R = llvm::make_unique(*BlockInCritSectionBugType, os.str(), ErrNode); + auto R = llvm::make_unique(*BlockInCritSectionBugType, + os.str(), ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(BlockDescSym); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp index de8763c1b7b57..6ccf4d51ae9b1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BoolAssignmentChecker.cpp @@ -34,7 +34,9 @@ void BoolAssignmentChecker::emitReport(ProgramStateRef state, if (ExplodedNode *N = C.generateNonFatalErrorNode(state)) { if (!BT) BT.reset(new BuiltinBug(this, "Assignment of a non-Boolean value")); - C.emitReport(llvm::make_unique(*BT, BT->getDescription(), N)); + + C.emitReport( + llvm::make_unique(*BT, BT->getDescription(), N)); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp index 8544f98b948df..10594e331cbee 100644 --- a/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/BuiltinFunctionChecker.cpp @@ -14,6 +14,7 @@ #include "clang/Basic/Builtins.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" using namespace clang; @@ -23,30 +24,32 @@ namespace { class BuiltinFunctionChecker : public Checker { public: - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; } -bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, +bool BuiltinFunctionChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); - const FunctionDecl *FD = C.getCalleeDecl(CE); - const LocationContext *LCtx = C.getLocationContext(); + const auto *FD = dyn_cast_or_null(Call.getDecl()); if (!FD) return false; + const LocationContext *LCtx = C.getLocationContext(); + const Expr *CE = Call.getOriginExpr(); + switch (FD->getBuiltinID()) { default: return false; case Builtin::BI__builtin_assume: { - assert (CE->arg_begin() != CE->arg_end()); - SVal ArgSVal = C.getSVal(CE->getArg(0)); - if (ArgSVal.isUndef()) + assert (Call.getNumArgs() > 0); + SVal Arg = Call.getArgSVal(0); + if (Arg.isUndef()) return true; // Return true to model purity. - state = state->assume(ArgSVal.castAs(), true); + state = state->assume(Arg.castAs(), true); // FIXME: do we want to warn here? Not right now. The most reports might // come from infeasible paths, thus being false positives. if (!state) { @@ -66,9 +69,9 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // __builtin_assume_aligned, just return the value of the subexpression. // __builtin_addressof is going from a reference to a pointer, but those // are represented the same way in the analyzer. - assert (CE->arg_begin() != CE->arg_end()); - SVal X = C.getSVal(*(CE->arg_begin())); - C.addTransition(state->BindExpr(CE, LCtx, X)); + assert (Call.getNumArgs() > 0); + SVal Arg = Call.getArgSVal(0); + C.addTransition(state->BindExpr(CE, LCtx, Arg)); return true; } @@ -82,12 +85,14 @@ bool BuiltinFunctionChecker::evalCall(const CallExpr *CE, // Set the extent of the region in bytes. This enables us to use the // SVal of the argument directly. If we save the extent in bits, we // cannot represent values like symbol*8. - auto Size = C.getSVal(*(CE->arg_begin())).castAs(); + auto Size = Call.getArgSVal(0); + if (Size.isUndef()) + return true; // Return true to model purity. SValBuilder& svalBuilder = C.getSValBuilder(); DefinedOrUnknownSVal Extent = R->getExtent(svalBuilder); DefinedOrUnknownSVal extentMatchesSizeArg = - svalBuilder.evalEQ(state, Extent, Size); + svalBuilder.evalEQ(state, Extent, Size.castAs()); state = state->assume(extentMatchesSizeArg, true); assert(state && "The region should not have any previous constraints"); diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt index df12fa5c9a11a..7cbd8c2a71389 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt @@ -16,6 +16,7 @@ add_clang_library(clangStaticAnalyzerCheckers CallAndMessageChecker.cpp CastSizeChecker.cpp CastToStructChecker.cpp + CastValueChecker.cpp CheckObjCDealloc.cpp CheckObjCInstMethSignature.cpp CheckSecuritySyntaxOnly.cpp @@ -83,6 +84,7 @@ add_clang_library(clangStaticAnalyzerCheckers RetainCountChecker/RetainCountDiagnostics.cpp ReturnPointerRangeChecker.cpp ReturnUndefChecker.cpp + ReturnValueChecker.cpp RunLoopAutoreleaseLeakChecker.cpp SimpleStreamChecker.cpp SmartPtrModeling.cpp diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp index 73a5d58d9eeab..fc461d2ea94ab 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringChecker.cpp @@ -17,6 +17,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "llvm/ADT/STLExtras.h" @@ -47,17 +48,17 @@ class CStringChecker : public Checker< eval::Call, DefaultBool CheckCStringBufferOverlap; DefaultBool CheckCStringNotNullTerm; - CheckName CheckNameCStringNullArg; - CheckName CheckNameCStringOutOfBounds; - CheckName CheckNameCStringBufferOverlap; - CheckName CheckNameCStringNotNullTerm; + CheckerNameRef CheckNameCStringNullArg; + CheckerNameRef CheckNameCStringOutOfBounds; + CheckerNameRef CheckNameCStringBufferOverlap; + CheckerNameRef CheckNameCStringNotNullTerm; }; CStringChecksFilter Filter; static void *getTag() { static int tag; return &tag; } - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; void checkPreStmt(const DeclStmt *DS, CheckerContext &C) const; void checkLiveSymbols(ProgramStateRef state, SymbolReaper &SR) const; void checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const; @@ -72,7 +73,38 @@ class CStringChecker : public Checker< eval::Call, typedef void (CStringChecker::*FnCheck)(CheckerContext &, const CallExpr *) const; + CallDescriptionMap Callbacks = { + {{CDF_MaybeBuiltin, "memcpy", 3}, &CStringChecker::evalMemcpy}, + {{CDF_MaybeBuiltin, "mempcpy", 3}, &CStringChecker::evalMempcpy}, + {{CDF_MaybeBuiltin, "memcmp", 3}, &CStringChecker::evalMemcmp}, + {{CDF_MaybeBuiltin, "memmove", 3}, &CStringChecker::evalMemmove}, + {{CDF_MaybeBuiltin, "memset", 3}, &CStringChecker::evalMemset}, + {{CDF_MaybeBuiltin, "explicit_memset", 3}, &CStringChecker::evalMemset}, + {{CDF_MaybeBuiltin, "strcpy", 2}, &CStringChecker::evalStrcpy}, + {{CDF_MaybeBuiltin, "strncpy", 3}, &CStringChecker::evalStrncpy}, + {{CDF_MaybeBuiltin, "stpcpy", 2}, &CStringChecker::evalStpcpy}, + {{CDF_MaybeBuiltin, "strlcpy", 3}, &CStringChecker::evalStrlcpy}, + {{CDF_MaybeBuiltin, "strcat", 2}, &CStringChecker::evalStrcat}, + {{CDF_MaybeBuiltin, "strncat", 3}, &CStringChecker::evalStrncat}, + {{CDF_MaybeBuiltin, "strlcat", 3}, &CStringChecker::evalStrlcat}, + {{CDF_MaybeBuiltin, "strlen", 1}, &CStringChecker::evalstrLength}, + {{CDF_MaybeBuiltin, "strnlen", 2}, &CStringChecker::evalstrnLength}, + {{CDF_MaybeBuiltin, "strcmp", 2}, &CStringChecker::evalStrcmp}, + {{CDF_MaybeBuiltin, "strncmp", 3}, &CStringChecker::evalStrncmp}, + {{CDF_MaybeBuiltin, "strcasecmp", 2}, &CStringChecker::evalStrcasecmp}, + {{CDF_MaybeBuiltin, "strncasecmp", 3}, &CStringChecker::evalStrncasecmp}, + {{CDF_MaybeBuiltin, "strsep", 2}, &CStringChecker::evalStrsep}, + {{CDF_MaybeBuiltin, "bcopy", 3}, &CStringChecker::evalBcopy}, + {{CDF_MaybeBuiltin, "bcmp", 3}, &CStringChecker::evalMemcmp}, + {{CDF_MaybeBuiltin, "bzero", 2}, &CStringChecker::evalBzero}, + {{CDF_MaybeBuiltin, "explicit_bzero", 2}, &CStringChecker::evalBzero}, + }; + + // These require a bit of special handling. + CallDescription StdCopy{{"std", "copy"}, 3}, + StdCopyBackward{{"std", "copy_backward"}, 3}; + FnCheck identifyCall(const CallEvent &Call, CheckerContext &C) const; void evalMemcpy(CheckerContext &C, const CallExpr *CE) const; void evalMempcpy(CheckerContext &C, const CallExpr *CE) const; void evalMemmove(CheckerContext &C, const CallExpr *CE) const; @@ -166,7 +198,8 @@ class CStringChecker : public Checker< eval::Call, ProgramStateRef checkNonNull(CheckerContext &C, ProgramStateRef state, const Expr *S, - SVal l) const; + SVal l, + unsigned IdxOfArg) const; ProgramStateRef CheckLocation(CheckerContext &C, ProgramStateRef state, const Expr *S, @@ -245,7 +278,8 @@ CStringChecker::assumeZero(CheckerContext &C, ProgramStateRef state, SVal V, ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, ProgramStateRef state, - const Expr *S, SVal l) const { + const Expr *S, SVal l, + unsigned IdxOfArg) const { // If a previous check has failed, propagate the failure. if (!state) return nullptr; @@ -256,11 +290,13 @@ ProgramStateRef CStringChecker::checkNonNull(CheckerContext &C, if (stateNull && !stateNonNull) { if (Filter.CheckCStringNullArg) { SmallString<80> buf; - llvm::raw_svector_ostream os(buf); + llvm::raw_svector_ostream OS(buf); assert(CurrentFunctionDescription); - os << "Null pointer argument in call to " << CurrentFunctionDescription; + OS << "Null pointer argument in call to " << CurrentFunctionDescription + << ' ' << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg) + << " parameter"; - emitNullArgBug(C, stateNull, S, os.str()); + emitNullArgBug(C, stateNull, S, OS.str()); } return nullptr; } @@ -352,7 +388,7 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, // Check that the first buffer is non-null. SVal BufVal = C.getSVal(FirstBuf); - state = checkNonNull(C, state, FirstBuf, BufVal); + state = checkNonNull(C, state, FirstBuf, BufVal, 1); if (!state) return nullptr; @@ -392,7 +428,7 @@ ProgramStateRef CStringChecker::CheckBufferAccess(CheckerContext &C, // If there's a second buffer, check it as well. if (SecondBuf) { BufVal = state->getSVal(SecondBuf, LCtx); - state = checkNonNull(C, state, SecondBuf, BufVal); + state = checkNonNull(C, state, SecondBuf, BufVal, 2); if (!state) return nullptr; @@ -534,7 +570,7 @@ void CStringChecker::emitOverlapBug(CheckerContext &C, ProgramStateRef state, categories::UnixAPI, "Improper arguments")); // Generate a report for this bug. - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_Overlap, "Arguments must not be overlapping buffers", N); report->addRange(First->getSourceRange()); report->addRange(Second->getSourceRange()); @@ -551,7 +587,7 @@ void CStringChecker::emitNullArgBug(CheckerContext &C, ProgramStateRef State, "Null pointer argument in call to byte string function")); BuiltinBug *BT = static_cast(BT_Null.get()); - auto Report = llvm::make_unique(*BT, WarningMsg, N); + auto Report = llvm::make_unique(*BT, WarningMsg, N); Report->addRange(S->getSourceRange()); if (const auto *Ex = dyn_cast(S)) bugreporter::trackExpressionValue(N, Ex, *Report); @@ -575,7 +611,7 @@ void CStringChecker::emitOutOfBoundsBug(CheckerContext &C, // FIXME: It would be nice to eventually make this diagnostic more clear, // e.g., by referencing the original declaration or by saying *why* this // reference is outside the range. - auto Report = llvm::make_unique(*BT, WarningMsg, N); + auto Report = llvm::make_unique(*BT, WarningMsg, N); Report->addRange(S->getSourceRange()); C.emitReport(std::move(Report)); } @@ -590,7 +626,8 @@ void CStringChecker::emitNotCStringBug(CheckerContext &C, ProgramStateRef State, Filter.CheckNameCStringNotNullTerm, categories::UnixAPI, "Argument is not a null-terminated string.")); - auto Report = llvm::make_unique(*BT_NotCString, WarningMsg, N); + auto Report = + llvm::make_unique(*BT_NotCString, WarningMsg, N); Report->addRange(S->getSourceRange()); C.emitReport(std::move(Report)); @@ -612,7 +649,8 @@ void CStringChecker::emitAdditionOverflowBug(CheckerContext &C, "This expression will create a string whose length is too big to " "be represented as a size_t"; - auto Report = llvm::make_unique(*BT_NotCString, WarningMsg, N); + auto Report = + llvm::make_unique(*BT_NotCString, WarningMsg, N); C.emitReport(std::move(Report)); } } @@ -1131,7 +1169,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // Ensure the destination is not null. If it is NULL there will be a // NULL pointer dereference. - state = checkNonNull(C, state, Dest, destVal); + state = checkNonNull(C, state, Dest, destVal, 1); if (!state) return; @@ -1140,7 +1178,7 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, // Ensure the source is not null. If it is NULL there will be a // NULL pointer dereference. - state = checkNonNull(C, state, Source, srcVal); + state = checkNonNull(C, state, Source, srcVal, 2); if (!state) return; @@ -1200,9 +1238,6 @@ void CStringChecker::evalCopyCommon(CheckerContext &C, void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void *memcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is the address of the destination buffer. const Expr *Dest = CE->getArg(0); @@ -1212,9 +1247,6 @@ void CStringChecker::evalMemcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void *mempcpy(void *restrict dst, const void *restrict src, size_t n); // The return value is a pointer to the byte following the last written byte. const Expr *Dest = CE->getArg(0); @@ -1224,9 +1256,6 @@ void CStringChecker::evalMempcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void *memmove(void *dst, const void *src, size_t n); // The return value is the address of the destination buffer. const Expr *Dest = CE->getArg(0); @@ -1236,18 +1265,12 @@ void CStringChecker::evalMemmove(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalBcopy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // void bcopy(const void *src, void *dst, size_t n); evalCopyCommon(C, CE, C.getState(), CE->getArg(2), CE->getArg(1), CE->getArg(0)); } void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // int memcmp(const void *s1, const void *s2, size_t n); CurrentFunctionDescription = "memory comparison function"; @@ -1322,18 +1345,12 @@ void CStringChecker::evalMemcmp(CheckerContext &C, const CallExpr *CE) const { void CStringChecker::evalstrLength(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 1) - return; - // size_t strlen(const char *s); evalstrLengthCommon(C, CE, /* IsStrnlen = */ false); } void CStringChecker::evalstrnLength(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - // size_t strnlen(const char *s, size_t maxlen); evalstrLengthCommon(C, CE, /* IsStrnlen = */ true); } @@ -1372,7 +1389,7 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, const Expr *Arg = CE->getArg(0); SVal ArgVal = state->getSVal(Arg, LCtx); - state = checkNonNull(C, state, Arg, ArgVal); + state = checkNonNull(C, state, Arg, ArgVal, 1); if (!state) return; @@ -1458,9 +1475,6 @@ void CStringChecker::evalstrLengthCommon(CheckerContext &C, const CallExpr *CE, } void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - // char *strcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1469,9 +1483,6 @@ void CStringChecker::evalStrcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // char *strncpy(char *restrict dst, const char *restrict src, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1480,9 +1491,6 @@ void CStringChecker::evalStrncpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - // char *stpcpy(char *restrict dst, const char *restrict src); evalStrcpyCommon(C, CE, /* returnEnd = */ true, @@ -1491,9 +1499,6 @@ void CStringChecker::evalStpcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrlcpy(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // char *strlcpy(char *dst, const char *src, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ true, @@ -1503,9 +1508,6 @@ void CStringChecker::evalStrlcpy(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - //char *strcat(char *restrict s1, const char *restrict s2); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1514,9 +1516,6 @@ void CStringChecker::evalStrcat(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - //char *strncat(char *restrict s1, const char *restrict s2, size_t n); evalStrcpyCommon(C, CE, /* returnEnd = */ false, @@ -1525,9 +1524,6 @@ void CStringChecker::evalStrncat(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalStrlcat(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - // FIXME: strlcat() uses a different rule for bound checking, i.e. 'n' means // a different thing as compared to strncat(). This currently causes // false positives in the alpha string bound checker. @@ -1551,14 +1547,14 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, const Expr *Dst = CE->getArg(0); SVal DstVal = state->getSVal(Dst, LCtx); - state = checkNonNull(C, state, Dst, DstVal); + state = checkNonNull(C, state, Dst, DstVal, 1); if (!state) return; // Check that the source is non-null. const Expr *srcExpr = CE->getArg(1); SVal srcVal = state->getSVal(srcExpr, LCtx); - state = checkNonNull(C, state, srcExpr, srcVal); + state = checkNonNull(C, state, srcExpr, srcVal, 2); if (!state) return; @@ -1884,35 +1880,23 @@ void CStringChecker::evalStrcpyCommon(CheckerContext &C, const CallExpr *CE, } void CStringChecker::evalStrcmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - //int strcmp(const char *s1, const char *s2); evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ false); } void CStringChecker::evalStrncmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - //int strncmp(const char *s1, const char *s2, size_t n); evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ false); } void CStringChecker::evalStrcasecmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 2) - return; - //int strcasecmp(const char *s1, const char *s2); evalStrcmpCommon(C, CE, /* isBounded = */ false, /* ignoreCase = */ true); } void CStringChecker::evalStrncasecmp(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) - return; - //int strncasecmp(const char *s1, const char *s2, size_t n); evalStrcmpCommon(C, CE, /* isBounded = */ true, /* ignoreCase = */ true); } @@ -1926,14 +1910,14 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, // Check that the first string is non-null const Expr *s1 = CE->getArg(0); SVal s1Val = state->getSVal(s1, LCtx); - state = checkNonNull(C, state, s1, s1Val); + state = checkNonNull(C, state, s1, s1Val, 1); if (!state) return; // Check that the second string is non-null. const Expr *s2 = CE->getArg(1); SVal s2Val = state->getSVal(s2, LCtx); - state = checkNonNull(C, state, s2, s2Val); + state = checkNonNull(C, state, s2, s2Val, 2); if (!state) return; @@ -2046,9 +2030,6 @@ void CStringChecker::evalStrcmpCommon(CheckerContext &C, const CallExpr *CE, void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { //char *strsep(char **stringp, const char *delim); - if (CE->getNumArgs() < 2) - return; - // Sanity: does the search string parameter match the return type? const Expr *SearchStrPtr = CE->getArg(0); QualType CharPtrTy = SearchStrPtr->getType()->getPointeeType(); @@ -2063,14 +2044,14 @@ void CStringChecker::evalStrsep(CheckerContext &C, const CallExpr *CE) const { // Check that the search string pointer is non-null (though it may point to // a null string). SVal SearchStrVal = State->getSVal(SearchStrPtr, LCtx); - State = checkNonNull(C, State, SearchStrPtr, SearchStrVal); + State = checkNonNull(C, State, SearchStrPtr, SearchStrVal, 1); if (!State) return; // Check that the delimiter string is non-null. const Expr *DelimStr = CE->getArg(1); SVal DelimStrVal = State->getSVal(DelimStr, LCtx); - State = checkNonNull(C, State, DelimStr, DelimStrVal); + State = checkNonNull(C, State, DelimStr, DelimStrVal, 2); if (!State) return; @@ -2117,7 +2098,7 @@ void CStringChecker::evalStdCopyBackward(CheckerContext &C, void CStringChecker::evalStdCopyCommon(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() < 3) + if (!CE->getArg(2)->getType()->isPointerType()) return; ProgramStateRef State = C.getState(); @@ -2144,9 +2125,6 @@ void CStringChecker::evalStdCopyCommon(CheckerContext &C, } void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() != 3) - return; - CurrentFunctionDescription = "memory set function"; const Expr *Mem = CE->getArg(0); @@ -2176,7 +2154,7 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { // Ensure the memory area is not null. // If it is NULL there will be a NULL pointer dereference. - State = checkNonNull(C, StateNonZeroSize, Mem, MemVal); + State = checkNonNull(C, StateNonZeroSize, Mem, MemVal, 1); if (!State) return; @@ -2195,9 +2173,6 @@ void CStringChecker::evalMemset(CheckerContext &C, const CallExpr *CE) const { } void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { - if (CE->getNumArgs() != 2) - return; - CurrentFunctionDescription = "memory clearance function"; const Expr *Mem = CE->getArg(0); @@ -2226,7 +2201,7 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { // Ensure the memory area is not null. // If it is NULL there will be a NULL pointer dereference. - State = checkNonNull(C, StateNonZeroSize, Mem, MemVal); + State = checkNonNull(C, StateNonZeroSize, Mem, MemVal, 1); if (!State) return; @@ -2240,110 +2215,53 @@ void CStringChecker::evalBzero(CheckerContext &C, const CallExpr *CE) const { C.addTransition(State); } -static bool isCPPStdLibraryFunction(const FunctionDecl *FD, StringRef Name) { - IdentifierInfo *II = FD->getIdentifier(); - if (!II) - return false; - - if (!AnalysisDeclContext::isInStdNamespace(FD)) - return false; - - if (II->getName().equals(Name)) - return true; - - return false; -} //===----------------------------------------------------------------------===// // The driver method, and other Checker callbacks. //===----------------------------------------------------------------------===// -static CStringChecker::FnCheck identifyCall(const CallExpr *CE, - CheckerContext &C) { - const FunctionDecl *FDecl = C.getCalleeDecl(CE); - if (!FDecl) +CStringChecker::FnCheck CStringChecker::identifyCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return nullptr; + + const FunctionDecl *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD) return nullptr; + if (Call.isCalled(StdCopy)) { + return &CStringChecker::evalStdCopy; + } else if (Call.isCalled(StdCopyBackward)) { + return &CStringChecker::evalStdCopyBackward; + } + // Pro-actively check that argument types are safe to do arithmetic upon. // We do not want to crash if someone accidentally passes a structure - // into, say, a C++ overload of any of these functions. - if (isCPPStdLibraryFunction(FDecl, "copy")) { - if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType()) - return nullptr; - return &CStringChecker::evalStdCopy; - } else if (isCPPStdLibraryFunction(FDecl, "copy_backward")) { - if (CE->getNumArgs() < 3 || !CE->getArg(2)->getType()->isPointerType()) + // into, say, a C++ overload of any of these functions. We could not check + // that for std::copy because they may have arguments of other types. + for (auto I : CE->arguments()) { + QualType T = I->getType(); + if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) return nullptr; - return &CStringChecker::evalStdCopyBackward; - } else { - // An umbrella check for all C library functions. - for (auto I: CE->arguments()) { - QualType T = I->getType(); - if (!T->isIntegralOrEnumerationType() && !T->isPointerType()) - return nullptr; - } } - // FIXME: Poorly-factored string switches are slow. - if (C.isCLibraryFunction(FDecl, "memcpy")) - return &CStringChecker::evalMemcpy; - else if (C.isCLibraryFunction(FDecl, "mempcpy")) - return &CStringChecker::evalMempcpy; - else if (C.isCLibraryFunction(FDecl, "memcmp")) - return &CStringChecker::evalMemcmp; - else if (C.isCLibraryFunction(FDecl, "memmove")) - return &CStringChecker::evalMemmove; - else if (C.isCLibraryFunction(FDecl, "memset") || - C.isCLibraryFunction(FDecl, "explicit_memset")) - return &CStringChecker::evalMemset; - else if (C.isCLibraryFunction(FDecl, "strcpy")) - return &CStringChecker::evalStrcpy; - else if (C.isCLibraryFunction(FDecl, "strncpy")) - return &CStringChecker::evalStrncpy; - else if (C.isCLibraryFunction(FDecl, "stpcpy")) - return &CStringChecker::evalStpcpy; - else if (C.isCLibraryFunction(FDecl, "strlcpy")) - return &CStringChecker::evalStrlcpy; - else if (C.isCLibraryFunction(FDecl, "strcat")) - return &CStringChecker::evalStrcat; - else if (C.isCLibraryFunction(FDecl, "strncat")) - return &CStringChecker::evalStrncat; - else if (C.isCLibraryFunction(FDecl, "strlcat")) - return &CStringChecker::evalStrlcat; - else if (C.isCLibraryFunction(FDecl, "strlen")) - return &CStringChecker::evalstrLength; - else if (C.isCLibraryFunction(FDecl, "strnlen")) - return &CStringChecker::evalstrnLength; - else if (C.isCLibraryFunction(FDecl, "strcmp")) - return &CStringChecker::evalStrcmp; - else if (C.isCLibraryFunction(FDecl, "strncmp")) - return &CStringChecker::evalStrncmp; - else if (C.isCLibraryFunction(FDecl, "strcasecmp")) - return &CStringChecker::evalStrcasecmp; - else if (C.isCLibraryFunction(FDecl, "strncasecmp")) - return &CStringChecker::evalStrncasecmp; - else if (C.isCLibraryFunction(FDecl, "strsep")) - return &CStringChecker::evalStrsep; - else if (C.isCLibraryFunction(FDecl, "bcopy")) - return &CStringChecker::evalBcopy; - else if (C.isCLibraryFunction(FDecl, "bcmp")) - return &CStringChecker::evalMemcmp; - else if (C.isCLibraryFunction(FDecl, "bzero") || - C.isCLibraryFunction(FDecl, "explicit_bzero")) - return &CStringChecker::evalBzero; + const FnCheck *Callback = Callbacks.lookup(Call); + if (Callback) + return *Callback; return nullptr; } -bool CStringChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - - FnCheck evalFunction = identifyCall(CE, C); +bool CStringChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + FnCheck Callback = identifyCall(Call, C); // If the callee isn't a string function, let another checker handle it. - if (!evalFunction) + if (!Callback) return false; // Check and evaluate the call. - (this->*evalFunction)(C, CE); + const auto *CE = cast(Call.getOriginExpr()); + (this->*Callback)(C, CE); // If the evaluate call resulted in no change, chain to the next eval call // handler. @@ -2491,14 +2409,12 @@ bool ento::shouldRegisterCStringModeling(const LangOptions &LO) { void ento::register##name(CheckerManager &mgr) { \ CStringChecker *checker = mgr.getChecker(); \ checker->Filter.Check##name = true; \ - checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ + checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } - REGISTER_CHECKER(CStringNullArg) - REGISTER_CHECKER(CStringOutOfBounds) - REGISTER_CHECKER(CStringBufferOverlap) +REGISTER_CHECKER(CStringNullArg) +REGISTER_CHECKER(CStringOutOfBounds) +REGISTER_CHECKER(CStringBufferOverlap) REGISTER_CHECKER(CStringNotNullTerm) diff --git a/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp index b828ac0592363..d84fcc69a4925 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CStringSyntaxChecker.cpp @@ -156,14 +156,21 @@ bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) { const Expr *DstArg = CE->getArg(0); const Expr *LenArg = CE->getArg(2); - const auto *DstArgDecl = dyn_cast(DstArg->IgnoreParenImpCasts()); - const auto *LenArgDecl = dyn_cast(LenArg->IgnoreParenLValueCasts()); + const auto *DstArgDRE = dyn_cast(DstArg->IgnoreParenImpCasts()); + const auto *LenArgDRE = + dyn_cast(LenArg->IgnoreParenLValueCasts()); uint64_t DstOff = 0; if (isSizeof(LenArg, DstArg)) return false; + // - size_t dstlen = sizeof(dst) - if (LenArgDecl) { - const auto *LenArgVal = dyn_cast(LenArgDecl->getDecl()); + if (LenArgDRE) { + const auto *LenArgVal = dyn_cast(LenArgDRE->getDecl()); + // If it's an EnumConstantDecl instead, then we're missing out on something. + if (!LenArgVal) { + assert(isa(LenArgDRE->getDecl())); + return false; + } if (LenArgVal->getInit()) LenArg = LenArgVal->getInit(); } @@ -177,9 +184,10 @@ bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) { // Case when there is pointer arithmetic on the destination buffer // especially when we offset from the base decreasing the // buffer length accordingly. - if (!DstArgDecl) { - if (const auto *BE = dyn_cast(DstArg->IgnoreParenImpCasts())) { - DstArgDecl = dyn_cast(BE->getLHS()->IgnoreParenImpCasts()); + if (!DstArgDRE) { + if (const auto *BE = + dyn_cast(DstArg->IgnoreParenImpCasts())) { + DstArgDRE = dyn_cast(BE->getLHS()->IgnoreParenImpCasts()); if (BE->getOpcode() == BO_Add) { if ((IL = dyn_cast(BE->getRHS()->IgnoreParenImpCasts()))) { DstOff = IL->getValue().getZExtValue(); @@ -187,8 +195,9 @@ bool WalkAST::containsBadStrlcpyStrlcatPattern(const CallExpr *CE) { } } } - if (DstArgDecl) { - if (const auto *Buffer = dyn_cast(DstArgDecl->getType())) { + if (DstArgDRE) { + if (const auto *Buffer = + dyn_cast(DstArgDRE->getType())) { ASTContext &C = BR.getContext(); uint64_t BufferLen = C.getTypeSize(Buffer) / 8; auto RemainingBufferLen = BufferLen - DstOff; diff --git a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp index 5a7eba0760fe5..e61c0664d521d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CallAndMessageChecker.cpp @@ -49,7 +49,7 @@ class CallAndMessageChecker public: DefaultBool Check_CallAndMessageUnInitRefArg; - CheckName CheckName_CallAndMessageUnInitRefArg; + CheckerNameRef CheckName_CallAndMessageUnInitRefArg; void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; void checkPreStmt(const CXXDeleteExpr *DE, CheckerContext &C) const; @@ -95,7 +95,8 @@ void CallAndMessageChecker::emitBadCall(BugType *BT, CheckerContext &C, if (!N) return; - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); if (BadE) { R->addRange(BadE->getSourceRange()); if (BadE->isGLValue()) @@ -175,7 +176,7 @@ bool CallAndMessageChecker::uninitRefOrPointer( if (PSV.isUndef()) { if (ExplodedNode *N = C.generateErrorNode()) { LazyInit_BT(BD, BT); - auto R = llvm::make_unique(*BT, Os.str(), N); + auto R = llvm::make_unique(*BT, Os.str(), N); R->addRange(ArgRange); if (ArgEx) bugreporter::trackExpressionValue(N, ArgEx, *R); @@ -252,7 +253,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, SmallString<200> Buf; llvm::raw_svector_ostream Os(Buf); describeUninitializedArgumentInCall(Call, ArgumentNumber, Os); - auto R = llvm::make_unique(*BT, Os.str(), N); + auto R = llvm::make_unique(*BT, Os.str(), N); R->addRange(ArgRange); if (ArgEx) @@ -295,7 +296,7 @@ bool CallAndMessageChecker::PreVisitProcessArg(CheckerContext &C, } // Generate a report for this bug. - auto R = llvm::make_unique(*BT, os.str(), N); + auto R = llvm::make_unique(*BT, os.str(), N); R->addRange(ArgRange); if (ArgEx) @@ -358,7 +359,7 @@ void CallAndMessageChecker::checkPreStmt(const CXXDeleteExpr *DE, else Desc = "Argument to 'delete' is uninitialized"; BugType *BT = BT_cxx_delete_undef.get(); - auto R = llvm::make_unique(*BT, Desc, N); + auto R = llvm::make_unique(*BT, Desc, N); bugreporter::trackExpressionValue(N, DE, *R); C.emitReport(std::move(R)); return; @@ -420,8 +421,8 @@ void CallAndMessageChecker::checkPreCall(const CallEvent &Call, << (Params == 1 ? "" : "s") << " is called with fewer (" << Call.getNumArgs() << ")"; - C.emitReport( - llvm::make_unique(*BT_call_few_args, os.str(), N)); + C.emitReport(llvm::make_unique(*BT_call_few_args, + os.str(), N)); } } @@ -482,7 +483,8 @@ void CallAndMessageChecker::checkPreObjCMessage(const ObjCMethodCall &msg, } assert(BT && "Unknown message kind."); - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = llvm::make_unique( + *BT, BT->getDescription(), N); const ObjCMessageExpr *ME = msg.getOriginExpr(); R->addRange(ME->getReceiverRange()); @@ -525,7 +527,8 @@ void CallAndMessageChecker::emitNilReceiverBug(CheckerContext &C, os << "' that will be garbage"; } - auto report = llvm::make_unique(*BT_msg_ret, os.str(), N); + auto report = + llvm::make_unique(*BT_msg_ret, os.str(), N); report->addRange(ME->getReceiverRange()); // FIXME: This won't track "self" in messages to super. if (const Expr *receiver = ME->getInstanceReceiver()) { @@ -611,7 +614,7 @@ bool ento::shouldRegisterCallAndMessageChecker(const LangOptions &LO) { void ento::registerCallAndMessageUnInitRefArg(CheckerManager &mgr) { CallAndMessageChecker *Checker = mgr.getChecker(); Checker->Check_CallAndMessageUnInitRefArg = true; - Checker->CheckName_CallAndMessageUnInitRefArg = mgr.getCurrentCheckName(); + Checker->CheckName_CallAndMessageUnInitRefArg = mgr.getCurrentCheckerName(); } bool ento::shouldRegisterCallAndMessageUnInitRefArg(const LangOptions &LO) { diff --git a/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp index 05ece961467fb..9d823574c9f55 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CastSizeChecker.cpp @@ -132,7 +132,8 @@ void CastSizeChecker::checkPreStmt(const CastExpr *CE,CheckerContext &C) const { BT.reset(new BuiltinBug(this, "Cast region with wrong size.", "Cast a region whose size is not a multiple" " of the destination type size.")); - auto R = llvm::make_unique(*BT, BT->getDescription(), errorNode); + auto R = llvm::make_unique(*BT, BT->getDescription(), + errorNode); R->addRange(CE->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp new file mode 100644 index 0000000000000..cc1c9a66b90e2 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/CastValueChecker.cpp @@ -0,0 +1,441 @@ +//===- CastValueChecker - Model implementation of custom RTTIs --*- 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 +// +//===----------------------------------------------------------------------===// +// +// This defines CastValueChecker which models casts of custom RTTIs. +// +// TODO list: +// - It only allows one succesful cast between two types however in the wild +// the object could be casted to multiple types. +// - It needs to check the most likely type information from the dynamic type +// map to increase precision of dynamic casting. +// +//===----------------------------------------------------------------------===// + +#include "clang/AST/DeclTemplate.h" +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "llvm/ADT/Optional.h" +#include + +using namespace clang; +using namespace ento; + +namespace { +class CastValueChecker : public Checker { + enum class CallKind { Function, Method, InstanceOf }; + + using CastCheck = + std::function; + +public: + // We have five cases to evaluate a cast: + // 1) The parameter is non-null, the return value is non-null. + // 2) The parameter is non-null, the return value is null. + // 3) The parameter is null, the return value is null. + // cast: 1; dyn_cast: 1, 2; cast_or_null: 1, 3; dyn_cast_or_null: 1, 2, 3. + // + // 4) castAs: Has no parameter, the return value is non-null. + // 5) getAs: Has no parameter, the return value is null or non-null. + // + // We have two cases to check the parameter is an instance of the given type. + // 1) isa: The parameter is non-null, returns boolean. + // 2) isa_and_nonnull: The parameter is null or non-null, returns boolean. + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + +private: + // These are known in the LLVM project. The pairs are in the following form: + // {{{namespace, call}, argument-count}, {callback, kind}} + const CallDescriptionMap> CDM = { + {{{"llvm", "cast"}, 1}, + {&CastValueChecker::evalCast, CallKind::Function}}, + {{{"llvm", "dyn_cast"}, 1}, + {&CastValueChecker::evalDynCast, CallKind::Function}}, + {{{"llvm", "cast_or_null"}, 1}, + {&CastValueChecker::evalCastOrNull, CallKind::Function}}, + {{{"llvm", "dyn_cast_or_null"}, 1}, + {&CastValueChecker::evalDynCastOrNull, CallKind::Function}}, + {{{"clang", "castAs"}, 0}, + {&CastValueChecker::evalCastAs, CallKind::Method}}, + {{{"clang", "getAs"}, 0}, + {&CastValueChecker::evalGetAs, CallKind::Method}}, + {{{"llvm", "isa"}, 1}, + {&CastValueChecker::evalIsa, CallKind::InstanceOf}}, + {{{"llvm", "isa_and_nonnull"}, 1}, + {&CastValueChecker::evalIsaAndNonNull, CallKind::InstanceOf}}}; + + void evalCast(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalDynCast(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalCastOrNull(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalDynCastOrNull(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalCastAs(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalGetAs(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalIsa(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; + void evalIsaAndNonNull(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const; +}; +} // namespace + +static bool isInfeasibleCast(const DynamicCastInfo *CastInfo, + bool CastSucceeds) { + if (!CastInfo) + return false; + + return CastSucceeds ? CastInfo->fails() : CastInfo->succeeds(); +} + +static const NoteTag *getNoteTag(CheckerContext &C, + const DynamicCastInfo *CastInfo, + QualType CastToTy, const Expr *Object, + bool CastSucceeds, bool IsKnownCast) { + std::string CastToName = + CastInfo ? CastInfo->to()->getPointeeCXXRecordDecl()->getNameAsString() + : CastToTy->getPointeeCXXRecordDecl()->getNameAsString(); + Object = Object->IgnoreParenImpCasts(); + + return C.getNoteTag( + [=]() -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + if (!IsKnownCast) + Out << "Assuming "; + + if (const auto *DRE = dyn_cast(Object)) { + Out << '\'' << DRE->getDecl()->getNameAsString() << '\''; + } else if (const auto *ME = dyn_cast(Object)) { + Out << (IsKnownCast ? "Field '" : "field '") + << ME->getMemberDecl()->getNameAsString() << '\''; + } else { + Out << (IsKnownCast ? "The object" : "the object"); + } + + Out << ' ' << (CastSucceeds ? "is a" : "is not a") << " '" << CastToName + << '\''; + + return Out.str(); + }, + /*IsPrunable=*/true); +} + +//===----------------------------------------------------------------------===// +// Main logic to evaluate a cast. +//===----------------------------------------------------------------------===// + +static QualType alignReferenceTypes(QualType toAlign, QualType alignTowards, + ASTContext &ACtx) { + if (alignTowards->isLValueReferenceType() && + alignTowards.isConstQualified()) { + toAlign.addConst(); + return ACtx.getLValueReferenceType(toAlign); + } else if (alignTowards->isLValueReferenceType()) + return ACtx.getLValueReferenceType(toAlign); + else if (alignTowards->isRValueReferenceType()) + return ACtx.getRValueReferenceType(toAlign); + + llvm_unreachable("Must align towards a reference type!"); +} + +static void addCastTransition(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C, bool IsNonNullParam, + bool IsNonNullReturn, + bool IsCheckedCast = false) { + ProgramStateRef State = C.getState()->assume(DV, IsNonNullParam); + if (!State) + return; + + const Expr *Object; + QualType CastFromTy; + QualType CastToTy = Call.getResultType(); + + if (Call.getNumArgs() > 0) { + Object = Call.getArgExpr(0); + CastFromTy = Call.parameters()[0]->getType(); + } else { + Object = cast(&Call)->getCXXThisExpr(); + CastFromTy = Object->getType(); + if (CastToTy->isPointerType()) { + if (!CastFromTy->isPointerType()) + return; + } else { + if (!CastFromTy->isReferenceType()) + return; + + CastFromTy = alignReferenceTypes(CastFromTy, CastToTy, C.getASTContext()); + } + } + + const MemRegion *MR = DV.getAsRegion(); + const DynamicCastInfo *CastInfo = + getDynamicCastInfo(State, MR, CastFromTy, CastToTy); + + // We assume that every checked cast succeeds. + bool CastSucceeds = IsCheckedCast || CastFromTy == CastToTy; + if (!CastSucceeds) { + if (CastInfo) + CastSucceeds = IsNonNullReturn && CastInfo->succeeds(); + else + CastSucceeds = IsNonNullReturn; + } + + // Check for infeasible casts. + if (isInfeasibleCast(CastInfo, CastSucceeds)) { + C.generateSink(State, C.getPredecessor()); + return; + } + + // Store the type and the cast information. + bool IsKnownCast = CastInfo || IsCheckedCast || CastFromTy == CastToTy; + if (!IsKnownCast || IsCheckedCast) + State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, + CastSucceeds); + + SVal V = CastSucceeds ? C.getSValBuilder().evalCast(DV, CastToTy, CastFromTy) + : C.getSValBuilder().makeNull(); + C.addTransition( + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), V, false), + getNoteTag(C, CastInfo, CastToTy, Object, CastSucceeds, IsKnownCast)); +} + +static void addInstanceOfTransition(const CallEvent &Call, + DefinedOrUnknownSVal DV, + ProgramStateRef State, CheckerContext &C, + bool IsInstanceOf) { + const FunctionDecl *FD = Call.getDecl()->getAsFunction(); + QualType CastFromTy = Call.parameters()[0]->getType(); + QualType CastToTy = FD->getTemplateSpecializationArgs()->get(0).getAsType(); + if (CastFromTy->isPointerType()) + CastToTy = C.getASTContext().getPointerType(CastToTy); + else if (CastFromTy->isReferenceType()) + CastToTy = alignReferenceTypes(CastToTy, CastFromTy, C.getASTContext()); + else + return; + + const MemRegion *MR = DV.getAsRegion(); + const DynamicCastInfo *CastInfo = + getDynamicCastInfo(State, MR, CastFromTy, CastToTy); + + bool CastSucceeds; + if (CastInfo) + CastSucceeds = IsInstanceOf && CastInfo->succeeds(); + else + CastSucceeds = IsInstanceOf || CastFromTy == CastToTy; + + if (isInfeasibleCast(CastInfo, CastSucceeds)) { + C.generateSink(State, C.getPredecessor()); + return; + } + + // Store the type and the cast information. + bool IsKnownCast = CastInfo || CastFromTy == CastToTy; + if (!IsKnownCast) + State = setDynamicTypeAndCastInfo(State, MR, CastFromTy, CastToTy, + IsInstanceOf); + + C.addTransition( + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + C.getSValBuilder().makeTruthVal(CastSucceeds)), + getNoteTag(C, CastInfo, CastToTy, Call.getArgExpr(0), CastSucceeds, + IsKnownCast)); +} + +//===----------------------------------------------------------------------===// +// Evaluating cast, dyn_cast, cast_or_null, dyn_cast_or_null. +//===----------------------------------------------------------------------===// + +static void evalNonNullParamNonNullReturn(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C, + bool IsCheckedCast = false) { + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/true, IsCheckedCast); +} + +static void evalNonNullParamNullReturn(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) { + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/false); +} + +static void evalNullParamNullReturn(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) { + if (ProgramStateRef State = C.getState()->assume(DV, false)) + C.addTransition(State->BindExpr(Call.getOriginExpr(), + C.getLocationContext(), + C.getSValBuilder().makeNull(), false), + C.getNoteTag("Assuming null pointer is passed into cast", + /*IsPrunable=*/true)); +} + +void CastValueChecker::evalCast(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(Call, DV, C, /*IsCheckedCast=*/true); +} + +void CastValueChecker::evalDynCast(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(Call, DV, C); + evalNonNullParamNullReturn(Call, DV, C); +} + +void CastValueChecker::evalCastOrNull(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(Call, DV, C); + evalNullParamNullReturn(Call, DV, C); +} + +void CastValueChecker::evalDynCastOrNull(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalNonNullParamNonNullReturn(Call, DV, C); + evalNonNullParamNullReturn(Call, DV, C); + evalNullParamNullReturn(Call, DV, C); +} + +//===----------------------------------------------------------------------===// +// Evaluating castAs, getAs. +//===----------------------------------------------------------------------===// + +static void evalZeroParamNonNullReturn(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C, + bool IsCheckedCast = false) { + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/true, IsCheckedCast); +} + +static void evalZeroParamNullReturn(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) { + addCastTransition(Call, DV, C, /*IsNonNullParam=*/true, + /*IsNonNullReturn=*/false); +} + +void CastValueChecker::evalCastAs(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalZeroParamNonNullReturn(Call, DV, C, /*IsCheckedCast=*/true); +} + +void CastValueChecker::evalGetAs(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + evalZeroParamNonNullReturn(Call, DV, C); + evalZeroParamNullReturn(Call, DV, C); +} + +//===----------------------------------------------------------------------===// +// Evaluating isa, isa_and_nonnull. +//===----------------------------------------------------------------------===// + +void CastValueChecker::evalIsa(const CallEvent &Call, DefinedOrUnknownSVal DV, + CheckerContext &C) const { + ProgramStateRef NonNullState, NullState; + std::tie(NonNullState, NullState) = C.getState()->assume(DV); + + if (NonNullState) { + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/true); + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/false); + } + + if (NullState) { + C.generateSink(NullState, C.getPredecessor()); + } +} + +void CastValueChecker::evalIsaAndNonNull(const CallEvent &Call, + DefinedOrUnknownSVal DV, + CheckerContext &C) const { + ProgramStateRef NonNullState, NullState; + std::tie(NonNullState, NullState) = C.getState()->assume(DV); + + if (NonNullState) { + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/true); + addInstanceOfTransition(Call, DV, NonNullState, C, /*IsInstanceOf=*/false); + } + + if (NullState) { + addInstanceOfTransition(Call, DV, NullState, C, /*IsInstanceOf=*/false); + } +} + +//===----------------------------------------------------------------------===// +// Main logic to evaluate a call. +//===----------------------------------------------------------------------===// + +bool CastValueChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { + const auto *Lookup = CDM.lookup(Call); + if (!Lookup) + return false; + + const CastCheck &Check = Lookup->first; + CallKind Kind = Lookup->second; + + Optional DV; + + switch (Kind) { + case CallKind::Function: { + // We only model casts from pointers to pointers or from references + // to references. Other casts are most likely specialized and we + // cannot model them. + QualType ParamT = Call.parameters()[0]->getType(); + QualType ResultT = Call.getResultType(); + if (!(ParamT->isPointerType() && ResultT->isPointerType()) && + !(ParamT->isReferenceType() && ResultT->isReferenceType())) + return false; + + DV = Call.getArgSVal(0).getAs(); + break; + } + case CallKind::InstanceOf: { + // We need to obtain the only template argument to determinte the type. + const FunctionDecl *FD = Call.getDecl()->getAsFunction(); + if (!FD || !FD->getTemplateSpecializationArgs()) + return false; + + DV = Call.getArgSVal(0).getAs(); + break; + } + case CallKind::Method: + const auto *InstanceCall = dyn_cast(&Call); + if (!InstanceCall) + return false; + + DV = InstanceCall->getCXXThisVal().getAs(); + break; + } + + if (!DV) + return false; + + Check(this, Call, *DV, C); + return true; +} + +void ento::registerCastValueChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterCastValueChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp index a7ca814c8f967..ed14cdd06206b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckObjCDealloc.cpp @@ -28,6 +28,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -36,7 +37,6 @@ #include "clang/Basic/TargetInfo.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -576,9 +576,8 @@ void ObjCDeallocChecker::diagnoseMissingReleases(CheckerContext &C) const { OS << " by a synthesized property but not released" " before '[super dealloc]'"; - std::unique_ptr BR( - new BugReport(*MissingReleaseBugType, OS.str(), ErrNode)); - + auto BR = llvm::make_unique(*MissingReleaseBugType, + OS.str(), ErrNode); C.emitReport(std::move(BR)); } @@ -699,8 +698,8 @@ bool ObjCDeallocChecker::diagnoseExtraRelease(SymbolRef ReleasedValue, OS << " property but was released in 'dealloc'"; } - std::unique_ptr BR( - new BugReport(*ExtraReleaseBugType, OS.str(), ErrNode)); + auto BR = llvm::make_unique(*ExtraReleaseBugType, + OS.str(), ErrNode); BR->addRange(M.getOriginExpr()->getSourceRange()); C.emitReport(std::move(BR)); @@ -741,8 +740,8 @@ bool ObjCDeallocChecker::diagnoseMistakenDealloc(SymbolRef DeallocedValue, OS << "'" << *PropImpl->getPropertyIvarDecl() << "' should be released rather than deallocated"; - std::unique_ptr BR( - new BugReport(*MistakenDeallocBugType, OS.str(), ErrNode)); + auto BR = llvm::make_unique(*MistakenDeallocBugType, + OS.str(), ErrNode); BR->addRange(M.getOriginExpr()->getSourceRange()); C.emitReport(std::move(BR)); diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp index a020d33bfd95f..1694c237cda42 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckObjCInstMethSignature.cpp @@ -13,11 +13,11 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/ASTContext.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Type.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "llvm/ADT/DenseMap.h" #include "llvm/Support/raw_ostream.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp index 3f1c213a56478..260a2896e78c8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CheckSecuritySyntaxOnly.cpp @@ -50,19 +50,19 @@ struct ChecksFilter { DefaultBool check_FloatLoopCounter; DefaultBool check_UncheckedReturn; - CheckName checkName_bcmp; - CheckName checkName_bcopy; - CheckName checkName_bzero; - CheckName checkName_gets; - CheckName checkName_getpw; - CheckName checkName_mktemp; - CheckName checkName_mkstemp; - CheckName checkName_strcpy; - CheckName checkName_DeprecatedOrUnsafeBufferHandling; - CheckName checkName_rand; - CheckName checkName_vfork; - CheckName checkName_FloatLoopCounter; - CheckName checkName_UncheckedReturn; + CheckerNameRef checkName_bcmp; + CheckerNameRef checkName_bcopy; + CheckerNameRef checkName_bzero; + CheckerNameRef checkName_gets; + CheckerNameRef checkName_getpw; + CheckerNameRef checkName_mktemp; + CheckerNameRef checkName_mkstemp; + CheckerNameRef checkName_strcpy; + CheckerNameRef checkName_DeprecatedOrUnsafeBufferHandling; + CheckerNameRef checkName_rand; + CheckerNameRef checkName_vfork; + CheckerNameRef checkName_FloatLoopCounter; + CheckerNameRef checkName_UncheckedReturn; }; class WalkAST : public StmtVisitor { @@ -204,6 +204,8 @@ void WalkAST::VisitForStmt(ForStmt *FS) { // Implements: CERT security coding advisory FLP-30. //===----------------------------------------------------------------------===// +// Returns either 'x' or 'y', depending on which one of them is incremented +// in 'expr', or nullptr if none of them is incremented. static const DeclRefExpr* getIncrementedVar(const Expr *expr, const VarDecl *x, const VarDecl *y) { expr = expr->IgnoreParenCasts(); @@ -289,14 +291,15 @@ void WalkAST::checkLoopConditionForFloat(const ForStmt *FS) { // Does either variable appear in increment? const DeclRefExpr *drInc = getIncrementedVar(increment, vdLHS, vdRHS); - if (!drInc) return; + const VarDecl *vdInc = cast(drInc->getDecl()); + assert(vdInc && (vdInc == vdLHS || vdInc == vdRHS)); + // Emit the error. First figure out which DeclRefExpr in the condition // referenced the compared variable. - assert(drInc->getDecl()); - const DeclRefExpr *drCond = vdLHS == drInc->getDecl() ? drLHS : drRHS; + const DeclRefExpr *drCond = vdLHS == vdInc ? drLHS : drRHS; SmallVector ranges; SmallString<256> sbuf; @@ -1012,14 +1015,12 @@ bool ento::shouldRegisterSecuritySyntaxChecker(const LangOptions &LO) { #define REGISTER_CHECKER(name) \ void ento::register##name(CheckerManager &mgr) { \ - SecuritySyntaxChecker *checker = mgr.getChecker(); \ + SecuritySyntaxChecker *checker = mgr.getChecker(); \ checker->filter.check_##name = true; \ - checker->filter.checkName_##name = mgr.getCurrentCheckName(); \ + checker->filter.checkName_##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(bcmp) REGISTER_CHECKER(bcopy) diff --git a/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp index cbc5b32931b6d..0dc775f8df28e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ChrootChecker.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -37,53 +38,44 @@ bool isRootChanged(intptr_t k) { return k == ROOT_CHANGED; } // ROOT_CHANGED<--chdir(..)-- JAIL_ENTERED<--chdir(..)-- // | | // bug<--foo()-- JAIL_ENTERED<--foo()-- -class ChrootChecker : public Checker > { - mutable IdentifierInfo *II_chroot, *II_chdir; +class ChrootChecker : public Checker { // This bug refers to possibly break out of a chroot() jail. mutable std::unique_ptr BT_BreakJail; + const CallDescription Chroot{"chroot", 1}, Chdir{"chdir", 1}; + public: - ChrootChecker() : II_chroot(nullptr), II_chdir(nullptr) {} + ChrootChecker() {} static void *getTag() { static int x; return &x; } - bool evalCall(const CallExpr *CE, CheckerContext &C) const; - void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; + void checkPreCall(const CallEvent &Call, CheckerContext &C) const; private: - void Chroot(CheckerContext &C, const CallExpr *CE) const; - void Chdir(CheckerContext &C, const CallExpr *CE) const; + void evalChroot(const CallEvent &Call, CheckerContext &C) const; + void evalChdir(const CallEvent &Call, CheckerContext &C) const; }; } // end anonymous namespace -bool ChrootChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) - return false; - - ASTContext &Ctx = C.getASTContext(); - if (!II_chroot) - II_chroot = &Ctx.Idents.get("chroot"); - if (!II_chdir) - II_chdir = &Ctx.Idents.get("chdir"); - - if (FD->getIdentifier() == II_chroot) { - Chroot(C, CE); +bool ChrootChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { + if (Call.isCalled(Chroot)) { + evalChroot(Call, C); return true; } - if (FD->getIdentifier() == II_chdir) { - Chdir(C, CE); + if (Call.isCalled(Chdir)) { + evalChdir(Call, C); return true; } return false; } -void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { +void ChrootChecker::evalChroot(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); ProgramStateManager &Mgr = state->getStateManager(); @@ -93,7 +85,7 @@ void ChrootChecker::Chroot(CheckerContext &C, const CallExpr *CE) const { C.addTransition(state); } -void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { +void ChrootChecker::evalChdir(const CallEvent &Call, CheckerContext &C) const { ProgramStateRef state = C.getState(); ProgramStateManager &Mgr = state->getStateManager(); @@ -103,7 +95,7 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { return; // After chdir("/"), enter the jail, set the enum value JAIL_ENTERED. - const Expr *ArgExpr = CE->getArg(0); + const Expr *ArgExpr = Call.getArgExpr(0); SVal ArgVal = C.getSVal(ArgExpr); if (const MemRegion *R = ArgVal.getAsRegion()) { @@ -120,19 +112,10 @@ void ChrootChecker::Chdir(CheckerContext &C, const CallExpr *CE) const { } // Check the jail state before any function call except chroot and chdir(). -void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { - const FunctionDecl *FD = C.getCalleeDecl(CE); - if (!FD) - return; - - ASTContext &Ctx = C.getASTContext(); - if (!II_chroot) - II_chroot = &Ctx.Idents.get("chroot"); - if (!II_chdir) - II_chdir = &Ctx.Idents.get("chdir"); - +void ChrootChecker::checkPreCall(const CallEvent &Call, + CheckerContext &C) const { // Ignore chroot and chdir. - if (FD->getIdentifier() == II_chroot || FD->getIdentifier() == II_chdir) + if (Call.isCalled(Chroot) || Call.isCalled(Chdir)) return; // If jail state is ROOT_CHANGED, generate BugReport. @@ -144,7 +127,7 @@ void ChrootChecker::checkPreStmt(const CallExpr *CE, CheckerContext &C) const { BT_BreakJail.reset(new BuiltinBug( this, "Break out of jail", "No call of chdir(\"/\") immediately " "after chroot")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_BreakJail, BT_BreakJail->getDescription(), N)); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp index 4fc225056d4c7..ebba31e4007e3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/CloneChecker.cpp @@ -114,8 +114,8 @@ void CloneChecker::reportClones( for (const CloneDetector::CloneGroup &Group : CloneGroups) { // We group the clones by printing the first as a warning and all others // as a note. - auto R = llvm::make_unique(*BT_Exact, "Duplicate code detected", - makeLocation(Group.front(), Mgr)); + auto R = llvm::make_unique( + *BT_Exact, "Duplicate code detected", makeLocation(Group.front(), Mgr)); R->addRange(Group.front().getSourceRange()); for (unsigned i = 1; i < Group.size(); ++i) @@ -169,7 +169,7 @@ void CloneChecker::reportSuspiciousClones( // which may confuse the user. // Think how to perform more accurate suggestions? - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_Suspicious, "Potential copy-paste error; did you really mean to use '" + Pair.FirstCloneInfo.Variable->getNameAsString() + "' here?", diff --git a/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp index 5058d101b8e59..fd2417cfe3c0b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ConversionChecker.cpp @@ -121,7 +121,7 @@ void ConversionChecker::reportBug(ExplodedNode *N, CheckerContext &C, new BuiltinBug(this, "Conversion", "Possible loss of sign/precision.")); // Generate a report for this bug. - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp index e316c9120b28e..61441889fc649 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeadStoresChecker.cpp @@ -11,6 +11,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Attr.h" @@ -119,11 +120,20 @@ LookThroughTransitiveAssignmentsAndCommaOperators(const Expr *Ex) { } namespace { +class DeadStoresChecker : public Checker { +public: + bool ShowFixIts = false; + bool WarnForDeadNestedAssignments = true; + + void checkASTCodeBody(const Decl *D, AnalysisManager &Mgr, + BugReporter &BR) const; +}; + class DeadStoreObs : public LiveVariables::Observer { const CFG &cfg; ASTContext &Ctx; BugReporter& BR; - const CheckerBase *Checker; + const DeadStoresChecker *Checker; AnalysisDeclContext* AC; ParentMap& Parents; llvm::SmallPtrSet Escaped; @@ -135,9 +145,10 @@ class DeadStoreObs : public LiveVariables::Observer { public: DeadStoreObs(const CFG &cfg, ASTContext &ctx, BugReporter &br, - const CheckerBase *checker, AnalysisDeclContext *ac, + const DeadStoresChecker *checker, AnalysisDeclContext *ac, ParentMap &parents, - llvm::SmallPtrSet &escaped) + llvm::SmallPtrSet &escaped, + bool warnForDeadNestedAssignments) : cfg(cfg), Ctx(ctx), BR(br), Checker(checker), AC(ac), Parents(parents), Escaped(escaped), currentBlock(nullptr) {} @@ -160,6 +171,26 @@ class DeadStoreObs : public LiveVariables::Observer { return InEH->count(D); } + bool isSuppressed(SourceRange R) { + SourceManager &SMgr = Ctx.getSourceManager(); + SourceLocation Loc = R.getBegin(); + if (!Loc.isValid()) + return false; + + FileID FID = SMgr.getFileID(Loc); + bool Invalid = false; + StringRef Data = SMgr.getBufferData(FID, &Invalid); + if (Invalid) + return false; + + // Files autogenerated by DriverKit IIG contain some dead stores that + // we don't want to report. + if (Data.startswith("/* iig")) + return true; + + return false; + } + void Report(const VarDecl *V, DeadStoreKind dsk, PathDiagnosticLocation L, SourceRange R) { if (Escaped.count(V)) @@ -175,16 +206,39 @@ class DeadStoreObs : public LiveVariables::Observer { if (!reachableCode->isReachable(currentBlock)) return; + if (isSuppressed(R)) + return; + SmallString<64> buf; llvm::raw_svector_ostream os(buf); const char *BugType = nullptr; + SmallVector Fixits; + switch (dsk) { - case DeadInit: + case DeadInit: { BugType = "Dead initialization"; os << "Value stored to '" << *V << "' during its initialization is never read"; + + ASTContext &ACtx = V->getASTContext(); + if (Checker->ShowFixIts) { + if (V->getInit()->HasSideEffects(ACtx, + /*IncludePossibleEffects=*/true)) { + break; + } + SourceManager &SM = ACtx.getSourceManager(); + const LangOptions &LO = ACtx.getLangOpts(); + SourceLocation L1 = + Lexer::findNextToken( + V->getTypeSourceInfo()->getTypeLoc().getEndLoc(), + SM, LO)->getEndLoc(); + SourceLocation L2 = + Lexer::getLocForEndOfToken(V->getInit()->getEndLoc(), 1, SM, LO); + Fixits.push_back(FixItHint::CreateRemoval({L1, L2})); + } break; + } case DeadIncrement: BugType = "Dead increment"; @@ -194,15 +248,20 @@ class DeadStoreObs : public LiveVariables::Observer { os << "Value stored to '" << *V << "' is never read"; break; + // eg.: f((x = foo())) case Enclosing: - // Don't report issues in this case, e.g.: "if (x = foo())", - // where 'x' is unused later. We have yet to see a case where - // this is a real bug. - return; + if (!Checker->WarnForDeadNestedAssignments) + return; + BugType = "Dead nested assignment"; + os << "Although the value stored to '" << *V + << "' is used in the enclosing expression, the value is never " + "actually read from '" + << *V << "'"; + break; } BR.EmitBasicReport(AC->getDecl(), Checker, BugType, "Dead store", os.str(), - L, R); + L, R, Fixits); } void CheckVarDecl(const VarDecl *VD, const Expr *Ex, const Expr *Val, @@ -448,35 +507,37 @@ class FindEscaped { // DeadStoresChecker //===----------------------------------------------------------------------===// -namespace { -class DeadStoresChecker : public Checker { -public: - void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, - BugReporter &BR) const { - - // Don't do anything for template instantiations. - // Proving that code in a template instantiation is "dead" - // means proving that it is dead in all instantiations. - // This same problem exists with -Wunreachable-code. - if (const FunctionDecl *FD = dyn_cast(D)) - if (FD->isTemplateInstantiation()) - return; +void DeadStoresChecker::checkASTCodeBody(const Decl *D, AnalysisManager &mgr, + BugReporter &BR) const { - if (LiveVariables *L = mgr.getAnalysis(D)) { - CFG &cfg = *mgr.getCFG(D); - AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D); - ParentMap &pmap = mgr.getParentMap(D); - FindEscaped FS; - cfg.VisitBlockStmts(FS); - DeadStoreObs A(cfg, BR.getContext(), BR, this, AC, pmap, FS.Escaped); - L->runOnAllBlocks(A); - } + // Don't do anything for template instantiations. + // Proving that code in a template instantiation is "dead" + // means proving that it is dead in all instantiations. + // This same problem exists with -Wunreachable-code. + if (const FunctionDecl *FD = dyn_cast(D)) + if (FD->isTemplateInstantiation()) + return; + + if (LiveVariables *L = mgr.getAnalysis(D)) { + CFG &cfg = *mgr.getCFG(D); + AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D); + ParentMap &pmap = mgr.getParentMap(D); + FindEscaped FS; + cfg.VisitBlockStmts(FS); + DeadStoreObs A(cfg, BR.getContext(), BR, this, AC, pmap, FS.Escaped, + WarnForDeadNestedAssignments); + L->runOnAllBlocks(A); } -}; } -void ento::registerDeadStoresChecker(CheckerManager &mgr) { - mgr.registerChecker(); +void ento::registerDeadStoresChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.registerChecker(); + + const AnalyzerOptions &AnOpts = Mgr.getAnalyzerOptions(); + Chk->WarnForDeadNestedAssignments = + AnOpts.getCheckerBooleanOption(Chk, "WarnForDeadNestedAssignments"); + Chk->ShowFixIts = + AnOpts.getCheckerBooleanOption(Chk, "ShowFixIts"); } bool ento::shouldRegisterDeadStoresChecker(const LangOptions &LO) { diff --git a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp index 63215e6bd3b4d..ceb638fac95bd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DebugCheckers.cpp @@ -35,9 +35,9 @@ class DominatorsTreeDumper : public Checker { void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, BugReporter &BR) const { if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { - DominatorTree dom; - dom.buildDominatorTree(*AC); - dom.dump(); + CFGDomTree Dom; + Dom.buildDominatorTree(AC->getCFG()); + Dom.dump(); } } }; @@ -51,6 +51,57 @@ bool ento::shouldRegisterDominatorsTreeDumper(const LangOptions &LO) { return true; } +//===----------------------------------------------------------------------===// +// PostDominatorsTreeDumper +//===----------------------------------------------------------------------===// + +namespace { +class PostDominatorsTreeDumper : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { + CFGPostDomTree Dom; + Dom.buildDominatorTree(AC->getCFG()); + Dom.dump(); + } + } +}; +} + +void ento::registerPostDominatorsTreeDumper(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterPostDominatorsTreeDumper(const LangOptions &LO) { + return true; +} + +//===----------------------------------------------------------------------===// +// ControlDependencyTreeDumper +//===----------------------------------------------------------------------===// + +namespace { +class ControlDependencyTreeDumper : public Checker { +public: + void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, + BugReporter &BR) const { + if (AnalysisDeclContext *AC = mgr.getAnalysisDeclContext(D)) { + ControlDependencyCalculator Dom(AC->getCFG()); + Dom.dump(); + } + } +}; +} + +void ento::registerControlDependencyTreeDumper(CheckerManager &mgr) { + mgr.registerChecker(); +} + +bool ento::shouldRegisterControlDependencyTreeDumper(const LangOptions &LO) { + return true; +} + //===----------------------------------------------------------------------===// // LiveVariablesDumper //===----------------------------------------------------------------------===// @@ -282,7 +333,8 @@ class ReportStmts : public Checker> { if (!Node) return; - auto Report = llvm::make_unique(BT_stmtLoc, "Statement", Node); + auto Report = + llvm::make_unique(BT_stmtLoc, "Statement", Node); C.emitReport(std::move(Report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp index 8bf77c109f8a6..1ddafd7268d9c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DeleteWithNonVirtualDtorChecker.cpp @@ -27,7 +27,7 @@ #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; @@ -45,9 +45,9 @@ class DeleteWithNonVirtualDtorChecker static int X = 0; ID.AddPointer(&X); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: bool Satisfied; @@ -92,7 +92,8 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, "Logic error")); ExplodedNode *N = C.generateNonFatalErrorNode(); - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); // Mark region of problematic base class for later use in the BugVisitor. R->markInteresting(BaseClassRegion); @@ -100,15 +101,15 @@ void DeleteWithNonVirtualDtorChecker::checkPreStmt(const CXXDeleteExpr *DE, C.emitReport(std::move(R)); } -std::shared_ptr +PathDiagnosticPieceRef DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { + PathSensitiveBugReport &BR) { // Stop traversal after the first conversion was found on a path. if (Satisfied) return nullptr; - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; @@ -140,8 +141,7 @@ DeleteWithNonVirtualDtorChecker::DeleteBugVisitor::VisitNode( OS << "Conversion from derived to base happened here"; PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } void ento::registerDeleteWithNonVirtualDtorChecker(CheckerManager &mgr) { diff --git a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp index 2c264833f2a9f..4b7d3998bca76 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DereferenceChecker.cpp @@ -179,7 +179,7 @@ void DereferenceChecker::reportBug(ProgramStateRef State, const Stmt *S, break; } - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_null, buf.empty() ? BT_null->getDescription() : StringRef(buf), N); bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); @@ -200,8 +200,8 @@ void DereferenceChecker::checkLocation(SVal l, bool isLoad, const Stmt* S, BT_undef.reset( new BuiltinBug(this, "Dereference of undefined pointer value")); - auto report = - llvm::make_unique(*BT_undef, BT_undef->getDescription(), N); + auto report = llvm::make_unique( + *BT_undef, BT_undef->getDescription(), N); bugreporter::trackExpressionValue(N, bugreporter::getDerefExpr(S), *report); C.emitReport(std::move(report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp index 33e8fcd8af7bb..e153cbc60680a 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DivZeroChecker.cpp @@ -47,7 +47,7 @@ void DivZeroChecker::reportBug( if (!BT) BT.reset(new BuiltinBug(this, "Division by zero")); - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); R->addVisitor(std::move(Visitor)); bugreporter::trackExpressionValue(N, getDenomExpr(N), *R); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp index 4d979dc9f2400..580579f2cbc35 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypeChecker.cpp @@ -21,7 +21,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -47,9 +47,9 @@ class DynamicTypeChecker : public Checker> { ID.AddPointer(Reg); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: // The tracked region. @@ -80,18 +80,16 @@ void DynamicTypeChecker::reportTypeError(QualType DynamicType, QualType::print(StaticType.getTypePtr(), Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); OS << "'"; - std::unique_ptr R( - new BugReport(*BT, OS.str(), C.generateNonFatalErrorNode())); + auto R = llvm::make_unique( + *BT, OS.str(), C.generateNonFatalErrorNode()); R->markInteresting(Reg); R->addVisitor(llvm::make_unique(Reg)); R->addRange(ReportedNode->getSourceRange()); C.emitReport(std::move(R)); } -std::shared_ptr -DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &) { +PathDiagnosticPieceRef DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); @@ -105,7 +103,7 @@ DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode(const ExplodedNode *N, return nullptr; // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; @@ -141,8 +139,7 @@ DynamicTypeChecker::DynamicTypeBugVisitor::VisitNode(const ExplodedNode *N, // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } static bool hasDefinition(const ObjCObjectPointerType *ObjPtr) { diff --git a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp index aede4e778a0ab..cfac1e0da1769 100644 --- a/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/DynamicTypePropagation.cpp @@ -20,16 +20,16 @@ // //===----------------------------------------------------------------------===// -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" #include "clang/AST/ParentMap.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/Basic/Builtins.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/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" using namespace clang; @@ -83,9 +83,9 @@ class DynamicTypePropagation: ID.AddPointer(Sym); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: // The tracked symbol. @@ -113,14 +113,7 @@ class DynamicTypePropagation: void DynamicTypePropagation::checkDeadSymbols(SymbolReaper &SR, CheckerContext &C) const { - ProgramStateRef State = C.getState(); - DynamicTypeMapTy TypeMap = State->get(); - for (DynamicTypeMapTy::iterator I = TypeMap.begin(), E = TypeMap.end(); - I != E; ++I) { - if (!SR.isLiveRegion(I->first)) { - State = State->remove(I->first); - } - } + ProgramStateRef State = removeDeadTypes(C.getState(), SR); MostSpecializedTypeArgsMapTy TyArgMap = State->get(); @@ -401,11 +394,11 @@ static const ObjCObjectPointerType *getMostInformativeDerivedClassImpl( } const auto *SuperOfTo = - To->getObjectType()->getSuperClassType()->getAs(); + To->getObjectType()->getSuperClassType()->castAs(); assert(SuperOfTo); QualType SuperPtrOfToQual = C.getObjCObjectPointerType(QualType(SuperOfTo, 0)); - const auto *SuperPtrOfTo = SuperPtrOfToQual->getAs(); + const auto *SuperPtrOfTo = SuperPtrOfToQual->castAs(); if (To->isUnspecialized()) return getMostInformativeDerivedClassImpl(From, SuperPtrOfTo, SuperPtrOfTo, C); @@ -834,16 +827,15 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, if (MessageExpr->getReceiverKind() == ObjCMessageExpr::Class && Sel.getAsString() == "class") { QualType ReceiverType = MessageExpr->getClassReceiver(); - const auto *ReceiverClassType = ReceiverType->getAs(); + const auto *ReceiverClassType = ReceiverType->castAs(); + if (!ReceiverClassType->isSpecialized()) + return; + QualType ReceiverClassPointerType = C.getASTContext().getObjCObjectPointerType( QualType(ReceiverClassType, 0)); - - if (!ReceiverClassType->isSpecialized()) - return; const auto *InferredType = - ReceiverClassPointerType->getAs(); - assert(InferredType); + ReceiverClassPointerType->castAs(); State = State->set(RetSym, InferredType); C.addTransition(State); @@ -882,7 +874,7 @@ void DynamicTypePropagation::checkPostObjCMessage(const ObjCMethodCall &M, // When there is an entry available for the return symbol in DynamicTypeMap, // the call was inlined, and the information in the DynamicTypeMap is should // be precise. - if (RetRegion && !State->get(RetRegion)) { + if (RetRegion && !getRawDynamicTypeInfo(State, RetRegion)) { // TODO: we have duplicated information in DynamicTypeMap and // MostSpecializedTypeArgsMap. We should only store anything in the later if // the stored data differs from the one stored in the former. @@ -919,8 +911,8 @@ void DynamicTypePropagation::reportGenericsBug( OS << "' to incompatible type '"; QualType::print(To, Qualifiers(), OS, C.getLangOpts(), llvm::Twine()); OS << "'"; - std::unique_ptr R( - new BugReport(*ObjCGenericsBugType, OS.str(), N)); + auto R = llvm::make_unique(*ObjCGenericsBugType, + OS.str(), N); R->markInteresting(Sym); R->addVisitor(llvm::make_unique(Sym)); if (ReportedNode) @@ -928,10 +920,9 @@ void DynamicTypePropagation::reportGenericsBug( C.emitReport(std::move(R)); } -std::shared_ptr -DynamicTypePropagation::GenericsBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef DynamicTypePropagation::GenericsBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = N->getFirstPred()->getState(); @@ -946,7 +937,7 @@ DynamicTypePropagation::GenericsBugVisitor::VisitNode(const ExplodedNode *N, return nullptr; // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; @@ -981,8 +972,7 @@ DynamicTypePropagation::GenericsBugVisitor::VisitNode(const ExplodedNode *N, // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } /// Register checkers. diff --git a/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp index 736d80ef9ec7e..79f49d064b9c7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/EnumCastOutOfRangeChecker.cpp @@ -83,7 +83,7 @@ void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C) const { new BuiltinBug(this, "Enum cast out of range", "The value provided to the cast expression is not in " "the valid range of values for the enum")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *EnumValueCastOutOfRange, EnumValueCastOutOfRange->getDescription(), N)); } @@ -91,6 +91,22 @@ void EnumCastOutOfRangeChecker::reportWarning(CheckerContext &C) const { void EnumCastOutOfRangeChecker::checkPreStmt(const CastExpr *CE, CheckerContext &C) const { + + // Only perform enum range check on casts where such checks are valid. For + // all other cast kinds (where enum range checks are unnecessary or invalid), + // just return immediately. TODO: The set of casts whitelisted for enum + // range checking may be incomplete. Better to add a missing cast kind to + // enable a missing check than to generate false negatives and have to remove + // those later. + switch (CE->getCastKind()) { + case CK_IntegralCast: + break; + + default: + return; + break; + } + // Get the value of the expression to cast. const llvm::Optional ValueToCast = C.getSVal(CE->getSubExpr()).getAs(); diff --git a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp index 7f715c9ba201f..3a771f783e756 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ExprInspectionChecker.cpp @@ -11,6 +11,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "llvm/ADT/StringSwitch.h" #include "llvm/Support/ScopedPrinter.h" @@ -53,7 +54,7 @@ class ExprInspectionChecker : public Checker(Call.getOriginExpr()); + if (!CE) + return false; + // These checks should have no effect on the surrounding environment // (globals should not be invalidated, etc), hence the use of evalCall. FnCheck Handler = llvm::StringSwitch(C.getCalleeName(CE)) @@ -143,7 +148,7 @@ ExplodedNode *ExprInspectionChecker::reportBug(llvm::StringRef Msg, if (!BT) BT.reset(new BugType(this, "Checking analyzer assumptions", "debug")); - BR.emitReport(llvm::make_unique(*BT, Msg, N)); + BR.emitReport(llvm::make_unique(*BT, Msg, N)); return N; } @@ -300,7 +305,7 @@ void ExprInspectionChecker::analyzerHashDump(const CallExpr *CE, const SourceManager &SM = C.getSourceManager(); FullSourceLoc FL(CE->getArg(0)->getBeginLoc(), SM); std::string HashContent = - GetIssueString(SM, FL, getCheckName().getName(), "Category", + GetIssueString(SM, FL, getCheckerName().getName(), "Category", C.getLocationContext()->getDecl(), Opts); reportBug(HashContent, C); diff --git a/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp index 94542be7dd7ca..92e3ff24610b6 100644 --- a/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/FixedAddressChecker.cpp @@ -55,7 +55,8 @@ void FixedAddressChecker::checkPreStmt(const BinaryOperator *B, "Using a fixed address is not portable because that " "address will probably not be valid in all " "environments or platforms.")); - auto R = llvm::make_unique(*BT, BT->getDescription(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); R->addRange(B->getRHS()->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp index d3ab980332365..3a004e2f7a675 100644 --- a/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/GenericTaintChecker.cpp @@ -15,16 +15,18 @@ //===----------------------------------------------------------------------===// #include "Taint.h" -#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "Yaml.h" #include "clang/AST/Attr.h" #include "clang/Basic/Builtins.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/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" -#include -#include +#include "llvm/ADT/StringMap.h" +#include "llvm/Support/YAMLTraits.h" +#include #include using namespace clang; @@ -44,14 +46,51 @@ class GenericTaintChecker void checkPreStmt(const CallExpr *CE, CheckerContext &C) const; - void printState(raw_ostream &Out, ProgramStateRef State, - const char *NL, const char *Sep) const override; + void printState(raw_ostream &Out, ProgramStateRef State, const char *NL, + const char *Sep) const override; -private: - static const unsigned InvalidArgIndex = UINT_MAX; + using ArgVector = SmallVector; + using SignedArgVector = SmallVector; + + enum class VariadicType { None, Src, Dst }; + + /// Used to parse the configuration file. + struct TaintConfiguration { + using NameArgsPair = std::pair; + + struct Propagation { + std::string Name; + ArgVector SrcArgs; + SignedArgVector DstArgs; + VariadicType VarType; + unsigned VarIndex; + }; + + std::vector Propagations; + std::vector Filters; + std::vector Sinks; + + TaintConfiguration() = default; + TaintConfiguration(const TaintConfiguration &) = default; + TaintConfiguration(TaintConfiguration &&) = default; + TaintConfiguration &operator=(const TaintConfiguration &) = default; + TaintConfiguration &operator=(TaintConfiguration &&) = default; + }; + + /// Convert SignedArgVector to ArgVector. + ArgVector convertToArgVector(CheckerManager &Mgr, const std::string &Option, + SignedArgVector Args); + + /// Parse the config. + void parseConfiguration(CheckerManager &Mgr, const std::string &Option, + TaintConfiguration &&Config); + + static const unsigned InvalidArgIndex{std::numeric_limits::max()}; /// Denotes the return vale. - static const unsigned ReturnValueIndex = UINT_MAX - 1; + static const unsigned ReturnValueIndex{std::numeric_limits::max() - + 1}; +private: mutable std::unique_ptr BT; void initBugType() const { if (!BT) @@ -76,28 +115,43 @@ class GenericTaintChecker static Optional getPointedToSVal(CheckerContext &C, const Expr *Arg); /// Check for CWE-134: Uncontrolled Format String. - static const char MsgUncontrolledFormatString[]; + static constexpr llvm::StringLiteral MsgUncontrolledFormatString = + "Untrusted data is used as a format string " + "(CWE-134: Uncontrolled Format String)"; bool checkUncontrolledFormatString(const CallExpr *CE, CheckerContext &C) const; /// Check for: /// CERT/STR02-C. "Sanitize data passed to complex subsystems" /// CWE-78, "Failure to Sanitize Data into an OS Command" - static const char MsgSanitizeSystemArgs[]; + static constexpr llvm::StringLiteral MsgSanitizeSystemArgs = + "Untrusted data is passed to a system call " + "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; bool checkSystemCall(const CallExpr *CE, StringRef Name, CheckerContext &C) const; /// Check if tainted data is used as a buffer size ins strn.. functions, /// and allocators. - static const char MsgTaintedBufferSize[]; + static constexpr llvm::StringLiteral MsgTaintedBufferSize = + "Untrusted data is used to specify the buffer size " + "(CERT/STR31-C. Guarantee that storage for strings has sufficient space " + "for character data and the null terminator)"; bool checkTaintedBufferSize(const CallExpr *CE, const FunctionDecl *FDecl, CheckerContext &C) const; + /// Check if tainted data is used as a custom sink's parameter. + static constexpr llvm::StringLiteral MsgCustomSink = + "Untrusted data is passed to a user-defined sink"; + bool checkCustomSinks(const CallExpr *CE, StringRef Name, + CheckerContext &C) const; + /// Generate a report if the expression is tainted or points to tainted data. - bool generateReportIfTainted(const Expr *E, const char Msg[], + bool generateReportIfTainted(const Expr *E, StringRef Msg, CheckerContext &C) const; - using ArgVector = SmallVector; + struct TaintPropagationRule; + using NameRuleMap = llvm::StringMap; + using NameArgMap = llvm::StringMap; /// A struct used to specify taint propagation rules for a function. /// @@ -109,8 +163,6 @@ class GenericTaintChecker /// ReturnValueIndex is added to the dst list, the return value will be /// tainted. struct TaintPropagationRule { - enum class VariadicType { None, Src, Dst }; - using PropagationFuncType = bool (*)(bool IsTainted, const CallExpr *, CheckerContext &C); @@ -131,8 +183,7 @@ class GenericTaintChecker : VariadicIndex(InvalidArgIndex), VarType(VariadicType::None), PropagationFunc(nullptr) {} - TaintPropagationRule(std::initializer_list &&Src, - std::initializer_list &&Dst, + TaintPropagationRule(ArgVector &&Src, ArgVector &&Dst, VariadicType Var = VariadicType::None, unsigned VarIndex = InvalidArgIndex, PropagationFuncType Func = nullptr) @@ -141,7 +192,8 @@ class GenericTaintChecker /// Get the propagation rule for a given function. static TaintPropagationRule - getTaintPropagationRule(const FunctionDecl *FDecl, StringRef Name, + getTaintPropagationRule(const NameRuleMap &CustomPropagations, + const FunctionDecl *FDecl, StringRef Name, CheckerContext &C); void addSrcArg(unsigned A) { SrcArgs.push_back(A); } @@ -176,25 +228,71 @@ class GenericTaintChecker static bool postSocket(bool IsTainted, const CallExpr *CE, CheckerContext &C); }; + + /// Defines a map between the propagation function's name and + /// TaintPropagationRule. + NameRuleMap CustomPropagations; + + /// Defines a map between the filter function's name and filtering args. + NameArgMap CustomFilters; + + /// Defines a map between the sink function's name and sinking args. + NameArgMap CustomSinks; }; const unsigned GenericTaintChecker::ReturnValueIndex; const unsigned GenericTaintChecker::InvalidArgIndex; -const char GenericTaintChecker::MsgUncontrolledFormatString[] = - "Untrusted data is used as a format string " - "(CWE-134: Uncontrolled Format String)"; +// FIXME: these lines can be removed in C++17 +constexpr llvm::StringLiteral GenericTaintChecker::MsgUncontrolledFormatString; +constexpr llvm::StringLiteral GenericTaintChecker::MsgSanitizeSystemArgs; +constexpr llvm::StringLiteral GenericTaintChecker::MsgTaintedBufferSize; +constexpr llvm::StringLiteral GenericTaintChecker::MsgCustomSink; +} // end of anonymous namespace -const char GenericTaintChecker::MsgSanitizeSystemArgs[] = - "Untrusted data is passed to a system call " - "(CERT/STR02-C. Sanitize data passed to complex subsystems)"; +using TaintConfig = GenericTaintChecker::TaintConfiguration; -const char GenericTaintChecker::MsgTaintedBufferSize[] = - "Untrusted data is used to specify the buffer size " - "(CERT/STR31-C. Guarantee that storage for strings has sufficient space " - "for character data and the null terminator)"; +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::Propagation) +LLVM_YAML_IS_SEQUENCE_VECTOR(TaintConfig::NameArgsPair) -} // end of anonymous namespace +namespace llvm { +namespace yaml { +template <> struct MappingTraits { + static void mapping(IO &IO, TaintConfig &Config) { + IO.mapOptional("Propagations", Config.Propagations); + IO.mapOptional("Filters", Config.Filters); + IO.mapOptional("Sinks", Config.Sinks); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TaintConfig::Propagation &Propagation) { + IO.mapRequired("Name", Propagation.Name); + IO.mapOptional("SrcArgs", Propagation.SrcArgs); + IO.mapOptional("DstArgs", Propagation.DstArgs); + IO.mapOptional("VariadicType", Propagation.VarType, + GenericTaintChecker::VariadicType::None); + IO.mapOptional("VariadicIndex", Propagation.VarIndex, + GenericTaintChecker::InvalidArgIndex); + } +}; + +template <> struct ScalarEnumerationTraits { + static void enumeration(IO &IO, GenericTaintChecker::VariadicType &Value) { + IO.enumCase(Value, "None", GenericTaintChecker::VariadicType::None); + IO.enumCase(Value, "Src", GenericTaintChecker::VariadicType::Src); + IO.enumCase(Value, "Dst", GenericTaintChecker::VariadicType::Dst); + } +}; + +template <> struct MappingTraits { + static void mapping(IO &IO, TaintConfig::NameArgsPair &NameArg) { + IO.mapRequired("Name", NameArg.first); + IO.mapRequired("Args", NameArg.second); + } +}; +} // namespace yaml +} // namespace llvm /// A set which is used to pass information from call pre-visit instruction /// to the call post-visit. The values are unsigned integers, which are either @@ -202,9 +300,46 @@ const char GenericTaintChecker::MsgTaintedBufferSize[] = /// points to data, which should be tainted on return. REGISTER_SET_WITH_PROGRAMSTATE(TaintArgsOnPostVisit, unsigned) +GenericTaintChecker::ArgVector GenericTaintChecker::convertToArgVector( + CheckerManager &Mgr, const std::string &Option, SignedArgVector Args) { + ArgVector Result; + for (int Arg : Args) { + if (Arg == -1) + Result.push_back(ReturnValueIndex); + else if (Arg < -1) { + Result.push_back(InvalidArgIndex); + Mgr.reportInvalidCheckerOptionValue( + this, Option, + "an argument number for propagation rules greater or equal to -1"); + } else + Result.push_back(static_cast(Arg)); + } + return Result; +} + +void GenericTaintChecker::parseConfiguration(CheckerManager &Mgr, + const std::string &Option, + TaintConfiguration &&Config) { + for (auto &P : Config.Propagations) { + GenericTaintChecker::CustomPropagations.try_emplace( + P.Name, std::move(P.SrcArgs), + convertToArgVector(Mgr, Option, P.DstArgs), P.VarType, P.VarIndex); + } + + for (auto &F : Config.Filters) { + GenericTaintChecker::CustomFilters.try_emplace(F.first, + std::move(F.second)); + } + + for (auto &S : Config.Sinks) { + GenericTaintChecker::CustomSinks.try_emplace(S.first, std::move(S.second)); + } +} + GenericTaintChecker::TaintPropagationRule GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( - const FunctionDecl *FDecl, StringRef Name, CheckerContext &C) { + const NameRuleMap &CustomPropagations, const FunctionDecl *FDecl, + StringRef Name, CheckerContext &C) { // TODO: Currently, we might lose precision here: we always mark a return // value as tainted even if it's just a pointer, pointing to tainted data. @@ -218,7 +353,8 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( .Case("freopen", TaintPropagationRule({}, {ReturnValueIndex})) .Case("getch", TaintPropagationRule({}, {ReturnValueIndex})) .Case("getchar", TaintPropagationRule({}, {ReturnValueIndex})) - .Case("getchar_unlocked", TaintPropagationRule({}, {ReturnValueIndex})) + .Case("getchar_unlocked", + TaintPropagationRule({}, {ReturnValueIndex})) .Case("getenv", TaintPropagationRule({}, {ReturnValueIndex})) .Case("gets", TaintPropagationRule({}, {0, ReturnValueIndex})) .Case("scanf", TaintPropagationRule({}, {}, VariadicType::Dst, 1)) @@ -297,6 +433,10 @@ GenericTaintChecker::TaintPropagationRule::getTaintPropagationRule( // or smart memory copy: // - memccpy - copying until hitting a special character. + auto It = CustomPropagations.find(Name); + if (It != CustomPropagations.end()) + return It->getValue(); + return TaintPropagationRule(); } @@ -336,8 +476,8 @@ void GenericTaintChecker::addSourcesPre(const CallExpr *CE, return; // First, try generating a propagation rule for this function. - TaintPropagationRule Rule = - TaintPropagationRule::getTaintPropagationRule(FDecl, Name, C); + TaintPropagationRule Rule = TaintPropagationRule::getTaintPropagationRule( + this->CustomPropagations, FDecl, Name, C); if (!Rule.isNull()) { State = Rule.process(CE, C); if (!State) @@ -409,6 +549,9 @@ bool GenericTaintChecker::checkPre(const CallExpr *CE, if (checkTaintedBufferSize(CE, FDecl, C)) return true; + if (checkCustomSinks(CE, Name, C)) + return true; + return false; } @@ -446,7 +589,8 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, bool IsTainted = true; for (unsigned ArgNum : SrcArgs) { if (ArgNum >= CE->getNumArgs()) - return State; + continue; + if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(ArgNum), State, C))) break; } @@ -454,7 +598,7 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, // Check for taint in variadic arguments. if (!IsTainted && VariadicType::Src == VarType) { // Check if any of the arguments is tainted - for (unsigned int i = VariadicIndex; i < CE->getNumArgs(); ++i) { + for (unsigned i = VariadicIndex; i < CE->getNumArgs(); ++i) { if ((IsTainted = isTaintedOrPointsToTainted(CE->getArg(i), State, C))) break; } @@ -474,8 +618,10 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, continue; } + if (ArgNum >= CE->getNumArgs()) + continue; + // Mark the given argument. - assert(ArgNum < CE->getNumArgs()); State = State->add(ArgNum); } @@ -485,7 +631,7 @@ GenericTaintChecker::TaintPropagationRule::process(const CallExpr *CE, // If they are not pointing to const data, mark data as tainted. // TODO: So far we are just going one level down; ideally we'd need to // recurse here. - for (unsigned int i = VariadicIndex; i < CE->getNumArgs(); ++i) { + for (unsigned i = VariadicIndex; i < CE->getNumArgs(); ++i) { const Expr *Arg = CE->getArg(i); // Process pointer argument. const Type *ArgTy = Arg->getType().getTypePtr(); @@ -550,7 +696,7 @@ bool GenericTaintChecker::isStdin(const Expr *E, CheckerContext &C) { static bool getPrintfFormatArgumentNum(const CallExpr *CE, const CheckerContext &C, - unsigned int &ArgNum) { + unsigned &ArgNum) { // Find if the function contains a format string argument. // Handles: fprintf, printf, sprintf, snprintf, vfprintf, vprintf, vsprintf, // vsnprintf, syslog, custom annotated functions. @@ -572,8 +718,7 @@ static bool getPrintfFormatArgumentNum(const CallExpr *CE, return false; } -bool GenericTaintChecker::generateReportIfTainted(const Expr *E, - const char Msg[], +bool GenericTaintChecker::generateReportIfTainted(const Expr *E, StringRef Msg, CheckerContext &C) const { assert(E); @@ -591,7 +736,7 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, // Generate diagnostic. if (ExplodedNode *N = C.generateNonFatalErrorNode()) { initBugType(); - auto report = llvm::make_unique(*BT, Msg, N); + auto report = llvm::make_unique(*BT, Msg, N); report->addRange(E->getSourceRange()); report->addVisitor(llvm::make_unique(TaintedSVal)); C.emitReport(std::move(report)); @@ -603,7 +748,7 @@ bool GenericTaintChecker::generateReportIfTainted(const Expr *E, bool GenericTaintChecker::checkUncontrolledFormatString( const CallExpr *CE, CheckerContext &C) const { // Check if the function contains a format string argument. - unsigned int ArgNum = 0; + unsigned ArgNum = 0; if (!getPrintfFormatArgumentNum(CE, C, ArgNum)) return false; @@ -629,9 +774,9 @@ bool GenericTaintChecker::checkSystemCall(const CallExpr *CE, StringRef Name, .Case("execvP", 0) .Case("execve", 0) .Case("dlopen", 0) - .Default(UINT_MAX); + .Default(InvalidArgIndex); - if (ArgNum == UINT_MAX || CE->getNumArgs() < (ArgNum + 1)) + if (ArgNum == InvalidArgIndex || CE->getNumArgs() < (ArgNum + 1)) return false; return generateReportIfTainted(CE->getArg(ArgNum), MsgSanitizeSystemArgs, C); @@ -676,8 +821,33 @@ bool GenericTaintChecker::checkTaintedBufferSize(const CallExpr *CE, generateReportIfTainted(CE->getArg(ArgNum), MsgTaintedBufferSize, C); } -void ento::registerGenericTaintChecker(CheckerManager &mgr) { - mgr.registerChecker(); +bool GenericTaintChecker::checkCustomSinks(const CallExpr *CE, StringRef Name, + CheckerContext &C) const { + auto It = CustomSinks.find(Name); + if (It == CustomSinks.end()) + return false; + + const GenericTaintChecker::ArgVector &Args = It->getValue(); + for (unsigned ArgNum : Args) { + if (ArgNum >= CE->getNumArgs()) + continue; + + if (generateReportIfTainted(CE->getArg(ArgNum), MsgCustomSink, C)) + return true; + } + + return false; +} + +void ento::registerGenericTaintChecker(CheckerManager &Mgr) { + auto *Checker = Mgr.registerChecker(); + std::string Option{"Config"}; + StringRef ConfigFile = + Mgr.getAnalyzerOptions().getCheckerStringOption(Checker, Option); + llvm::Optional Config = + getConfiguration(Mgr, Checker, Option, ConfigFile); + if (Config) + Checker->parseConfiguration(Mgr, Option, std::move(Config.getValue())); } bool ento::shouldRegisterGenericTaintChecker(const LangOptions &LO) { diff --git a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp index e3270f1f7be27..df9d7a92929a7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/InnerPointerChecker.cpp @@ -54,9 +54,9 @@ class InnerPointerChecker ID.AddPointer(getTag()); } - virtual std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + virtual PathDiagnosticPieceRef + VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; // FIXME: Scan the map once in the visitor's constructor and do a direct // lookup by region. @@ -278,15 +278,13 @@ const MemRegion *getContainerObjRegion(ProgramStateRef State, SymbolRef Sym) { } // end namespace ento } // end namespace clang -std::shared_ptr -InnerPointerChecker::InnerPointerBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &) { +PathDiagnosticPieceRef InnerPointerChecker::InnerPointerBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { if (!isSymbolTracked(N->getState(), PtrToBuf) || isSymbolTracked(N->getFirstPred()->getState(), PtrToBuf)) return nullptr; - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; @@ -301,8 +299,7 @@ InnerPointerChecker::InnerPointerBRVisitor::VisitNode(const ExplodedNode *N, << "' obtained here"; PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, OS.str(), true, - nullptr); + return std::make_shared(Pos, OS.str(), true); } void ento::registerInnerPointerChecker(CheckerManager &Mgr) { diff --git a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp index 6f1060b5f26d9..883c4871402c4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IteratorChecker.cpp @@ -71,7 +71,7 @@ #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include @@ -248,7 +248,7 @@ class IteratorChecker }; DefaultBool ChecksEnabled[CK_NumCheckKinds]; - CheckName CheckNames[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostCall(const CallEvent &Call, CheckerContext &C) const; @@ -356,14 +356,12 @@ bool isZero(ProgramStateRef State, const NonLoc &Val); IteratorChecker::IteratorChecker() { OutOfRangeBugType.reset( - new BugType(this, "Iterator out of range", "Misuse of STL APIs", - /*SuppressOnSink=*/true)); + new BugType(this, "Iterator out of range", "Misuse of STL APIs")); MismatchedBugType.reset( new BugType(this, "Iterator(s) mismatched", "Misuse of STL APIs", /*SuppressOnSink=*/true)); InvalidatedBugType.reset( - new BugType(this, "Iterator invalidated", "Misuse of STL APIs", - /*SuppressOnSink=*/true)); + new BugType(this, "Iterator invalidated", "Misuse of STL APIs")); } void IteratorChecker::checkPreCall(const CallEvent &Call, @@ -406,13 +404,15 @@ void IteratorChecker::checkPreCall(const CallEvent &Call, } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { // Check for out-of-range incrementions and decrementions - if (Call.getNumArgs() >= 1) { + if (Call.getNumArgs() >= 1 && + Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), InstCall->getCXXThisVal(), Call.getArgSVal(0)); } } else { - if (Call.getNumArgs() >= 2) { + if (Call.getNumArgs() >= 2 && + Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { verifyRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getArgSVal(0), Call.getArgSVal(1)); } @@ -565,7 +565,8 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, if (Func->isOverloadedOperator()) { const auto Op = Func->getOverloadedOperator(); if (isAssignmentOperator(Op)) { - const auto *InstCall = dyn_cast(&Call); + // Overloaded 'operator=' must be a non-static member function. + const auto *InstCall = cast(&Call); if (cast(Func)->isMoveAssignmentOperator()) { handleAssign(C, InstCall->getCXXThisVal(), Call.getOriginExpr(), Call.getArgSVal(0)); @@ -590,14 +591,16 @@ void IteratorChecker::checkPostCall(const CallEvent &Call, return; } else if (isRandomIncrOrDecrOperator(Func->getOverloadedOperator())) { if (const auto *InstCall = dyn_cast(&Call)) { - if (Call.getNumArgs() >= 1) { + if (Call.getNumArgs() >= 1 && + Call.getArgExpr(0)->getType()->isIntegralOrEnumerationType()) { handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), InstCall->getCXXThisVal(), Call.getArgSVal(0)); return; } } else { - if (Call.getNumArgs() >= 2) { + if (Call.getNumArgs() >= 2 && + Call.getArgExpr(1)->getType()->isIntegralOrEnumerationType()) { handleRandomIncrOrDecr(C, Func->getOverloadedOperator(), Call.getReturnValue(), Call.getArgSVal(0), Call.getArgSVal(1)); @@ -923,7 +926,7 @@ void IteratorChecker::verifyDereference(CheckerContext &C, auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && isPastTheEnd(State, *Pos)) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) return; reportOutOfRangeBug("Past-the-end iterator dereferenced.", Val, C, N); @@ -935,7 +938,7 @@ void IteratorChecker::verifyAccess(CheckerContext &C, const SVal &Val) const { auto State = C.getState(); const auto *Pos = getIteratorPosition(State, Val); if (Pos && !Pos->isValid()) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) { return; } @@ -1043,14 +1046,14 @@ void IteratorChecker::verifyRandomIncrOrDecr(CheckerContext &C, // The result may be the past-end iterator of the container, but any other // out of range position is undefined behaviour if (isAheadOfRange(State, advancePosition(C, Op, *Pos, Value))) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) return; reportOutOfRangeBug("Iterator decremented ahead of its valid range.", LHS, C, N); } if (isBehindPastTheEnd(State, advancePosition(C, Op, *Pos, Value))) { - auto *N = C.generateNonFatalErrorNode(State); + auto *N = C.generateErrorNode(State); if (!N) return; reportOutOfRangeBug("Iterator incremented behind the past-the-end " @@ -1587,7 +1590,8 @@ IteratorPosition IteratorChecker::advancePosition(CheckerContext &C, void IteratorChecker::reportOutOfRangeBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*OutOfRangeBugType, Message, ErrNode); + auto R = llvm::make_unique(*OutOfRangeBugType, Message, + ErrNode); R->markInteresting(Val); C.emitReport(std::move(R)); } @@ -1596,7 +1600,8 @@ void IteratorChecker::reportMismatchedBug(const StringRef &Message, const SVal &Val1, const SVal &Val2, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*MismatchedBugType, Message, ErrNode); + auto R = llvm::make_unique(*MismatchedBugType, Message, + ErrNode); R->markInteresting(Val1); R->markInteresting(Val2); C.emitReport(std::move(R)); @@ -1606,7 +1611,8 @@ void IteratorChecker::reportMismatchedBug(const StringRef &Message, const SVal &Val, const MemRegion *Reg, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*MismatchedBugType, Message, ErrNode); + auto R = llvm::make_unique(*MismatchedBugType, Message, + ErrNode); R->markInteresting(Val); R->markInteresting(Reg); C.emitReport(std::move(R)); @@ -1615,7 +1621,8 @@ void IteratorChecker::reportMismatchedBug(const StringRef &Message, void IteratorChecker::reportInvalidatedBug(const StringRef &Message, const SVal &Val, CheckerContext &C, ExplodedNode *ErrNode) const { - auto R = llvm::make_unique(*InvalidatedBugType, Message, ErrNode); + auto R = llvm::make_unique(*InvalidatedBugType, + Message, ErrNode); R->markInteresting(Val); C.emitReport(std::move(R)); } @@ -2373,12 +2380,10 @@ bool ento::shouldRegisterIteratorModeling(const LangOptions &LO) { auto *checker = Mgr.getChecker(); \ checker->ChecksEnabled[IteratorChecker::CK_##name] = true; \ checker->CheckNames[IteratorChecker::CK_##name] = \ - Mgr.getCurrentCheckName(); \ + Mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(IteratorRangeChecker) REGISTER_CHECKER(MismatchedIteratorChecker) diff --git a/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp index 2b75f3acc9228..0d64fbd6f62ec 100644 --- a/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/IvarInvalidationChecker.cpp @@ -48,8 +48,8 @@ struct ChecksFilter { /// Check that all ivars are invalidated. DefaultBool check_InstanceVariableInvalidation; - CheckName checkName_MissingInvalidationMethod; - CheckName checkName_InstanceVariableInvalidation; + CheckerNameRef checkName_MissingInvalidationMethod; + CheckerNameRef checkName_InstanceVariableInvalidation; }; class IvarInvalidationCheckerImpl { @@ -199,7 +199,7 @@ class IvarInvalidationCheckerImpl { const ObjCIvarDecl *IvarDecl, const IvarToPropMapTy &IvarToPopertyMap); - void reportNoInvalidationMethod(CheckName CheckName, + void reportNoInvalidationMethod(CheckerNameRef CheckName, const ObjCIvarDecl *FirstIvarDecl, const IvarToPropMapTy &IvarToPopertyMap, const ObjCInterfaceDecl *InterfaceD, @@ -526,7 +526,7 @@ visit(const ObjCImplementationDecl *ImplD) const { } void IvarInvalidationCheckerImpl::reportNoInvalidationMethod( - CheckName CheckName, const ObjCIvarDecl *FirstIvarDecl, + CheckerNameRef CheckName, const ObjCIvarDecl *FirstIvarDecl, const IvarToPropMapTy &IvarToPopertyMap, const ObjCInterfaceDecl *InterfaceD, bool MissingDeclaration) const { SmallString<128> sbuf; @@ -748,12 +748,10 @@ bool ento::shouldRegisterIvarInvalidationModeling(const LangOptions &LO) { IvarInvalidationChecker *checker = \ mgr.getChecker(); \ checker->Filter.check_##name = true; \ - checker->Filter.checkName_##name = mgr.getCurrentCheckName(); \ + checker->Filter.checkName_##name = mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(InstanceVariableInvalidation) REGISTER_CHECKER(MissingInvalidationMethod) diff --git a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp index 6927ba39c0a78..068f972eba6d7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/LocalizationChecker.cpp @@ -120,12 +120,12 @@ class NonLocalizedStringBRVisitor final : public BugReporterVisitor { public: NonLocalizedStringBRVisitor(const MemRegion *NonLocalizedString) : NonLocalizedString(NonLocalizedString), Satisfied(false) { - assert(NonLocalizedString); + assert(NonLocalizedString); } - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(NonLocalizedString); @@ -762,8 +762,8 @@ void NonLocalizedStringChecker::reportLocalizationError( return; // Generate the bug report. - std::unique_ptr R(new BugReport( - *BT, "User-facing text should use localized string macro", ErrNode)); + auto R = llvm::make_unique( + *BT, "User-facing text should use localized string macro", ErrNode); if (argumentNumber) { R->addRange(M.getArgExpr(argumentNumber - 1)->getSourceRange()); } else { @@ -883,18 +883,17 @@ void NonLocalizedStringChecker::checkPreObjCMessage(const ObjCMethodCall &msg, void NonLocalizedStringChecker::checkPreCall(const CallEvent &Call, CheckerContext &C) const { - const Decl *D = Call.getDecl(); - if (D && isa(D)) { - const FunctionDecl *FD = dyn_cast(D); - auto formals = FD->parameters(); - for (unsigned i = 0, - ei = std::min(unsigned(formals.size()), Call.getNumArgs()); - i != ei; ++i) { - if (isAnnotatedAsTakingLocalized(formals[i])) { - auto actual = Call.getArgSVal(i); - if (hasNonLocalizedState(actual, C)) { - reportLocalizationError(actual, Call, C, i + 1); - } + const auto *FD = dyn_cast_or_null(Call.getDecl()); + if (!FD) + return; + + auto formals = FD->parameters(); + for (unsigned i = 0, ei = std::min(static_cast(formals.size()), + Call.getNumArgs()); i != ei; ++i) { + if (isAnnotatedAsTakingLocalized(formals[i])) { + auto actual = Call.getArgSVal(i); + if (hasNonLocalizedState(actual, C)) { + reportLocalizationError(actual, Call, C, i + 1); } } } @@ -999,9 +998,10 @@ void NonLocalizedStringChecker::checkPostStmt(const ObjCStringLiteral *SL, setNonLocalizedState(sv, C); } -std::shared_ptr +PathDiagnosticPieceRef NonLocalizedStringBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { if (Satisfied) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp index 6e7776bb484e5..b06490d37e36b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MIGChecker.cpp @@ -274,7 +274,7 @@ void MIGChecker::checkReturnAux(const ReturnStmt *RS, CheckerContext &C) const { if (!N) return; - auto R = llvm::make_unique( + auto R = llvm::make_unique( BT, "MIG callback fails with error after deallocating argument value. " "This is a use-after-free vulnerability because the caller will try to " @@ -282,7 +282,8 @@ void MIGChecker::checkReturnAux(const ReturnStmt *RS, CheckerContext &C) const { N); R->addRange(RS->getSourceRange()); - bugreporter::trackExpressionValue(N, RS->getRetValue(), *R, false); + bugreporter::trackExpressionValue(N, RS->getRetValue(), *R, + bugreporter::TrackingKind::Thorough, false); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp index b250d3f8795e7..212229db6e482 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.cpp @@ -30,8 +30,8 @@ void MPIBugReporter::reportDoubleNonblocking( ErrorText = "Double nonblocking on request " + RequestRegion->getDescriptiveName() + ". "; - auto Report = llvm::make_unique(*DoubleNonblockingBugType, - ErrorText, ExplNode); + auto Report = llvm::make_unique( + *DoubleNonblockingBugType, ErrorText, ExplNode); Report->addRange(MPICallEvent.getSourceRange()); SourceRange Range = RequestRegion->sourceRange(); @@ -53,8 +53,8 @@ void MPIBugReporter::reportMissingWait( std::string ErrorText{"Request " + RequestRegion->getDescriptiveName() + " has no matching wait. "}; - auto Report = - llvm::make_unique(*MissingWaitBugType, ErrorText, ExplNode); + auto Report = llvm::make_unique(*MissingWaitBugType, + ErrorText, ExplNode); SourceRange Range = RequestRegion->sourceRange(); if (Range.isValid()) @@ -73,8 +73,8 @@ void MPIBugReporter::reportUnmatchedWait( std::string ErrorText{"Request " + RequestRegion->getDescriptiveName() + " has no matching nonblocking call. "}; - auto Report = - llvm::make_unique(*UnmatchedWaitBugType, ErrorText, ExplNode); + auto Report = llvm::make_unique(*UnmatchedWaitBugType, + ErrorText, ExplNode); Report->addRange(CE.getSourceRange()); SourceRange Range = RequestRegion->sourceRange(); @@ -84,20 +84,22 @@ void MPIBugReporter::reportUnmatchedWait( BReporter.emitReport(std::move(Report)); } -std::shared_ptr +PathDiagnosticPieceRef MPIBugReporter::RequestNodeVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { + PathSensitiveBugReport &BR) { if (IsNodeFound) return nullptr; const Request *const Req = N->getState()->get(RequestRegion); + assert(Req && "The region must be tracked and alive, given that we've " + "just emitted a report against it"); const Request *const PrevReq = N->getFirstPred()->getState()->get(RequestRegion); // Check if request was previously unused or in a different state. - if ((Req && !PrevReq) || (Req->CurrentState != PrevReq->CurrentState)) { + if (!PrevReq || (Req->CurrentState != PrevReq->CurrentState)) { IsNodeFound = true; ProgramPoint P = N->getFirstPred()->getLocation(); diff --git a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h index 6fbc30288655c..9871da026b042 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h +++ b/clang/lib/StaticAnalyzer/Checkers/MPI-Checker/MPIBugReporter.h @@ -89,9 +89,9 @@ class MPIBugReporter { ID.AddPointer(RequestRegion); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: const MemRegion *const RequestRegion; diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp index 32ba9bc8e2ef5..ab07ceeeca248 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSKeychainAPIChecker.cpp @@ -113,11 +113,14 @@ class MacOSKeychainAPIChecker : public Checker, const ExplodedNode *getAllocationNode(const ExplodedNode *N, SymbolRef Sym, CheckerContext &C) const; - std::unique_ptr generateAllocatedDataNotReleasedReport( - const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const; + std::unique_ptr + generateAllocatedDataNotReleasedReport(const AllocationPair &AP, + ExplodedNode *N, + CheckerContext &C) const; /// Mark an AllocationPair interesting for diagnostic reporting. - void markInteresting(BugReport *R, const AllocationPair &AP) const { + void markInteresting(PathSensitiveBugReport *R, + const AllocationPair &AP) const { R->markInteresting(AP.first); R->markInteresting(AP.second->Region); } @@ -139,9 +142,9 @@ class MacOSKeychainAPIChecker : public Checker, ID.AddPointer(Sym); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; }; }; } @@ -236,7 +239,7 @@ void MacOSKeychainAPIChecker:: os << "Deallocator doesn't match the allocator: '" << FunctionsToTrack[PDeallocIdx].Name << "' should be used."; - auto Report = llvm::make_unique(*BT, os.str(), N); + auto Report = llvm::make_unique(*BT, os.str(), N); Report->addVisitor(llvm::make_unique(AP.first)); Report->addRange(ArgExpr->getSourceRange()); markInteresting(Report.get(), AP); @@ -280,7 +283,8 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, << "the allocator: missing a call to '" << FunctionsToTrack[DIdx].Name << "'."; - auto Report = llvm::make_unique(*BT, os.str(), N); + auto Report = + llvm::make_unique(*BT, os.str(), N); Report->addVisitor(llvm::make_unique(V)); Report->addRange(ArgExpr->getSourceRange()); Report->markInteresting(AS->Region); @@ -334,7 +338,7 @@ void MacOSKeychainAPIChecker::checkPreStmt(const CallExpr *CE, if (!N) return; initBugType(); - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT, "Trying to free data which has not been allocated.", N); Report->addRange(ArgExpr->getSourceRange()); if (AS) @@ -465,7 +469,7 @@ MacOSKeychainAPIChecker::getAllocationNode(const ExplodedNode *N, return AllocNode; } -std::unique_ptr +std::unique_ptr MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( const AllocationPair &AP, ExplodedNode *N, CheckerContext &C) const { const ADFunctionInfo &FI = FunctionsToTrack[AP.second->AllocatorIdx]; @@ -480,16 +484,16 @@ MacOSKeychainAPIChecker::generateAllocatedDataNotReleasedReport( // allocated, and only report a single path. PathDiagnosticLocation LocUsedForUniqueing; const ExplodedNode *AllocNode = getAllocationNode(N, AP.first, C); - const Stmt *AllocStmt = PathDiagnosticLocation::getStmt(AllocNode); + const Stmt *AllocStmt = AllocNode->getStmtForDiagnostics(); if (AllocStmt) LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocStmt, C.getSourceManager(), AllocNode->getLocationContext()); - auto Report = - llvm::make_unique(*BT, os.str(), N, LocUsedForUniqueing, - AllocNode->getLocationContext()->getDecl()); + auto Report = llvm::make_unique( + *BT, os.str(), N, LocUsedForUniqueing, + AllocNode->getLocationContext()->getDecl()); Report->addVisitor(llvm::make_unique(AP.first)); markInteresting(Report.get(), AP); @@ -613,9 +617,10 @@ ProgramStateRef MacOSKeychainAPIChecker::checkPointerEscape( return State; } -std::shared_ptr +PathDiagnosticPieceRef MacOSKeychainAPIChecker::SecKeychainBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { const AllocationState *AS = N->getState()->get(Sym); if (!AS) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp index 1c52d20d09914..2cfe89e0eadfc 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MacOSXAPIChecker.cpp @@ -139,7 +139,8 @@ void MacOSXAPIChecker::CheckDispatchOnce(CheckerContext &C, const CallExpr *CE, BT_dispatchOnce.reset(new BugType(this, "Improper use of 'dispatch_once'", "API Misuse (Apple)")); - auto report = llvm::make_unique(*BT_dispatchOnce, os.str(), N); + auto report = + llvm::make_unique(*BT_dispatchOnce, os.str(), N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp index c416b98cf4bd5..41d2c83829814 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocChecker.cpp @@ -6,8 +6,41 @@ // //===----------------------------------------------------------------------===// // -// This file defines malloc/free checker, which checks for potential memory -// leaks, double free, and use-after-free problems. +// This file defines a variety of memory management related checkers, such as +// leak, double free, and use-after-free. +// +// The following checkers are defined here: +// +// * MallocChecker +// Despite its name, it models all sorts of memory allocations and +// de- or reallocation, including but not limited to malloc, free, +// relloc, new, delete. It also reports on a variety of memory misuse +// errors. +// Many other checkers interact very closely with this checker, in fact, +// most are merely options to this one. Other checkers may register +// MallocChecker, but do not enable MallocChecker's reports (more details +// to follow around its field, ChecksEnabled). +// It also has a boolean "Optimistic" checker option, which if set to true +// will cause the checker to model user defined memory management related +// functions annotated via the attribute ownership_takes, ownership_holds +// and ownership_returns. +// +// * NewDeleteChecker +// Enables the modeling of new, new[], delete, delete[] in MallocChecker, +// and checks for related double-free and use-after-free errors. +// +// * NewDeleteLeaksChecker +// Checks for leaks related to new, new[], delete, delete[]. +// Depends on NewDeleteChecker. +// +// * MismatchedDeallocatorChecker +// Enables checking whether memory is deallocated with the correspending +// allocation function in MallocChecker, such as malloc() allocated +// regions are only freed by free(), new by delete, new[] by delete[]. +// +// InnerPointerChecker interacts very closely with MallocChecker, but unlike +// the above checkers, it has it's own file, hence the many InnerPointerChecker +// related headers and non-static functions. // //===----------------------------------------------------------------------===// @@ -17,6 +50,7 @@ #include "clang/AST/ParentMap.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/TargetInfo.h" +#include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/CommonBugCategories.h" #include "clang/StaticAnalyzer/Core/Checker.h" @@ -36,6 +70,10 @@ using namespace clang; using namespace ento; +//===----------------------------------------------------------------------===// +// The types of allocation we're modeling. +//===----------------------------------------------------------------------===// + namespace { // Used to check correspondence between allocators and deallocators. @@ -49,57 +87,88 @@ enum AllocationFamily { AF_InnerBuffer }; +struct MemFunctionInfoTy; + +} // end of anonymous namespace + +/// Determine family of a deallocation expression. +static AllocationFamily +getAllocationFamily(const MemFunctionInfoTy &MemFunctionInfo, CheckerContext &C, + const Stmt *S); + +/// Print names of allocators and deallocators. +/// +/// \returns true on success. +static bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, + const Expr *E); + +/// Print expected name of an allocator based on the deallocator's +/// family derived from the DeallocExpr. +static void printExpectedAllocName(raw_ostream &os, + const MemFunctionInfoTy &MemFunctionInfo, + CheckerContext &C, const Expr *E); + +/// Print expected name of a deallocator based on the allocator's +/// family. +static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family); + +//===----------------------------------------------------------------------===// +// The state of a symbol, in terms of memory management. +//===----------------------------------------------------------------------===// + +namespace { + class RefState { - enum Kind { // Reference to allocated memory. - Allocated, - // Reference to zero-allocated memory. - AllocatedOfSizeZero, - // Reference to released/freed memory. - Released, - // The responsibility for freeing resources has transferred from - // this reference. A relinquished symbol should not be freed. - Relinquished, - // We are no longer guaranteed to have observed all manipulations - // of this pointer/memory. For example, it could have been - // passed as a parameter to an opaque function. - Escaped + enum Kind { + // Reference to allocated memory. + Allocated, + // Reference to zero-allocated memory. + AllocatedOfSizeZero, + // Reference to released/freed memory. + Released, + // The responsibility for freeing resources has transferred from + // this reference. A relinquished symbol should not be freed. + Relinquished, + // We are no longer guaranteed to have observed all manipulations + // of this pointer/memory. For example, it could have been + // passed as a parameter to an opaque function. + Escaped }; const Stmt *S; - unsigned K : 3; // Kind enum, but stored as a bitfield. - unsigned Family : 29; // Rest of 32-bit word, currently just an allocation - // family. - RefState(Kind k, const Stmt *s, unsigned family) - : S(s), K(k), Family(family) { + Kind K; + AllocationFamily Family; + + RefState(Kind k, const Stmt *s, AllocationFamily family) + : S(s), K(k), Family(family) { assert(family != AF_None); } + public: bool isAllocated() const { return K == Allocated; } bool isAllocatedOfSizeZero() const { return K == AllocatedOfSizeZero; } bool isReleased() const { return K == Released; } bool isRelinquished() const { return K == Relinquished; } bool isEscaped() const { return K == Escaped; } - AllocationFamily getAllocationFamily() const { - return (AllocationFamily)Family; - } + AllocationFamily getAllocationFamily() const { return Family; } const Stmt *getStmt() const { return S; } bool operator==(const RefState &X) const { return K == X.K && S == X.S && Family == X.Family; } - static RefState getAllocated(unsigned family, const Stmt *s) { + static RefState getAllocated(AllocationFamily family, const Stmt *s) { return RefState(Allocated, s, family); } static RefState getAllocatedOfSizeZero(const RefState *RS) { return RefState(AllocatedOfSizeZero, RS->getStmt(), RS->getAllocationFamily()); } - static RefState getReleased(unsigned family, const Stmt *s) { + static RefState getReleased(AllocationFamily family, const Stmt *s) { return RefState(Released, s, family); } - static RefState getRelinquished(unsigned family, const Stmt *s) { + static RefState getRelinquished(AllocationFamily family, const Stmt *s) { return RefState(Relinquished, s, family); } static RefState getEscaped(const RefState *RS) { @@ -112,8 +181,8 @@ class RefState { ID.AddInteger(Family); } - void dump(raw_ostream &OS) const { - switch (static_cast(K)) { + LLVM_DUMP_METHOD void dump(raw_ostream &OS) const { + switch (K) { #define CASE(ID) case ID: OS << #ID; break; CASE(Allocated) CASE(AllocatedOfSizeZero) @@ -126,24 +195,62 @@ class RefState { LLVM_DUMP_METHOD void dump() const { dump(llvm::errs()); } }; -enum ReallocPairKind { - RPToBeFreedAfterFailure, - // The symbol has been freed when reallocation failed. - RPIsFreeOnFailure, - // The symbol does not need to be freed after reallocation fails. - RPDoNotTrackAfterFailure +} // end of anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState) + +/// Check if the memory associated with this symbol was released. +static bool isReleased(SymbolRef Sym, CheckerContext &C); + +/// Update the RefState to reflect the new memory allocation. +/// The optional \p RetVal parameter specifies the newly allocated pointer +/// value; if unspecified, the value of expression \p E is used. +static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, + ProgramStateRef State, + AllocationFamily Family = AF_Malloc, + Optional RetVal = None); + +//===----------------------------------------------------------------------===// +// The modeling of memory reallocation. +// +// The terminology 'toPtr' and 'fromPtr' will be used: +// toPtr = realloc(fromPtr, 20); +//===----------------------------------------------------------------------===// + +REGISTER_SET_WITH_PROGRAMSTATE(ReallocSizeZeroSymbols, SymbolRef) + +namespace { + +/// The state of 'fromPtr' after reallocation is known to have failed. +enum OwnershipAfterReallocKind { + // The symbol needs to be freed (e.g.: realloc) + OAR_ToBeFreedAfterFailure, + // The symbol has been freed (e.g.: reallocf) + OAR_FreeOnFailure, + // The symbol doesn't have to freed (e.g.: we aren't sure if, how and where + // 'fromPtr' was allocated: + // void Haha(int *ptr) { + // ptr = realloc(ptr, 67); + // // ... + // } + // ). + OAR_DoNotTrackAfterFailure }; -/// \class ReallocPair -/// Stores information about the symbol being reallocated by a call to -/// 'realloc' to allow modeling failed reallocation later in the path. +/// Stores information about the 'fromPtr' symbol after reallocation. +/// +/// This is important because realloc may fail, and that needs special modeling. +/// Whether reallocation failed or not will not be known until later, so we'll +/// store whether upon failure 'fromPtr' will be freed, or needs to be freed +/// later, etc. struct ReallocPair { - // The symbol which realloc reallocated. + + // The 'fromPtr'. SymbolRef ReallocatedSym; - ReallocPairKind Kind; + OwnershipAfterReallocKind Kind; - ReallocPair(SymbolRef S, ReallocPairKind K) : - ReallocatedSym(S), Kind(K) {} + ReallocPair(SymbolRef S, OwnershipAfterReallocKind K) + : ReallocatedSym(S), Kind(K) {} void Profile(llvm::FoldingSetNodeID &ID) const { ID.AddInteger(Kind); ID.AddPointer(ReallocatedSym); @@ -154,41 +261,88 @@ struct ReallocPair { } }; -typedef std::pair LeakInfo; - -class MallocChecker : public Checker, - check::EndFunction, - check::PreCall, - check::PostStmt, - check::PostStmt, - check::NewAllocator, - check::PreStmt, - check::PostStmt, - check::PostObjCMessage, - check::Location, - eval::Assume> -{ -public: - MallocChecker() - : II_alloca(nullptr), II_win_alloca(nullptr), II_malloc(nullptr), - II_free(nullptr), II_realloc(nullptr), II_calloc(nullptr), - II_valloc(nullptr), II_reallocf(nullptr), II_strndup(nullptr), - II_strdup(nullptr), II_win_strdup(nullptr), II_kmalloc(nullptr), - II_if_nameindex(nullptr), II_if_freenameindex(nullptr), - II_wcsdup(nullptr), II_win_wcsdup(nullptr), II_g_malloc(nullptr), - II_g_malloc0(nullptr), II_g_realloc(nullptr), II_g_try_malloc(nullptr), - II_g_try_malloc0(nullptr), II_g_try_realloc(nullptr), - II_g_free(nullptr), II_g_memdup(nullptr), II_g_malloc_n(nullptr), - II_g_malloc0_n(nullptr), II_g_realloc_n(nullptr), - II_g_try_malloc_n(nullptr), II_g_try_malloc0_n(nullptr), - II_g_try_realloc_n(nullptr) {} +} // end of anonymous namespace + +REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) +//===----------------------------------------------------------------------===// +// Kinds of memory operations, information about resource managing functions. +//===----------------------------------------------------------------------===// + +namespace { + +enum class MemoryOperationKind { MOK_Allocate, MOK_Free, MOK_Any }; + +struct MemFunctionInfoTy { + /// The value of the MallocChecker:Optimistic is stored in this variable. + /// /// In pessimistic mode, the checker assumes that it does not know which /// functions might free the memory. + /// In optimistic mode, the checker assumes that all user-defined functions + /// which might free a pointer are annotated. + DefaultBool ShouldIncludeOwnershipAnnotatedFunctions; + + // TODO: Change these to CallDescription, and get rid of lazy initialization. + mutable IdentifierInfo *II_alloca = nullptr, *II_win_alloca = nullptr, + *II_malloc = nullptr, *II_free = nullptr, + *II_realloc = nullptr, *II_calloc = nullptr, + *II_valloc = nullptr, *II_reallocf = nullptr, + *II_strndup = nullptr, *II_strdup = nullptr, + *II_win_strdup = nullptr, *II_kmalloc = nullptr, + *II_if_nameindex = nullptr, + *II_if_freenameindex = nullptr, *II_wcsdup = nullptr, + *II_win_wcsdup = nullptr, *II_g_malloc = nullptr, + *II_g_malloc0 = nullptr, *II_g_realloc = nullptr, + *II_g_try_malloc = nullptr, + *II_g_try_malloc0 = nullptr, + *II_g_try_realloc = nullptr, *II_g_free = nullptr, + *II_g_memdup = nullptr, *II_g_malloc_n = nullptr, + *II_g_malloc0_n = nullptr, *II_g_realloc_n = nullptr, + *II_g_try_malloc_n = nullptr, + *II_g_try_malloc0_n = nullptr, *II_kfree = nullptr, + *II_g_try_realloc_n = nullptr; + + void initIdentifierInfo(ASTContext &C) const; + + ///@{ + /// Check if this is one of the functions which can allocate/reallocate + /// memory pointed to by one of its arguments. + bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; + bool isCMemFunction(const FunctionDecl *FD, ASTContext &C, + AllocationFamily Family, + MemoryOperationKind MemKind) const; + + /// Tells if the callee is one of the builtin new/delete operators, including + /// placement operators and other standard overloads. + bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; + ///@} +}; + +} // end of anonymous namespace + +//===----------------------------------------------------------------------===// +// Definition of the MallocChecker class. +//===----------------------------------------------------------------------===// + +namespace { + +class MallocChecker + : public Checker, + check::EndFunction, check::PreCall, + check::PostStmt, check::PostStmt, + check::NewAllocator, check::PreStmt, + check::PostStmt, check::PostObjCMessage, + check::Location, eval::Assume> { +public: + MemFunctionInfoTy MemFunctionInfo; + + /// Many checkers are essentially built into this one, so enabling them will + /// make MallocChecker perform additional modeling and reporting. enum CheckKind { + /// When a subchecker is enabled but MallocChecker isn't, model memory + /// management but do not emit warnings emitted with MallocChecker only + /// enabled. CK_MallocChecker, CK_NewDeleteChecker, CK_NewDeleteLeaksChecker, @@ -197,16 +351,10 @@ class MallocChecker : public Checker; DefaultBool ChecksEnabled[CK_NumCheckKinds]; - CheckName CheckNames[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; void checkPostStmt(const CallExpr *CE, CheckerContext &C) const; @@ -246,47 +394,9 @@ class MallocChecker : public Checker BT_MismatchedDealloc; mutable std::unique_ptr BT_OffsetFree[CK_NumCheckKinds]; mutable std::unique_ptr BT_UseZerroAllocated[CK_NumCheckKinds]; - mutable IdentifierInfo *II_alloca, *II_win_alloca, *II_malloc, *II_free, - *II_realloc, *II_calloc, *II_valloc, *II_reallocf, - *II_strndup, *II_strdup, *II_win_strdup, *II_kmalloc, - *II_if_nameindex, *II_if_freenameindex, *II_wcsdup, - *II_win_wcsdup, *II_g_malloc, *II_g_malloc0, - *II_g_realloc, *II_g_try_malloc, *II_g_try_malloc0, - *II_g_try_realloc, *II_g_free, *II_g_memdup, - *II_g_malloc_n, *II_g_malloc0_n, *II_g_realloc_n, - *II_g_try_malloc_n, *II_g_try_malloc0_n, - *II_g_try_realloc_n; - mutable Optional KernelZeroFlagVal; - void initIdentifierInfo(ASTContext &C) const; - - /// Determine family of a deallocation expression. - AllocationFamily getAllocationFamily(CheckerContext &C, const Stmt *S) const; - - /// Print names of allocators and deallocators. - /// - /// \returns true on success. - bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, - const Expr *E) const; - - /// Print expected name of an allocator based on the deallocator's - /// family derived from the DeallocExpr. - void printExpectedAllocName(raw_ostream &os, CheckerContext &C, - const Expr *DeallocExpr) const; - /// Print expected name of a deallocator based on the allocator's - /// family. - void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) const; - - ///@{ - /// Check if this is one of the functions which can allocate/reallocate memory - /// pointed to by one of its arguments. - bool isMemFunction(const FunctionDecl *FD, ASTContext &C) const; - bool isCMemFunction(const FunctionDecl *FD, - ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const; - bool isStandardNewDelete(const FunctionDecl *FD, ASTContext &C) const; - ///@} + // TODO: Remove mutable by moving the initializtaion to the registry function. + mutable Optional KernelZeroFlagVal; /// Process C++ operator new()'s allocation, which is the part of C++ /// new-expression that goes before the constructor. @@ -294,23 +404,64 @@ class MallocChecker : public Checker RetVal = None) const; - + /// + /// \param [in] E The expression that allocates memory. + /// \param [in] IndexOfSizeArg Index of the argument that specifies the size + /// of the memory that needs to be allocated. E.g. for malloc, this would be + /// 0. + /// \param [in] RetVal Specifies the newly allocated pointer value; + /// if unspecified, the value of expression \p E is used. + static ProgramStateRef ProcessZeroAllocCheck(CheckerContext &C, const Expr *E, + const unsigned IndexOfSizeArg, + ProgramStateRef State, + Optional RetVal = None); + + /// Model functions with the ownership_returns attribute. + /// + /// User-defined function may have the ownership_returns attribute, which + /// annotates that the function returns with an object that was allocated on + /// the heap, and passes the ownertship to the callee. + /// + /// void __attribute((ownership_returns(malloc, 1))) *my_malloc(size_t); + /// + /// It has two parameters: + /// - first: name of the resource (e.g. 'malloc') + /// - (OPTIONAL) second: size of the allocated region + /// + /// \param [in] CE The expression that allocates memory. + /// \param [in] Att The ownership_returns attribute. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after allocation. ProgramStateRef MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att, ProgramStateRef State) const; + + /// Models memory allocation. + /// + /// \param [in] CE The expression that allocates memory. + /// \param [in] SizeEx Size of the memory that needs to be allocated. + /// \param [in] Init The value the allocated memory needs to be initialized. + /// with. For example, \c calloc initializes the allocated memory to 0, + /// malloc leaves it undefined. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after allocation. static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, const Expr *SizeEx, SVal Init, ProgramStateRef State, AllocationFamily Family = AF_Malloc); + + /// Models memory allocation. + /// + /// \param [in] CE The expression that allocates memory. + /// \param [in] Size Size of the memory that needs to be allocated. + /// \param [in] Init The value the allocated memory needs to be initialized. + /// with. For example, \c calloc initializes the allocated memory to 0, + /// malloc leaves it undefined. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after allocation. static ProgramStateRef MallocMemAux(CheckerContext &C, const CallExpr *CE, - SVal SizeEx, SVal Init, + SVal Size, SVal Init, ProgramStateRef State, AllocationFamily Family = AF_Malloc); @@ -323,49 +474,125 @@ class MallocChecker : public Checker RetVal = None); - + /// Model functions with the ownership_takes and ownership_holds attributes. + /// + /// User-defined function may have the ownership_takes and/or ownership_holds + /// attributes, which annotates that the function frees the memory passed as a + /// parameter. + /// + /// void __attribute((ownership_takes(malloc, 1))) my_free(void *); + /// void __attribute((ownership_holds(malloc, 1))) my_hold(void *); + /// + /// They have two parameters: + /// - first: name of the resource (e.g. 'malloc') + /// - second: index of the parameter the attribute applies to + /// + /// \param [in] CE The expression that frees memory. + /// \param [in] Att The ownership_takes or ownership_holds attribute. + /// \param [in] State The \c ProgramState right before allocation. + /// \returns The ProgramState right after deallocation. ProgramStateRef FreeMemAttr(CheckerContext &C, const CallExpr *CE, const OwnershipAttr* Att, ProgramStateRef State) const; + + /// Models memory deallocation. + /// + /// \param [in] CE The expression that frees memory. + /// \param [in] State The \c ProgramState right before allocation. + /// \param [in] Num Index of the argument that needs to be freed. This is + /// normally 0, but for custom free functions it may be different. + /// \param [in] Hold Whether the parameter at \p Index has the ownership_holds + /// attribute. + /// \param [out] IsKnownToBeAllocated Whether the memory to be freed is known + /// to have been allocated, or in other words, the symbol to be freed was + /// registered as allocated by this checker. In the following case, \c ptr + /// isn't known to be allocated. + /// void Haha(int *ptr) { + /// ptr = realloc(ptr, 67); + /// // ... + /// } + /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function + /// we're modeling returns with Null on failure. + /// \returns The ProgramState right after deallocation. ProgramStateRef FreeMemAux(CheckerContext &C, const CallExpr *CE, - ProgramStateRef state, unsigned Num, - bool Hold, - bool &ReleasedAllocated, + ProgramStateRef State, unsigned Num, bool Hold, + bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure = false) const; - ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *Arg, - const Expr *ParentExpr, - ProgramStateRef State, - bool Hold, - bool &ReleasedAllocated, + + /// Models memory deallocation. + /// + /// \param [in] ArgExpr The variable who's pointee needs to be freed. + /// \param [in] ParentExpr The expression that frees the memory. + /// \param [in] State The \c ProgramState right before allocation. + /// normally 0, but for custom free functions it may be different. + /// \param [in] Hold Whether the parameter at \p Index has the ownership_holds + /// attribute. + /// \param [out] IsKnownToBeAllocated Whether the memory to be freed is known + /// to have been allocated, or in other words, the symbol to be freed was + /// registered as allocated by this checker. In the following case, \c ptr + /// isn't known to be allocated. + /// void Haha(int *ptr) { + /// ptr = realloc(ptr, 67); + /// // ... + /// } + /// \param [in] ReturnsNullOnFailure Whether the memory deallocation function + /// we're modeling returns with Null on failure. + /// \returns The ProgramState right after deallocation. + ProgramStateRef FreeMemAux(CheckerContext &C, const Expr *ArgExpr, + const Expr *ParentExpr, ProgramStateRef State, + bool Hold, bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure = false) const; + // TODO: Needs some refactoring, as all other deallocation modeling + // functions are suffering from out parameters and messy code due to how + // realloc is handled. + // + /// Models memory reallocation. + /// + /// \param [in] CE The expression that reallocated memory + /// \param [in] ShouldFreeOnFail Whether if reallocation fails, the supplied + /// memory should be freed. + /// \param [in] State The \c ProgramState right before reallocation. + /// \param [in] SuffixWithN Whether the reallocation function we're modeling + /// has an '_n' suffix, such as g_realloc_n. + /// \returns The ProgramState right after reallocation. ProgramStateRef ReallocMemAux(CheckerContext &C, const CallExpr *CE, - bool FreesMemOnFailure, - ProgramStateRef State, + bool ShouldFreeOnFail, ProgramStateRef State, bool SuffixWithN = false) const; + + /// Evaluates the buffer size that needs to be allocated. + /// + /// \param [in] Blocks The amount of blocks that needs to be allocated. + /// \param [in] BlockBytes The size of a block. + /// \returns The symbolic value of \p Blocks * \p BlockBytes. static SVal evalMulForBufferSize(CheckerContext &C, const Expr *Blocks, const Expr *BlockBytes); + + /// Models zero initialized array allocation. + /// + /// \param [in] CE The expression that reallocated memory + /// \param [in] State The \c ProgramState right before reallocation. + /// \returns The ProgramState right after allocation. static ProgramStateRef CallocMem(CheckerContext &C, const CallExpr *CE, ProgramStateRef State); - /// Check if the memory associated with this symbol was released. - bool isReleased(SymbolRef Sym, CheckerContext &C) const; + /// See if deallocation happens in a suspicious context. If so, escape the + /// pointers that otherwise would have been deallocated and return true. + bool suppressDeallocationsInSuspiciousContexts(const CallExpr *CE, + CheckerContext &C) const; + /// If in \p S \p Sym is used, check whether \p Sym was already freed. bool checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; + /// If in \p S \p Sym is used, check whether \p Sym was allocated as a zero + /// sized memory region. void checkUseZeroAllocated(SymbolRef Sym, CheckerContext &C, const Stmt *S) const; + /// If in \p S \p Sym is being freed, check whether \p Sym was already freed. bool checkDoubleDelete(SymbolRef Sym, CheckerContext &C) const; - /// Check if the function is known free memory, or if it is + /// Check if the function is known to free memory, or if it is /// "interesting" and should be modeled explicitly. /// /// \param [out] EscapingSymbol A function might not free memory in general, @@ -379,12 +606,12 @@ class MallocChecker : public Checker allocated. Other state (released) -> allocated. - return (Stmt && (isa(Stmt) || isa(Stmt)) && - (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && - (!SPrev || !(SPrev->isAllocated() || - SPrev->isAllocatedOfSizeZero()))); - } + // The allocated region symbol tracked by the main analysis. + SymbolRef Sym; - inline bool isReleased(const RefState *S, const RefState *SPrev, - const Stmt *Stmt) { - // Did not track -> released. Other state (allocated) -> released. - // The statement associated with the release might be missing. - bool IsReleased = (S && S->isReleased()) && - (!SPrev || !SPrev->isReleased()); - assert(!IsReleased || - (Stmt && (isa(Stmt) || isa(Stmt))) || - (!Stmt && S->getAllocationFamily() == AF_InnerBuffer)); - return IsReleased; - } + // The mode we are in, i.e. what kind of diagnostics will be emitted. + NotificationMode Mode; - inline bool isRelinquished(const RefState *S, const RefState *SPrev, - const Stmt *Stmt) { - // Did not track -> relinquished. Other state (allocated) -> relinquished. - return (Stmt && (isa(Stmt) || isa(Stmt) || - isa(Stmt)) && - (S && S->isRelinquished()) && - (!SPrev || !SPrev->isRelinquished())); - } + // A symbol from when the primary region should have been reallocated. + SymbolRef FailedReallocSymbol; - inline bool isReallocFailedCheck(const RefState *S, const RefState *SPrev, - const Stmt *Stmt) { - // If the expression is not a call, and the state change is - // released -> allocated, it must be the realloc return value - // check. If we have to handle more cases here, it might be cleaner just - // to track this extra bit in the state itself. - return ((!Stmt || !isa(Stmt)) && - (S && (S->isAllocated() || S->isAllocatedOfSizeZero())) && - (SPrev && !(SPrev->isAllocated() || - SPrev->isAllocatedOfSizeZero()))); - } + // A C++ destructor stack frame in which memory was released. Used for + // miscellaneous false positive suppression. + const StackFrameContext *ReleaseDestructorLC; - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + bool IsLeak; - std::shared_ptr - getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) override { - if (!IsLeak) - return nullptr; +public: + MallocBugVisitor(SymbolRef S, bool isLeak = false) + : Sym(S), Mode(Normal), FailedReallocSymbol(nullptr), + ReleaseDestructorLC(nullptr), IsLeak(isLeak) {} + + static void *getTag() { + static int Tag = 0; + return &Tag; + } + + void Profile(llvm::FoldingSetNodeID &ID) const override { + ID.AddPointer(getTag()); + ID.AddPointer(Sym); + } + + /// Did not track -> allocated. Other state (released) -> allocated. + static inline bool isAllocated(const RefState *RSCurr, const RefState *RSPrev, + const Stmt *Stmt) { + return (Stmt && (isa(Stmt) || isa(Stmt)) && + (RSCurr && + (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && + (!RSPrev || + !(RSPrev->isAllocated() || RSPrev->isAllocatedOfSizeZero()))); + } + + /// Did not track -> released. Other state (allocated) -> released. + /// The statement associated with the release might be missing. + static inline bool isReleased(const RefState *RSCurr, const RefState *RSPrev, + const Stmt *Stmt) { + bool IsReleased = + (RSCurr && RSCurr->isReleased()) && (!RSPrev || !RSPrev->isReleased()); + assert(!IsReleased || + (Stmt && (isa(Stmt) || isa(Stmt))) || + (!Stmt && RSCurr->getAllocationFamily() == AF_InnerBuffer)); + return IsReleased; + } + + /// Did not track -> relinquished. Other state (allocated) -> relinquished. + static inline bool isRelinquished(const RefState *RSCurr, + const RefState *RSPrev, const Stmt *Stmt) { + return (Stmt && + (isa(Stmt) || isa(Stmt) || + isa(Stmt)) && + (RSCurr && RSCurr->isRelinquished()) && + (!RSPrev || !RSPrev->isRelinquished())); + } + + /// If the expression is not a call, and the state change is + /// released -> allocated, it must be the realloc return value + /// check. If we have to handle more cases here, it might be cleaner just + /// to track this extra bit in the state itself. + static inline bool hasReallocFailed(const RefState *RSCurr, + const RefState *RSPrev, + const Stmt *Stmt) { + return ((!Stmt || !isa(Stmt)) && + (RSCurr && + (RSCurr->isAllocated() || RSCurr->isAllocatedOfSizeZero())) && + (RSPrev && + !(RSPrev->isAllocated() || RSPrev->isAllocatedOfSizeZero()))); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; + + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + PathSensitiveBugReport &BR) override { + if (!IsLeak) + return nullptr; - PathDiagnosticLocation L = - PathDiagnosticLocation::createEndOfPath(EndPathNode, - BRC.getSourceManager()); - // Do not add the statement itself as a range in case of leak. - return std::make_shared(L, BR.getDescription(), - false); - } + PathDiagnosticLocation L = BR.getLocation(); + // Do not add the statement itself as a range in case of leak. + return std::make_shared(L, BR.getDescription(), + false); + } - private: - class StackHintGeneratorForReallocationFailed - : public StackHintGeneratorForSymbol { - public: - StackHintGeneratorForReallocationFailed(SymbolRef S, StringRef M) +private: + class StackHintGeneratorForReallocationFailed + : public StackHintGeneratorForSymbol { + public: + StackHintGeneratorForReallocationFailed(SymbolRef S, StringRef M) : StackHintGeneratorForSymbol(S, M) {} - std::string getMessageForArg(const Expr *ArgE, - unsigned ArgIndex) override { - // Printed parameters start at 1, not 0. - ++ArgIndex; + std::string getMessageForArg(const Expr *ArgE, unsigned ArgIndex) override { + // Printed parameters start at 1, not 0. + ++ArgIndex; - SmallString<200> buf; - llvm::raw_svector_ostream os(buf); + SmallString<200> buf; + llvm::raw_svector_ostream os(buf); - os << "Reallocation of " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) - << " parameter failed"; + os << "Reallocation of " << ArgIndex << llvm::getOrdinalSuffix(ArgIndex) + << " parameter failed"; - return os.str(); - } + return os.str(); + } - std::string getMessageForReturn(const CallExpr *CallExpr) override { - return "Reallocation of returned value failed"; - } - }; + std::string getMessageForReturn(const CallExpr *CallExpr) override { + return "Reallocation of returned value failed"; + } }; }; -} // end anonymous namespace - -REGISTER_MAP_WITH_PROGRAMSTATE(RegionState, SymbolRef, RefState) -REGISTER_MAP_WITH_PROGRAMSTATE(ReallocPairs, SymbolRef, ReallocPair) -REGISTER_SET_WITH_PROGRAMSTATE(ReallocSizeZeroSymbols, SymbolRef) // A map from the freed symbol to the symbol representing the return value of // the free function. @@ -584,7 +811,11 @@ class StopTrackingCallback final : public SymbolVisitor { }; } // end anonymous namespace -void MallocChecker::initIdentifierInfo(ASTContext &Ctx) const { +//===----------------------------------------------------------------------===// +// Methods of MemFunctionInfoTy. +//===----------------------------------------------------------------------===// + +void MemFunctionInfoTy::initIdentifierInfo(ASTContext &Ctx) const { if (II_malloc) return; II_alloca = &Ctx.Idents.get("alloca"); @@ -598,6 +829,7 @@ void MallocChecker::initIdentifierInfo(ASTContext &Ctx) const { II_strndup = &Ctx.Idents.get("strndup"); II_wcsdup = &Ctx.Idents.get("wcsdup"); II_kmalloc = &Ctx.Idents.get("kmalloc"); + II_kfree = &Ctx.Idents.get("kfree"); II_if_nameindex = &Ctx.Idents.get("if_nameindex"); II_if_freenameindex = &Ctx.Idents.get("if_freenameindex"); @@ -623,7 +855,8 @@ void MallocChecker::initIdentifierInfo(ASTContext &Ctx) const { II_g_try_realloc_n = &Ctx.Idents.get("g_try_realloc_n"); } -bool MallocChecker::isMemFunction(const FunctionDecl *FD, ASTContext &C) const { +bool MemFunctionInfoTy::isMemFunction(const FunctionDecl *FD, + ASTContext &C) const { if (isCMemFunction(FD, C, AF_Malloc, MemoryOperationKind::MOK_Any)) return true; @@ -639,10 +872,9 @@ bool MallocChecker::isMemFunction(const FunctionDecl *FD, ASTContext &C) const { return false; } -bool MallocChecker::isCMemFunction(const FunctionDecl *FD, - ASTContext &C, - AllocationFamily Family, - MemoryOperationKind MemKind) const { +bool MemFunctionInfoTy::isCMemFunction(const FunctionDecl *FD, ASTContext &C, + AllocationFamily Family, + MemoryOperationKind MemKind) const { if (!FD) return false; @@ -657,7 +889,7 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD, if (Family == AF_Malloc && CheckFree) { if (FunI == II_free || FunI == II_realloc || FunI == II_reallocf || - FunI == II_g_free) + FunI == II_g_free || FunI == II_kfree) return true; } @@ -695,7 +927,7 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD, if (Family != AF_Malloc) return false; - if (IsOptimistic && FD->hasAttrs()) { + if (ShouldIncludeOwnershipAnnotatedFunctions && FD->hasAttrs()) { for (const auto *I : FD->specific_attrs()) { OwnershipAttr::OwnershipKind OwnKind = I->getOwnKind(); if(OwnKind == OwnershipAttr::Takes || OwnKind == OwnershipAttr::Holds) { @@ -710,11 +942,8 @@ bool MallocChecker::isCMemFunction(const FunctionDecl *FD, return false; } - -// Tells if the callee is one of the builtin new/delete operators, including -// placement operators and other standard overloads. -bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, - ASTContext &C) const { +bool MemFunctionInfoTy::isStandardNewDelete(const FunctionDecl *FD, + ASTContext &C) const { if (!FD) return false; @@ -730,6 +959,10 @@ bool MallocChecker::isStandardNewDelete(const FunctionDecl *FD, return !L.isValid() || C.getSourceManager().isInSystemHeader(L); } +//===----------------------------------------------------------------------===// +// Methods of MallocChecker and MallocBugVisitor. +//===----------------------------------------------------------------------===// + llvm::Optional MallocChecker::performKernelMalloc( const CallExpr *CE, CheckerContext &C, const ProgramStateRef &State) const { // 3-argument malloc(), as commonly used in {Free,Net,Open}BSD Kernels: @@ -828,28 +1061,35 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { return; ProgramStateRef State = C.getState(); - bool ReleasedAllocatedMemory = false; + bool IsKnownToBeAllocatedMemory = false; if (FD->getKind() == Decl::Function) { - initIdentifierInfo(C.getASTContext()); + MemFunctionInfo.initIdentifierInfo(C.getASTContext()); IdentifierInfo *FunI = FD->getIdentifier(); - if (FunI == II_malloc || FunI == II_g_malloc || FunI == II_g_try_malloc) { - if (CE->getNumArgs() < 1) + if (FunI == MemFunctionInfo.II_malloc || + FunI == MemFunctionInfo.II_g_malloc || + FunI == MemFunctionInfo.II_g_try_malloc) { + switch (CE->getNumArgs()) { + default: return; - if (CE->getNumArgs() < 3) { + case 1: State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - if (CE->getNumArgs() == 1) - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (CE->getNumArgs() == 3) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + break; + case 2: + State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); + break; + case 3: llvm::Optional MaybeState = performKernelMalloc(CE, C, State); if (MaybeState.hasValue()) State = MaybeState.getValue(); else State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); + break; } - } else if (FunI == II_kmalloc) { + } else if (FunI == MemFunctionInfo.II_kmalloc) { if (CE->getNumArgs() < 1) return; llvm::Optional MaybeState = @@ -858,97 +1098,116 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { State = MaybeState.getValue(); else State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - } else if (FunI == II_valloc) { + } else if (FunI == MemFunctionInfo.II_valloc) { if (CE->getNumArgs() < 1) return; State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State); - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (FunI == II_realloc || FunI == II_g_realloc || - FunI == II_g_try_realloc) { - State = ReallocMemAux(C, CE, false, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_reallocf) { - State = ReallocMemAux(C, CE, true, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_calloc) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + } else if (FunI == MemFunctionInfo.II_realloc || + FunI == MemFunctionInfo.II_g_realloc || + FunI == MemFunctionInfo.II_g_try_realloc) { + State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_reallocf) { + State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ true, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_calloc) { State = CallocMem(C, CE, State); - State = ProcessZeroAllocation(C, CE, 0, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_free || FunI == II_g_free) { - State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); - } else if (FunI == II_strdup || FunI == II_win_strdup || - FunI == II_wcsdup || FunI == II_win_wcsdup) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_free || + FunI == MemFunctionInfo.II_g_free || + FunI == MemFunctionInfo.II_kfree) { + if (suppressDeallocationsInSuspiciousContexts(CE, C)) + return; + + State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); + } else if (FunI == MemFunctionInfo.II_strdup || + FunI == MemFunctionInfo.II_win_strdup || + FunI == MemFunctionInfo.II_wcsdup || + FunI == MemFunctionInfo.II_win_wcsdup) { State = MallocUpdateRefState(C, CE, State); - } else if (FunI == II_strndup) { + } else if (FunI == MemFunctionInfo.II_strndup) { State = MallocUpdateRefState(C, CE, State); - } else if (FunI == II_alloca || FunI == II_win_alloca) { + } else if (FunI == MemFunctionInfo.II_alloca || + FunI == MemFunctionInfo.II_win_alloca) { if (CE->getNumArgs() < 1) return; State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_Alloca); - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (isStandardNewDelete(FD, C.getASTContext())) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + } else if (MemFunctionInfo.isStandardNewDelete(FD, C.getASTContext())) { // Process direct calls to operator new/new[]/delete/delete[] functions // as distinct from new/new[]/delete/delete[] expressions that are // processed by the checkPostStmt callbacks for CXXNewExpr and // CXXDeleteExpr. - OverloadedOperatorKind K = FD->getOverloadedOperator(); - if (K == OO_New) { + switch (FD->getOverloadedOperator()) { + case OO_New: State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_CXXNew); - State = ProcessZeroAllocation(C, CE, 0, State); - } - else if (K == OO_Array_New) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + break; + case OO_Array_New: State = MallocMemAux(C, CE, CE->getArg(0), UndefinedVal(), State, AF_CXXNewArray); - State = ProcessZeroAllocation(C, CE, 0, State); - } - else if (K == OO_Delete || K == OO_Array_Delete) - State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); - else + State = ProcessZeroAllocCheck(C, CE, 0, State); + break; + case OO_Delete: + case OO_Array_Delete: + State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); + break; + default: llvm_unreachable("not a new/delete operator"); - } else if (FunI == II_if_nameindex) { + } + } else if (FunI == MemFunctionInfo.II_if_nameindex) { // Should we model this differently? We can allocate a fixed number of // elements with zeros in the last one. State = MallocMemAux(C, CE, UnknownVal(), UnknownVal(), State, AF_IfNameIndex); - } else if (FunI == II_if_freenameindex) { - State = FreeMemAux(C, CE, State, 0, false, ReleasedAllocatedMemory); - } else if (FunI == II_g_malloc0 || FunI == II_g_try_malloc0) { + } else if (FunI == MemFunctionInfo.II_if_freenameindex) { + State = FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocatedMemory); + } else if (FunI == MemFunctionInfo.II_g_malloc0 || + FunI == MemFunctionInfo.II_g_try_malloc0) { if (CE->getNumArgs() < 1) return; SValBuilder &svalBuilder = C.getSValBuilder(); SVal zeroVal = svalBuilder.makeZeroVal(svalBuilder.getContext().CharTy); State = MallocMemAux(C, CE, CE->getArg(0), zeroVal, State); - State = ProcessZeroAllocation(C, CE, 0, State); - } else if (FunI == II_g_memdup) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + } else if (FunI == MemFunctionInfo.II_g_memdup) { if (CE->getNumArgs() < 2) return; State = MallocMemAux(C, CE, CE->getArg(1), UndefinedVal(), State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_g_malloc_n || FunI == II_g_try_malloc_n || - FunI == II_g_malloc0_n || FunI == II_g_try_malloc0_n) { + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_g_malloc_n || + FunI == MemFunctionInfo.II_g_try_malloc_n || + FunI == MemFunctionInfo.II_g_malloc0_n || + FunI == MemFunctionInfo.II_g_try_malloc0_n) { if (CE->getNumArgs() < 2) return; SVal Init = UndefinedVal(); - if (FunI == II_g_malloc0_n || FunI == II_g_try_malloc0_n) { + if (FunI == MemFunctionInfo.II_g_malloc0_n || + FunI == MemFunctionInfo.II_g_try_malloc0_n) { SValBuilder &SB = C.getSValBuilder(); Init = SB.makeZeroVal(SB.getContext().CharTy); } SVal TotalSize = evalMulForBufferSize(C, CE->getArg(0), CE->getArg(1)); State = MallocMemAux(C, CE, TotalSize, Init, State); - State = ProcessZeroAllocation(C, CE, 0, State); - State = ProcessZeroAllocation(C, CE, 1, State); - } else if (FunI == II_g_realloc_n || FunI == II_g_try_realloc_n) { + State = ProcessZeroAllocCheck(C, CE, 0, State); + State = ProcessZeroAllocCheck(C, CE, 1, State); + } else if (FunI == MemFunctionInfo.II_g_realloc_n || + FunI == MemFunctionInfo.II_g_try_realloc_n) { if (CE->getNumArgs() < 3) return; - State = ReallocMemAux(C, CE, false, State, true); - State = ProcessZeroAllocation(C, CE, 1, State); - State = ProcessZeroAllocation(C, CE, 2, State); + State = ReallocMemAux(C, CE, /*ShouldFreeOnFail*/ false, State, + /*SuffixWithN*/ true); + State = ProcessZeroAllocCheck(C, CE, 1, State); + State = ProcessZeroAllocCheck(C, CE, 2, State); } } - if (IsOptimistic || ChecksEnabled[CK_MismatchedDeallocatorChecker]) { + if (MemFunctionInfo.ShouldIncludeOwnershipAnnotatedFunctions || + ChecksEnabled[CK_MismatchedDeallocatorChecker]) { // Check all the attributes, if there are any. // There can be multiple of these attributes. if (FD->hasAttrs()) @@ -968,9 +1227,9 @@ void MallocChecker::checkPostStmt(const CallExpr *CE, CheckerContext &C) const { } // Performs a 0-sized allocations check. -ProgramStateRef MallocChecker::ProcessZeroAllocation( - CheckerContext &C, const Expr *E, const unsigned AllocationSizeArg, - ProgramStateRef State, Optional RetVal) const { +ProgramStateRef MallocChecker::ProcessZeroAllocCheck( + CheckerContext &C, const Expr *E, const unsigned IndexOfSizeArg, + ProgramStateRef State, Optional RetVal) { if (!State) return nullptr; @@ -980,7 +1239,7 @@ ProgramStateRef MallocChecker::ProcessZeroAllocation( const Expr *Arg = nullptr; if (const CallExpr *CE = dyn_cast(E)) { - Arg = CE->getArg(AllocationSizeArg); + Arg = CE->getArg(IndexOfSizeArg); } else if (const CXXNewExpr *NE = dyn_cast(E)) { if (NE->isArray()) @@ -1042,7 +1301,9 @@ static QualType getDeepPointeeType(QualType T) { return Result; } -static bool treatUnusedNewEscaped(const CXXNewExpr *NE) { +/// \returns true if the constructor invoked by \p NE has an argument of a +/// pointer/reference to a record type. +static bool hasNonTrivialConstructorCall(const CXXNewExpr *NE) { const CXXConstructExpr *ConstructE = NE->getConstructExpr(); if (!ConstructE) @@ -1072,11 +1333,17 @@ static bool treatUnusedNewEscaped(const CXXNewExpr *NE) { void MallocChecker::processNewAllocation(const CXXNewExpr *NE, CheckerContext &C, SVal Target) const { - if (!isStandardNewDelete(NE->getOperatorNew(), C.getASTContext())) + if (!MemFunctionInfo.isStandardNewDelete(NE->getOperatorNew(), + C.getASTContext())) return; - ParentMap &PM = C.getLocationContext()->getParentMap(); - if (!PM.isConsumedExpr(NE) && treatUnusedNewEscaped(NE)) + const ParentMap &PM = C.getLocationContext()->getParentMap(); + + // Non-trivial constructors have a chance to escape 'this', but marking all + // invocations of trivial constructors as escaped would cause too great of + // reduction of true positives, so let's just do that for constructors that + // have an argument of a pointer-to-record type. + if (!PM.isConsumedExpr(NE) && hasNonTrivialConstructorCall(NE)) return; ProgramStateRef State = C.getState(); @@ -1087,7 +1354,7 @@ void MallocChecker::processNewAllocation(const CXXNewExpr *NE, State = MallocUpdateRefState(C, NE, State, NE->isArray() ? AF_CXXNewArray : AF_CXXNew, Target); State = addExtentSize(C, NE, State, Target); - State = ProcessZeroAllocation(C, NE, 0, State, Target); + State = ProcessZeroAllocCheck(C, NE, 0, State, Target); C.addTransition(State); } @@ -1121,14 +1388,13 @@ ProgramStateRef MallocChecker::addExtentSize(CheckerContext &C, // Store the extent size for the (symbolic)region // containing the elements. Region = Target.getAsRegion() - ->getAs() + ->castAs() ->StripCasts() - ->getAs(); + ->castAs(); } else { ElementCount = svalBuilder.makeIntVal(1, true); - Region = Target.getAsRegion()->getAs(); + Region = Target.getAsRegion()->castAs(); } - assert(Region); // Set the region's extent equal to the Size in Bytes. QualType ElementType = NE->getAllocatedType(); @@ -1156,13 +1422,14 @@ void MallocChecker::checkPreStmt(const CXXDeleteExpr *DE, if (SymbolRef Sym = C.getSVal(DE->getArgument()).getAsSymbol()) checkUseAfterFree(Sym, C, DE->getArgument()); - if (!isStandardNewDelete(DE->getOperatorDelete(), C.getASTContext())) + if (!MemFunctionInfo.isStandardNewDelete(DE->getOperatorDelete(), + C.getASTContext())) return; ProgramStateRef State = C.getState(); - bool ReleasedAllocated; + bool IsKnownToBeAllocated; State = FreeMemAux(C, DE->getArgument(), DE, State, - /*Hold*/false, ReleasedAllocated); + /*Hold*/ false, IsKnownToBeAllocated); C.addTransition(State); } @@ -1202,11 +1469,11 @@ void MallocChecker::checkPostObjCMessage(const ObjCMethodCall &Call, if (!*FreeWhenDone) return; - bool ReleasedAllocatedMemory; - ProgramStateRef State = FreeMemAux(C, Call.getArgExpr(0), - Call.getOriginExpr(), C.getState(), - /*Hold=*/true, ReleasedAllocatedMemory, - /*RetNullOnFailure=*/true); + bool IsKnownToBeAllocatedMemory; + ProgramStateRef State = + FreeMemAux(C, Call.getArgExpr(0), Call.getOriginExpr(), C.getState(), + /*Hold=*/true, IsKnownToBeAllocatedMemory, + /*RetNullOnFailure=*/true); C.addTransition(State); } @@ -1218,7 +1485,7 @@ MallocChecker::MallocMemReturnsAttr(CheckerContext &C, const CallExpr *CE, if (!State) return nullptr; - if (Att->getModule() != II_malloc) + if (Att->getModule() != MemFunctionInfo.II_malloc) return nullptr; OwnershipAttr::args_iterator I = Att->args_begin(), E = Att->args_end(); @@ -1284,11 +1551,10 @@ ProgramStateRef MallocChecker::MallocMemAux(CheckerContext &C, return MallocUpdateRefState(C, CE, State, Family); } -ProgramStateRef MallocChecker::MallocUpdateRefState(CheckerContext &C, - const Expr *E, - ProgramStateRef State, - AllocationFamily Family, - Optional RetVal) { +static ProgramStateRef MallocUpdateRefState(CheckerContext &C, const Expr *E, + ProgramStateRef State, + AllocationFamily Family, + Optional RetVal) { if (!State) return nullptr; @@ -1316,27 +1582,24 @@ ProgramStateRef MallocChecker::FreeMemAttr(CheckerContext &C, if (!State) return nullptr; - if (Att->getModule() != II_malloc) + if (Att->getModule() != MemFunctionInfo.II_malloc) return nullptr; - bool ReleasedAllocated = false; + bool IsKnownToBeAllocated = false; for (const auto &Arg : Att->args()) { ProgramStateRef StateI = FreeMemAux( C, CE, State, Arg.getASTIndex(), - Att->getOwnKind() == OwnershipAttr::Holds, ReleasedAllocated); + Att->getOwnKind() == OwnershipAttr::Holds, IsKnownToBeAllocated); if (StateI) State = StateI; } return State; } -ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, - const CallExpr *CE, - ProgramStateRef State, - unsigned Num, - bool Hold, - bool &ReleasedAllocated, +ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const CallExpr *CE, + ProgramStateRef State, unsigned Num, + bool Hold, bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure) const { if (!State) return nullptr; @@ -1344,8 +1607,8 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, if (CE->getNumArgs() < (Num + 1)) return nullptr; - return FreeMemAux(C, CE->getArg(Num), CE, State, Hold, - ReleasedAllocated, ReturnsNullOnFailure); + return FreeMemAux(C, CE->getArg(Num), CE, State, Hold, IsKnownToBeAllocated, + ReturnsNullOnFailure); } /// Checks if the previous call to free on the given symbol failed - if free @@ -1363,8 +1626,10 @@ static bool didPreviousFreeFail(ProgramStateRef State, return false; } -AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, - const Stmt *S) const { +static AllocationFamily +getAllocationFamily(const MemFunctionInfoTy &MemFunctionInfo, CheckerContext &C, + const Stmt *S) { + if (!S) return AF_None; @@ -1376,10 +1641,11 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, ASTContext &Ctx = C.getASTContext(); - if (isCMemFunction(FD, Ctx, AF_Malloc, MemoryOperationKind::MOK_Any)) + if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Malloc, + MemoryOperationKind::MOK_Any)) return AF_Malloc; - if (isStandardNewDelete(FD, Ctx)) { + if (MemFunctionInfo.isStandardNewDelete(FD, Ctx)) { OverloadedOperatorKind Kind = FD->getOverloadedOperator(); if (Kind == OO_New || Kind == OO_Delete) return AF_CXXNew; @@ -1387,10 +1653,12 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, return AF_CXXNewArray; } - if (isCMemFunction(FD, Ctx, AF_IfNameIndex, MemoryOperationKind::MOK_Any)) + if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_IfNameIndex, + MemoryOperationKind::MOK_Any)) return AF_IfNameIndex; - if (isCMemFunction(FD, Ctx, AF_Alloca, MemoryOperationKind::MOK_Any)) + if (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Alloca, + MemoryOperationKind::MOK_Any)) return AF_Alloca; return AF_None; @@ -1408,8 +1676,8 @@ AllocationFamily MallocChecker::getAllocationFamily(CheckerContext &C, return AF_None; } -bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C, - const Expr *E) const { +static bool printAllocDeallocName(raw_ostream &os, CheckerContext &C, + const Expr *E) { if (const CallExpr *CE = dyn_cast(E)) { // FIXME: This doesn't handle indirect calls. const FunctionDecl *FD = CE->getDirectCallee(); @@ -1448,9 +1716,10 @@ bool MallocChecker::printAllocDeallocName(raw_ostream &os, CheckerContext &C, return false; } -void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C, - const Expr *E) const { - AllocationFamily Family = getAllocationFamily(C, E); +static void printExpectedAllocName(raw_ostream &os, + const MemFunctionInfoTy &MemFunctionInfo, + CheckerContext &C, const Expr *E) { + AllocationFamily Family = getAllocationFamily(MemFunctionInfo, C, E); switch(Family) { case AF_Malloc: os << "malloc()"; return; @@ -1463,8 +1732,7 @@ void MallocChecker::printExpectedAllocName(raw_ostream &os, CheckerContext &C, } } -void MallocChecker::printExpectedDeallocName(raw_ostream &os, - AllocationFamily Family) const { +static void printExpectedDeallocName(raw_ostream &os, AllocationFamily Family) { switch(Family) { case AF_Malloc: os << "free()"; return; case AF_CXXNew: os << "'delete'"; return; @@ -1479,9 +1747,8 @@ void MallocChecker::printExpectedDeallocName(raw_ostream &os, ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const Expr *ArgExpr, const Expr *ParentExpr, - ProgramStateRef State, - bool Hold, - bool &ReleasedAllocated, + ProgramStateRef State, bool Hold, + bool &IsKnownToBeAllocated, bool ReturnsNullOnFailure) const { if (!State) @@ -1555,6 +1822,9 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, const RefState *RsBase = State->get(SymBase); SymbolRef PreviousRetStatusSymbol = nullptr; + IsKnownToBeAllocated = + RsBase && (RsBase->isAllocated() || RsBase->isAllocatedOfSizeZero()); + if (RsBase) { // Memory returned by alloca() shouldn't be freed. @@ -1577,7 +1847,8 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, // Check if an expected deallocation function matches the real one. bool DeallocMatchesAlloc = - RsBase->getAllocationFamily() == getAllocationFamily(C, ParentExpr); + RsBase->getAllocationFamily() == + getAllocationFamily(MemFunctionInfo, C, ParentExpr); if (!DeallocMatchesAlloc) { ReportMismatchedDealloc(C, ArgExpr->getSourceRange(), ParentExpr, RsBase, SymBase, Hold); @@ -1603,9 +1874,6 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, return nullptr; } - ReleasedAllocated = (RsBase != nullptr) && (RsBase->isAllocated() || - RsBase->isAllocatedOfSizeZero()); - // Clean out the info on previous call to free return info. State = State->remove(SymBase); @@ -1620,8 +1888,9 @@ ProgramStateRef MallocChecker::FreeMemAux(CheckerContext &C, } } - AllocationFamily Family = RsBase ? RsBase->getAllocationFamily() - : getAllocationFamily(C, ParentExpr); + AllocationFamily Family = + RsBase ? RsBase->getAllocationFamily() + : getAllocationFamily(MemFunctionInfo, C, ParentExpr); // Normal free. if (Hold) return State->set(SymBase, @@ -1671,8 +1940,8 @@ Optional MallocChecker::getCheckIfTracked(CheckerContext &C, const Stmt *AllocDeallocStmt, bool IsALeakCheck) const { - return getCheckIfTracked(getAllocationFamily(C, AllocDeallocStmt), - IsALeakCheck); + return getCheckIfTracked( + getAllocationFamily(MemFunctionInfo, C, AllocDeallocStmt), IsALeakCheck); } Optional @@ -1810,9 +2079,10 @@ void MallocChecker::ReportBadFree(CheckerContext &C, SVal ArgVal, else os << "not memory allocated by "; - printExpectedAllocName(os, C, DeallocExpr); + printExpectedAllocName(os, MemFunctionInfo, C, DeallocExpr); - auto R = llvm::make_unique(*BT_BadFree[*CheckKind], os.str(), N); + auto R = llvm::make_unique(*BT_BadFree[*CheckKind], + os.str(), N); R->markInteresting(MR); R->addRange(Range); C.emitReport(std::move(R)); @@ -1836,7 +2106,7 @@ void MallocChecker::ReportFreeAlloca(CheckerContext &C, SVal ArgVal, BT_FreeAlloca[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Free alloca()", categories::MemoryError)); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_FreeAlloca[*CheckKind], "Memory allocated by alloca() should not be deallocated", N); R->markInteresting(ArgVal.getAsRegion()); @@ -1892,7 +2162,8 @@ void MallocChecker::ReportMismatchedDealloc(CheckerContext &C, os << ", not " << DeallocOs.str(); } - auto R = llvm::make_unique(*BT_MismatchedDealloc, os.str(), N); + auto R = llvm::make_unique(*BT_MismatchedDealloc, + os.str(), N); R->markInteresting(Sym); R->addRange(Range); R->addVisitor(llvm::make_unique(Sym)); @@ -1951,7 +2222,8 @@ void MallocChecker::ReportOffsetFree(CheckerContext &C, SVal ArgVal, else os << "allocated memory"; - auto R = llvm::make_unique(*BT_OffsetFree[*CheckKind], os.str(), N); + auto R = llvm::make_unique(*BT_OffsetFree[*CheckKind], + os.str(), N); R->markInteresting(MR->getBaseRegion()); R->addRange(Range); C.emitReport(std::move(R)); @@ -1977,10 +2249,11 @@ void MallocChecker::ReportUseAfterFree(CheckerContext &C, SourceRange Range, AllocationFamily AF = C.getState()->get(Sym)->getAllocationFamily(); - auto R = llvm::make_unique(*BT_UseFree[*CheckKind], + auto R = llvm::make_unique( + *BT_UseFree[*CheckKind], AF == AF_InnerBuffer - ? "Inner pointer of container used after re/deallocation" - : "Use of memory after it is freed", + ? "Inner pointer of container used after re/deallocation" + : "Use of memory after it is freed", N); R->markInteresting(Sym); @@ -2011,7 +2284,7 @@ void MallocChecker::ReportDoubleFree(CheckerContext &C, SourceRange Range, BT_DoubleFree[*CheckKind].reset(new BugType( CheckNames[*CheckKind], "Double free", categories::MemoryError)); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_DoubleFree[*CheckKind], (Released ? "Attempt to free released memory" : "Attempt to free non-owned memory"), @@ -2040,7 +2313,7 @@ void MallocChecker::ReportDoubleDelete(CheckerContext &C, SymbolRef Sym) const { "Double delete", categories::MemoryError)); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_DoubleDelete, "Attempt to delete released memory", N); R->markInteresting(Sym); @@ -2068,8 +2341,8 @@ void MallocChecker::ReportUseZeroAllocated(CheckerContext &C, new BugType(CheckNames[*CheckKind], "Use of zero allocated", categories::MemoryError)); - auto R = llvm::make_unique(*BT_UseZerroAllocated[*CheckKind], - "Use of zero-allocated memory", N); + auto R = llvm::make_unique( + *BT_UseZerroAllocated[*CheckKind], "Use of zero-allocated memory", N); R->addRange(Range); if (Sym) { @@ -2108,7 +2381,8 @@ void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, Os << " is a function pointer"; - auto R = llvm::make_unique(*BT_BadFree[*CheckKind], Os.str(), N); + auto R = llvm::make_unique(*BT_BadFree[*CheckKind], + Os.str(), N); R->markInteresting(MR); R->addRange(Range); C.emitReport(std::move(R)); @@ -2117,7 +2391,7 @@ void MallocChecker::ReportFunctionPointerFree(CheckerContext &C, SVal ArgVal, ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, const CallExpr *CE, - bool FreesOnFail, + bool ShouldFreeOnFail, ProgramStateRef State, bool SuffixWithN) const { if (!State) @@ -2182,33 +2456,32 @@ ProgramStateRef MallocChecker::ReallocMemAux(CheckerContext &C, if (!FromPtr || !ToPtr) return nullptr; - bool ReleasedAllocated = false; + bool IsKnownToBeAllocated = false; // If the size is 0, free the memory. if (SizeIsZero) - if (ProgramStateRef stateFree = FreeMemAux(C, CE, StateSizeIsZero, 0, - false, ReleasedAllocated)){ - // The semantics of the return value are: - // If size was equal to 0, either NULL or a pointer suitable to be passed - // to free() is returned. We just free the input pointer and do not add - // any constrains on the output pointer. + // The semantics of the return value are: + // If size was equal to 0, either NULL or a pointer suitable to be passed + // to free() is returned. We just free the input pointer and do not add + // any constrains on the output pointer. + if (ProgramStateRef stateFree = + FreeMemAux(C, CE, StateSizeIsZero, 0, false, IsKnownToBeAllocated)) return stateFree; - } // Default behavior. if (ProgramStateRef stateFree = - FreeMemAux(C, CE, State, 0, false, ReleasedAllocated)) { + FreeMemAux(C, CE, State, 0, false, IsKnownToBeAllocated)) { ProgramStateRef stateRealloc = MallocMemAux(C, CE, TotalSize, UnknownVal(), stateFree); if (!stateRealloc) return nullptr; - ReallocPairKind Kind = RPToBeFreedAfterFailure; - if (FreesOnFail) - Kind = RPIsFreeOnFailure; - else if (!ReleasedAllocated) - Kind = RPDoNotTrackAfterFailure; + OwnershipAfterReallocKind Kind = OAR_ToBeFreedAfterFailure; + if (ShouldFreeOnFail) + Kind = OAR_FreeOnFailure; + else if (!IsKnownToBeAllocated) + Kind = OAR_DoNotTrackAfterFailure; // Record the info about the reallocated symbol so that we could properly // process failed reallocation. @@ -2236,9 +2509,9 @@ ProgramStateRef MallocChecker::CallocMem(CheckerContext &C, const CallExpr *CE, return MallocMemAux(C, CE, TotalSize, zeroVal, State); } -LeakInfo -MallocChecker::getAllocationSite(const ExplodedNode *N, SymbolRef Sym, - CheckerContext &C) const { +MallocChecker::LeakInfo MallocChecker::getAllocationSite(const ExplodedNode *N, + SymbolRef Sym, + CheckerContext &C) { const LocationContext *LeakContext = N->getLocationContext(); // Walk the ExplodedGraph backwards and find the first node that referred to // the tracked symbol. @@ -2318,7 +2591,7 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, const MemRegion *Region = nullptr; std::tie(AllocNode, Region) = getAllocationSite(N, Sym, C); - const Stmt *AllocationStmt = PathDiagnosticLocation::getStmt(AllocNode); + const Stmt *AllocationStmt = AllocNode->getStmtForDiagnostics(); if (AllocationStmt) LocUsedForUniqueing = PathDiagnosticLocation::createBegin(AllocationStmt, C.getSourceManager(), @@ -2333,7 +2606,7 @@ void MallocChecker::reportLeak(SymbolRef Sym, ExplodedNode *N, os << "Potential memory leak"; } - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_Leak[*CheckKind], os.str(), N, LocUsedForUniqueing, AllocNode->getLocationContext()->getDecl()); R->markInteresting(Sym); @@ -2419,9 +2692,10 @@ void MallocChecker::checkPreCall(const CallEvent &Call, ASTContext &Ctx = C.getASTContext(); if (ChecksEnabled[CK_MallocChecker] && - (isCMemFunction(FD, Ctx, AF_Malloc, MemoryOperationKind::MOK_Free) || - isCMemFunction(FD, Ctx, AF_IfNameIndex, - MemoryOperationKind::MOK_Free))) + (MemFunctionInfo.isCMemFunction(FD, Ctx, AF_Malloc, + MemoryOperationKind::MOK_Free) || + MemFunctionInfo.isCMemFunction(FD, Ctx, AF_IfNameIndex, + MemoryOperationKind::MOK_Free))) return; } @@ -2524,12 +2798,41 @@ void MallocChecker::checkPostStmt(const BlockExpr *BE, C.addTransition(state); } -bool MallocChecker::isReleased(SymbolRef Sym, CheckerContext &C) const { +static bool isReleased(SymbolRef Sym, CheckerContext &C) { assert(Sym); const RefState *RS = C.getState()->get(Sym); return (RS && RS->isReleased()); } +bool MallocChecker::suppressDeallocationsInSuspiciousContexts( + const CallExpr *CE, CheckerContext &C) const { + if (CE->getNumArgs() == 0) + return false; + + StringRef FunctionStr = ""; + if (const auto *FD = dyn_cast(C.getStackFrame()->getDecl())) + if (const Stmt *Body = FD->getBody()) + if (Body->getBeginLoc().isValid()) + FunctionStr = + Lexer::getSourceText(CharSourceRange::getTokenRange( + {FD->getBeginLoc(), Body->getBeginLoc()}), + C.getSourceManager(), C.getLangOpts()); + + // We do not model the Integer Set Library's retain-count based allocation. + if (!FunctionStr.contains("__isl_")) + return false; + + ProgramStateRef State = C.getState(); + + for (const Expr *Arg : CE->arguments()) + if (SymbolRef Sym = C.getSVal(Arg).getAsSymbol()) + if (const RefState *RS = State->get(Sym)) + State = State->set(Sym, RefState::getEscaped(RS)); + + C.addTransition(State); + return true; +} + bool MallocChecker::checkUseAfterFree(SymbolRef Sym, CheckerContext &C, const Stmt *S) const { @@ -2600,13 +2903,17 @@ ProgramStateRef MallocChecker::evalAssume(ProgramStateRef state, SymbolRef ReallocSym = I.getData().ReallocatedSym; if (const RefState *RS = state->get(ReallocSym)) { if (RS->isReleased()) { - if (I.getData().Kind == RPToBeFreedAfterFailure) + switch (I.getData().Kind) { + case OAR_ToBeFreedAfterFailure: state = state->set(ReallocSym, RefState::getAllocated(RS->getAllocationFamily(), RS->getStmt())); - else if (I.getData().Kind == RPDoNotTrackAfterFailure) + break; + case OAR_DoNotTrackAfterFailure: state = state->remove(ReallocSym); - else - assert(I.getData().Kind == RPIsFreeOnFailure); + break; + default: + assert(I.getData().Kind == OAR_FreeOnFailure); + } } } state = state->remove(I.getKey()); @@ -2689,7 +2996,7 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( // If it's one of the allocation functions we can reason about, we model // its behavior explicitly. - if (isMemFunction(FD, ASTC)) + if (MemFunctionInfo.isMemFunction(FD, ASTC)) return false; // If it's not a system call, assume it frees memory. @@ -2781,35 +3088,32 @@ bool MallocChecker::mayFreeAnyEscapedMemoryOrIsModeledExplicitly( return false; } -static bool retTrue(const RefState *RS) { - return true; -} - -static bool checkIfNewOrNewArrayFamily(const RefState *RS) { - return (RS->getAllocationFamily() == AF_CXXNewArray || - RS->getAllocationFamily() == AF_CXXNew); -} - ProgramStateRef MallocChecker::checkPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { - return checkPointerEscapeAux(State, Escaped, Call, Kind, &retTrue); + return checkPointerEscapeAux(State, Escaped, Call, Kind, + /*IsConstPointerEscape*/ false); } ProgramStateRef MallocChecker::checkConstPointerEscape(ProgramStateRef State, const InvalidatedSymbols &Escaped, const CallEvent *Call, PointerEscapeKind Kind) const { + // If a const pointer escapes, it may not be freed(), but it could be deleted. return checkPointerEscapeAux(State, Escaped, Call, Kind, - &checkIfNewOrNewArrayFamily); + /*IsConstPointerEscape*/ true); } -ProgramStateRef MallocChecker::checkPointerEscapeAux(ProgramStateRef State, - const InvalidatedSymbols &Escaped, - const CallEvent *Call, - PointerEscapeKind Kind, - bool(*CheckRefState)(const RefState*)) const { +static bool checkIfNewOrNewArrayFamily(const RefState *RS) { + return (RS->getAllocationFamily() == AF_CXXNewArray || + RS->getAllocationFamily() == AF_CXXNew); +} + +ProgramStateRef MallocChecker::checkPointerEscapeAux( + ProgramStateRef State, const InvalidatedSymbols &Escaped, + const CallEvent *Call, PointerEscapeKind Kind, + bool IsConstPointerEscape) const { // If we know that the call does not free memory, or we want to process the // call later, keep tracking the top level arguments. SymbolRef EscapingSymbol = nullptr; @@ -2828,13 +3132,10 @@ ProgramStateRef MallocChecker::checkPointerEscapeAux(ProgramStateRef State, if (EscapingSymbol && EscapingSymbol != sym) continue; - if (const RefState *RS = State->get(sym)) { - if ((RS->isAllocated() || RS->isAllocatedOfSizeZero()) && - CheckRefState(RS)) { - State = State->remove(sym); - State = State->set(sym, RefState::getEscaped(RS)); - } - } + if (const RefState *RS = State->get(sym)) + if (RS->isAllocated() || RS->isAllocatedOfSizeZero()) + if (!IsConstPointerEscape || checkIfNewOrNewArrayFamily(RS)) + State = State->set(sym, RefState::getEscaped(RS)); } return State; } @@ -2844,9 +3145,8 @@ static SymbolRef findFailedReallocSymbol(ProgramStateRef currState, ReallocPairsTy currMap = currState->get(); ReallocPairsTy prevMap = prevState->get(); - for (ReallocPairsTy::iterator I = prevMap.begin(), E = prevMap.end(); - I != E; ++I) { - SymbolRef sym = I.getKey(); + for (const ReallocPairsTy::value_type &Pair : prevMap) { + SymbolRef sym = Pair.first; if (!currMap.lookup(sym)) return sym; } @@ -2867,19 +3167,19 @@ static bool isReferenceCountingPointerDestructor(const CXXDestructorDecl *DD) { return false; } -std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, BugReport &BR) { - +PathDiagnosticPieceRef MallocBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef state = N->getState(); ProgramStateRef statePrev = N->getFirstPred()->getState(); - const RefState *RS = state->get(Sym); + const RefState *RSCurr = state->get(Sym); const RefState *RSPrev = statePrev->get(Sym); - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); // When dealing with containers, we sometimes want to give a note // even if the statement is missing. - if (!S && (!RS || RS->getAllocationFamily() != AF_InnerBuffer)) + if (!S && (!RSCurr || RSCurr->getAllocationFamily() != AF_InnerBuffer)) return nullptr; const LocationContext *CurrentLC = N->getLocationContext(); @@ -2909,17 +3209,17 @@ std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( // Find out if this is an interesting point and what is the kind. StringRef Msg; - StackHintGeneratorForSymbol *StackHint = nullptr; + std::unique_ptr StackHint = nullptr; SmallString<256> Buf; llvm::raw_svector_ostream OS(Buf); if (Mode == Normal) { - if (isAllocated(RS, RSPrev, S)) { + if (isAllocated(RSCurr, RSPrev, S)) { Msg = "Memory is allocated"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returned allocated memory"); - } else if (isReleased(RS, RSPrev, S)) { - const auto Family = RS->getAllocationFamily(); + StackHint = std::make_unique( + Sym, "Returned allocated memory"); + } else if (isReleased(RSCurr, RSPrev, S)) { + const auto Family = RSCurr->getAllocationFamily(); switch (Family) { case AF_Alloca: case AF_Malloc: @@ -2927,8 +3227,8 @@ std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( case AF_CXXNewArray: case AF_IfNameIndex: Msg = "Memory is released"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returning; memory was released"); + StackHint = std::make_unique( + Sym, "Returning; memory was released"); break; case AF_InnerBuffer: { const MemRegion *ObjRegion = @@ -2939,11 +3239,11 @@ std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( if (N->getLocation().getKind() == ProgramPoint::PostImplicitCallKind) { OS << "deallocated by call to destructor"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returning; inner buffer was deallocated"); + StackHint = std::make_unique( + Sym, "Returning; inner buffer was deallocated"); } else { OS << "reallocated by call to '"; - const Stmt *S = RS->getStmt(); + const Stmt *S = RSCurr->getStmt(); if (const auto *MemCallE = dyn_cast(S)) { OS << MemCallE->getMethodDecl()->getNameAsString(); } else if (const auto *OpCallE = dyn_cast(S)) { @@ -2955,8 +3255,8 @@ std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( OS << (D ? D->getNameAsString() : "unknown"); } OS << "'"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returning; inner buffer was reallocated"); + StackHint = std::make_unique( + Sym, "Returning; inner buffer was reallocated"); } Msg = OS.str(); break; @@ -2994,14 +3294,14 @@ std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( } } } - } else if (isRelinquished(RS, RSPrev, S)) { + } else if (isRelinquished(RSCurr, RSPrev, S)) { Msg = "Memory ownership is transferred"; - StackHint = new StackHintGeneratorForSymbol(Sym, ""); - } else if (isReallocFailedCheck(RS, RSPrev, S)) { + StackHint = std::make_unique(Sym, ""); + } else if (hasReallocFailed(RSCurr, RSPrev, S)) { Mode = ReallocationFailed; Msg = "Reallocation failed"; - StackHint = new StackHintGeneratorForReallocationFailed(Sym, - "Reallocation failed"); + StackHint = std::make_unique( + Sym, "Reallocation failed"); if (SymbolRef sym = findFailedReallocSymbol(state, statePrev)) { // Is it possible to fail two reallocs WITHOUT testing in between? @@ -3020,21 +3320,24 @@ std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( if (!statePrev->get(FailedReallocSymbol)) { // We're at the reallocation point. Msg = "Attempt to reallocate memory"; - StackHint = new StackHintGeneratorForSymbol(Sym, - "Returned reallocated memory"); + StackHint = std::make_unique( + Sym, "Returned reallocated memory"); FailedReallocSymbol = nullptr; Mode = Normal; } } - if (Msg.empty()) + if (Msg.empty()) { + assert(!StackHint); return nullptr; + } + assert(StackHint); // Generate the extra diagnostic. PathDiagnosticLocation Pos; if (!S) { - assert(RS->getAllocationFamily() == AF_InnerBuffer); + assert(RSCurr->getAllocationFamily() == AF_InnerBuffer); auto PostImplCall = N->getLocation().getAs(); if (!PostImplCall) return nullptr; @@ -3045,7 +3348,9 @@ std::shared_ptr MallocChecker::MallocBugVisitor::VisitNode( N->getLocationContext()); } - return std::make_shared(Pos, Msg, true, StackHint); + auto P = std::make_shared(Pos, Msg, true); + BR.addCallStackHint(P, std::move(StackHint)); + return P; } void MallocChecker::printState(raw_ostream &Out, ProgramStateRef State, @@ -3092,13 +3397,13 @@ void ento::registerInnerPointerCheckerAux(CheckerManager &mgr) { MallocChecker *checker = mgr.getChecker(); checker->ChecksEnabled[MallocChecker::CK_InnerPointerChecker] = true; checker->CheckNames[MallocChecker::CK_InnerPointerChecker] = - mgr.getCurrentCheckName(); + mgr.getCurrentCheckerName(); } void ento::registerDynamicMemoryModeling(CheckerManager &mgr) { auto *checker = mgr.registerChecker(); - checker->IsOptimistic = mgr.getAnalyzerOptions().getCheckerBooleanOption( - checker, "Optimistic"); + checker->MemFunctionInfo.ShouldIncludeOwnershipAnnotatedFunctions = + mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "Optimistic"); } bool ento::shouldRegisterDynamicMemoryModeling(const LangOptions &LO) { @@ -3109,12 +3414,11 @@ bool ento::shouldRegisterDynamicMemoryModeling(const LangOptions &LO) { void ento::register##name(CheckerManager &mgr) { \ MallocChecker *checker = mgr.getChecker(); \ checker->ChecksEnabled[MallocChecker::CK_##name] = true; \ - checker->CheckNames[MallocChecker::CK_##name] = mgr.getCurrentCheckName(); \ + checker->CheckNames[MallocChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ } \ \ - bool ento::shouldRegister##name(const LangOptions &LO) { \ - return true; \ - } + bool ento::shouldRegister##name(const LangOptions &LO) { return true; } REGISTER_CHECKER(MallocChecker) REGISTER_CHECKER(NewDeleteChecker) diff --git a/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp index 2eb4d7141e284..b5881a9e65338 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MallocSizeofChecker.cpp @@ -183,7 +183,7 @@ class MallocSizeofChecker : public Checker { QualType CastedType = i->CastedExpr->getType(); if (!CastedType->isPointerType()) continue; - QualType PointeeType = CastedType->getAs()->getPointeeType(); + QualType PointeeType = CastedType->getPointeeType(); if (PointeeType->isVoidType()) continue; diff --git a/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp index 270efede83858..604a2b39146fd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MmapWriteExecChecker.cpp @@ -67,7 +67,7 @@ void MmapWriteExecChecker::checkPreCall(const CallEvent &Call, if (!N) return; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT, "Both PROT_WRITE and PROT_EXEC flags are set. This can " "lead to exploitable memory regions, which could be overwritten " "with malicious code", N); diff --git a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp index d8a9af78536a0..8160b54fe7fdd 100644 --- a/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/MoveChecker.cpp @@ -169,9 +169,9 @@ class MoveChecker // in the first place. } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: const MoveChecker &Chk; @@ -270,9 +270,10 @@ static const MemRegion *unwrapRValueReferenceIndirection(const MemRegion *MR) { return MR; } -std::shared_ptr +PathDiagnosticPieceRef MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { // We need only the last move of the reported object's region. // The visitor walks the ExplodedGraph backwards. if (Found) @@ -288,7 +289,7 @@ MoveChecker::MovedBugVisitor::VisitNode(const ExplodedNode *N, return nullptr; // Retrieve the associated statement. - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; Found = true; @@ -400,7 +401,7 @@ ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, PathDiagnosticLocation LocUsedForUniqueing; const ExplodedNode *MoveNode = getMoveLocation(N, Region, C); - if (const Stmt *MoveStmt = PathDiagnosticLocation::getStmt(MoveNode)) + if (const Stmt *MoveStmt = MoveNode->getStmtForDiagnostics()) LocUsedForUniqueing = PathDiagnosticLocation::createBegin( MoveStmt, C.getSourceManager(), MoveNode->getLocationContext()); @@ -428,9 +429,9 @@ ExplodedNode *MoveChecker::reportBug(const MemRegion *Region, break; } - auto R = - llvm::make_unique(*BT, OS.str(), N, LocUsedForUniqueing, - MoveNode->getLocationContext()->getDecl()); + auto R = llvm::make_unique( + *BT, OS.str(), N, LocUsedForUniqueing, + MoveNode->getLocationContext()->getDecl()); R->addVisitor(llvm::make_unique(*this, Region, RD, MK)); C.emitReport(std::move(R)); return N; diff --git a/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp index 6fc7c17bc42fb..c42efbce47e06 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NSAutoreleasePoolChecker.cpp @@ -67,9 +67,11 @@ void NSAutoreleasePoolChecker::checkPreObjCMessage(const ObjCMethodCall &msg, return; } - auto Report = llvm::make_unique( - *BT, "Use -drain instead of -release when using NSAutoreleasePool and " - "garbage collection", N); + auto Report = llvm::make_unique( + *BT, + "Use -drain instead of -release when using NSAutoreleasePool and " + "garbage collection", + N); Report->addRange(msg.getSourceRange()); C.emitReport(std::move(Report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp index 5cec012258c15..74f2cbefe9de7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NSErrorChecker.cpp @@ -273,7 +273,8 @@ void NSOrCFErrorDerefChecker::checkEvent(ImplicitNullDerefEvent event) const { CFBT.reset(new CFErrorDerefBug(this)); bug = CFBT.get(); } - BR.emitReport(llvm::make_unique(*bug, os.str(), event.SinkNode)); + BR.emitReport( + llvm::make_unique(*bug, os.str(), event.SinkNode)); } static bool IsNSError(QualType T, IdentifierInfo *II) { diff --git a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp index bf6b3e3e87cf8..02bc3d2900126 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonNullParamChecker.cpp @@ -35,9 +35,11 @@ class NonNullParamChecker void checkPreCall(const CallEvent &Call, CheckerContext &C) const; - std::unique_ptr - genReportNullAttrNonNull(const ExplodedNode *ErrorN, const Expr *ArgE) const; - std::unique_ptr + std::unique_ptr + genReportNullAttrNonNull(const ExplodedNode *ErrorN, + const Expr *ArgE, + unsigned IdxOfArg) const; + std::unique_ptr genReportReferenceToNullPointer(const ExplodedNode *ErrorN, const Expr *ArgE) const; }; @@ -143,7 +145,7 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, std::unique_ptr R; if (haveAttrNonNull) - R = genReportNullAttrNonNull(errorNode, ArgE); + R = genReportNullAttrNonNull(errorNode, ArgE, idx + 1); else if (haveRefTypeParam) R = genReportReferenceToNullPointer(errorNode, ArgE); @@ -177,9 +179,10 @@ void NonNullParamChecker::checkPreCall(const CallEvent &Call, C.addTransition(state); } -std::unique_ptr +std::unique_ptr NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, - const Expr *ArgE) const { + const Expr *ArgE, + unsigned IdxOfArg) const { // Lazily allocate the BugType object if it hasn't already been // created. Ownership is transferred to the BugReporter object once // the BugReport is passed to 'EmitWarning'. @@ -187,21 +190,27 @@ NonNullParamChecker::genReportNullAttrNonNull(const ExplodedNode *ErrorNode, BTAttrNonNull.reset(new BugType( this, "Argument with 'nonnull' attribute passed null", "API")); - auto R = llvm::make_unique( - *BTAttrNonNull, - "Null pointer passed as an argument to a 'nonnull' parameter", ErrorNode); + llvm::SmallString<256> SBuf; + llvm::raw_svector_ostream OS(SBuf); + OS << "Null pointer passed to " + << IdxOfArg << llvm::getOrdinalSuffix(IdxOfArg) + << " parameter expecting 'nonnull'"; + + auto R = + llvm::make_unique(*BTAttrNonNull, SBuf, ErrorNode); if (ArgE) bugreporter::trackExpressionValue(ErrorNode, ArgE, *R); return R; } -std::unique_ptr NonNullParamChecker::genReportReferenceToNullPointer( +std::unique_ptr +NonNullParamChecker::genReportReferenceToNullPointer( const ExplodedNode *ErrorNode, const Expr *ArgE) const { if (!BTNullRefArg) BTNullRefArg.reset(new BuiltinBug(this, "Dereference of null pointer")); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BTNullRefArg, "Forming reference to null pointer", ErrorNode); if (ArgE) { const Expr *ArgEDeref = bugreporter::getDerefExpr(ArgE); diff --git a/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp index dd76fd2f1294e..43dbe57b8432b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NonnullGlobalConstantsChecker.cpp @@ -106,14 +106,21 @@ bool NonnullGlobalConstantsChecker::isGlobalConstString(SVal V) const { return true; // Look through the typedefs. - while (auto *T = dyn_cast(Ty)) { - Ty = T->getDecl()->getUnderlyingType(); - - // It is sufficient for any intermediate typedef - // to be classified const. - HasConst = HasConst || Ty.isConstQualified(); - if (isNonnullType(Ty) && HasConst) - return true; + while (const Type *T = Ty.getTypePtr()) { + if (const auto *TT = dyn_cast(T)) { + Ty = TT->getDecl()->getUnderlyingType(); + // It is sufficient for any intermediate typedef + // to be classified const. + HasConst = HasConst || Ty.isConstQualified(); + if (isNonnullType(Ty) && HasConst) + return true; + } else if (const auto *AT = dyn_cast(T)) { + if (AT->getAttrKind() == attr::TypeNonNull) + return true; + Ty = AT->getModifiedType(); + } else { + return false; + } } return false; } diff --git a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp index b7bf9f3db3ff6..cdf998762c5b8 100644 --- a/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/NullabilityChecker.cpp @@ -112,11 +112,11 @@ class NullabilityChecker DefaultBool CheckNullablePassedToNonnull; DefaultBool CheckNullableReturnedFromNonnull; - CheckName CheckNameNullPassedToNonnull; - CheckName CheckNameNullReturnedFromNonnull; - CheckName CheckNameNullableDereferenced; - CheckName CheckNameNullablePassedToNonnull; - CheckName CheckNameNullableReturnedFromNonnull; + CheckerNameRef CheckNameNullPassedToNonnull; + CheckerNameRef CheckNameNullReturnedFromNonnull; + CheckerNameRef CheckNameNullableDereferenced; + CheckerNameRef CheckNameNullablePassedToNonnull; + CheckerNameRef CheckNameNullableReturnedFromNonnull; }; NullabilityChecksFilter Filter; @@ -137,9 +137,9 @@ class NullabilityChecker ID.AddPointer(Region); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: // The tracked region. @@ -163,7 +163,7 @@ class NullabilityChecker if (!BT) BT.reset(new BugType(this, "Nullability", categories::MemoryError)); - auto R = llvm::make_unique(*BT, Msg, N); + auto R = llvm::make_unique(*BT, Msg, N); if (Region) { R->markInteresting(Region); R->addVisitor(llvm::make_unique(Region)); @@ -290,10 +290,9 @@ NullabilityChecker::getTrackRegion(SVal Val, bool CheckSuperRegion) const { return dyn_cast(Region); } -std::shared_ptr -NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef NullabilityChecker::NullabilityBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); @@ -310,7 +309,7 @@ NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, // Retrieve the associated statement. const Stmt *S = TrackedNullab->getNullabilitySource(); if (!S || S->getBeginLoc().isInvalid()) { - S = PathDiagnosticLocation::getStmt(N); + S = N->getStmtForDiagnostics(); } if (!S) @@ -324,8 +323,7 @@ NullabilityChecker::NullabilityBugVisitor::VisitNode(const ExplodedNode *N, // Generate the extra diagnostic. PathDiagnosticLocation Pos(S, BRC.getSourceManager(), N->getLocationContext()); - return std::make_shared(Pos, InfoText, true, - nullptr); + return std::make_shared(Pos, InfoText, true); } /// Returns true when the value stored at the given location has been @@ -1203,12 +1201,12 @@ bool ento::shouldRegisterNullabilityBase(const LangOptions &LO) { void ento::register##name##Checker(CheckerManager &mgr) { \ NullabilityChecker *checker = mgr.getChecker(); \ checker->Filter.Check##name = true; \ - checker->Filter.CheckName##name = mgr.getCurrentCheckName(); \ + checker->Filter.CheckName##name = mgr.getCurrentCheckerName(); \ checker->NeedTracking = checker->NeedTracking || trackingRequired; \ checker->NoDiagnoseCallsToSystemHeaders = \ checker->NoDiagnoseCallsToSystemHeaders || \ mgr.getAnalyzerOptions().getCheckerBooleanOption( \ - checker, "NoDiagnoseCallsToSystemHeaders", true); \ + checker, "NoDiagnoseCallsToSystemHeaders", true); \ } \ \ bool ento::shouldRegister##name##Checker(const LangOptions &LO) { \ diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp index bd8cfb14680fe..22e4d48e86b2d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCAtSyncChecker.cpp @@ -46,8 +46,8 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, if (!BT_undef) BT_undef.reset(new BuiltinBug(this, "Uninitialized value used as mutex " "for @synchronized")); - auto report = - llvm::make_unique(*BT_undef, BT_undef->getDescription(), N); + auto report = llvm::make_unique( + *BT_undef, BT_undef->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); } @@ -70,8 +70,8 @@ void ObjCAtSyncChecker::checkPreStmt(const ObjCAtSynchronizedStmt *S, BT_null.reset(new BuiltinBug( this, "Nil value used as mutex for @synchronized() " "(no synchronization will occur)")); - auto report = - llvm::make_unique(*BT_null, BT_null->getDescription(), N); + auto report = llvm::make_unique( + *BT_null, BT_null->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *report); C.emitReport(std::move(report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp index f69a3944a56ca..023beab367188 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCContainersChecker.cpp @@ -144,10 +144,11 @@ void ObjCContainersChecker::checkPreStmt(const CallExpr *CE, if (!N) return; initBugType(); - auto R = llvm::make_unique(*BT, "Index is out of bounds", N); + auto R = llvm::make_unique( + *BT, "Index is out of bounds", N); R->addRange(IdxExpr->getSourceRange()); - bugreporter::trackExpressionValue(N, IdxExpr, *R, - /*EnableNullFPSuppression=*/false); + bugreporter::trackExpressionValue( + N, IdxExpr, *R, bugreporter::TrackingKind::Thorough, false); C.emitReport(std::move(R)); return; } diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp index 33e4d2af000dd..1870c08432de3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCMissingSuperCallChecker.cpp @@ -13,12 +13,12 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/RecursiveASTVisitor.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "llvm/ADT/SmallSet.h" diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp index 767b7bf4063c8..542266a505aea 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCSelfInitChecker.cpp @@ -159,7 +159,7 @@ void ObjCSelfInitChecker::checkForInvalidSelf(const Expr *E, CheckerContext &C, if (!BT) BT.reset(new BugType(this, "Missing \"self = [(super or self) init...]\"", categories::CoreFoundationObjectiveC)); - C.emitReport(llvm::make_unique(*BT, errorStr, N)); + C.emitReport(llvm::make_unique(*BT, errorStr, N)); } void ObjCSelfInitChecker::checkPostObjCMessage(const ObjCMethodCall &Msg, diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp index f435f00c08e79..52f7d11e9038f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCSuperDeallocChecker.cpp @@ -67,12 +67,11 @@ class SuperDeallocBRVisitor final : public BugReporterVisitor { public: SuperDeallocBRVisitor(SymbolRef ReceiverSymbol) - : ReceiverSymbol(ReceiverSymbol), - Satisfied(false) {} + : ReceiverSymbol(ReceiverSymbol), Satisfied(false) {} - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(ReceiverSymbol); @@ -188,8 +187,8 @@ void ObjCSuperDeallocChecker::reportUseAfterDealloc(SymbolRef Sym, Desc = "Use of 'self' after it has been deallocated"; // Generate the report. - std::unique_ptr BR( - new BugReport(*DoubleSuperDeallocBugType, Desc, ErrNode)); + auto BR = llvm::make_unique(*DoubleSuperDeallocBugType, + Desc, ErrNode); BR->addRange(S->getSourceRange()); BR->addVisitor(llvm::make_unique(Sym)); C.emitReport(std::move(BR)); @@ -243,9 +242,10 @@ ObjCSuperDeallocChecker::isSuperDeallocMessage(const ObjCMethodCall &M) const { return M.getSelector() == SELdealloc; } -std::shared_ptr +PathDiagnosticPieceRef SuperDeallocBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &) { + BugReporterContext &BRC, + PathSensitiveBugReport &) { if (Satisfied) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp index 4b39a97c7e8d1..cb47704515723 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ObjCUnusedIVarsChecker.cpp @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Attr.h" #include "clang/AST/DeclObjC.h" #include "clang/AST/Expr.h" @@ -20,7 +21,6 @@ #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" using namespace clang; @@ -94,7 +94,7 @@ static void Scan(IvarUsageMap& M, const ObjCContainerDecl *D) { } static void Scan(IvarUsageMap &M, const DeclContext *C, const FileID FID, - SourceManager &SM) { + const SourceManager &SM) { for (const auto *I : C->decls()) if (const auto *FD = dyn_cast(I)) { SourceLocation L = FD->getBeginLoc(); @@ -148,7 +148,7 @@ static void checkObjCUnusedIvar(const ObjCImplementationDecl *D, // FIXME: In the future hopefully we can just use the lexical DeclContext // to go from the ObjCImplementationDecl to the lexically "nested" // C functions. - SourceManager &SM = BR.getSourceManager(); + const SourceManager &SM = BR.getSourceManager(); Scan(M, D->getDeclContext(), SM.getFileID(D->getLocation()), SM); // Find ivars that are unused. diff --git a/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp index 09f2fd635b6ca..81dbde46391d1 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp @@ -337,7 +337,8 @@ class PaddingChecker : public Checker> { PathDiagnosticLocation CELoc = PathDiagnosticLocation::create(RD, BR->getSourceManager()); - auto Report = llvm::make_unique(*PaddingBug, Os.str(), CELoc); + auto Report = + llvm::make_unique(*PaddingBug, Os.str(), CELoc); Report->setDeclWithIssue(RD); Report->addRange(RD->getSourceRange()); BR->emitReport(std::move(Report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp index 03c3f4dd23570..2415b42dcb182 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerArithChecker.cpp @@ -119,12 +119,12 @@ const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region, AllocKind &AKind, CheckerContext &C) const { assert(Region); - while (Region->getKind() == MemRegion::Kind::CXXBaseObjectRegionKind) { - Region = Region->getAs()->getSuperRegion(); + while (const auto *BaseRegion = dyn_cast(Region)) { + Region = BaseRegion->getSuperRegion(); Polymorphic = true; } - if (Region->getKind() == MemRegion::Kind::ElementRegionKind) { - Region = Region->getAs()->getSuperRegion(); + if (const auto *ElemRegion = dyn_cast(Region)) { + Region = ElemRegion->getSuperRegion(); } ProgramStateRef State = C.getState(); @@ -137,7 +137,7 @@ const MemRegion *PointerArithChecker::getArrayRegion(const MemRegion *Region, } // When the region is symbolic and we do not have any information about it, // assume that this is an array to avoid false positives. - if (Region->getKind() == MemRegion::Kind::SymbolicRegionKind) + if (isa(Region)) return Region; // No AllocKind stored and not symbolic, assume that it points to a single @@ -173,8 +173,8 @@ void PointerArithChecker::reportPointerArithMisuse(const Expr *E, this, "Dangerous pointer arithmetic", "Pointer arithmetic on a pointer to base class is dangerous " "because derived and base class may have different size.")); - auto R = llvm::make_unique(*BT_polyArray, - BT_polyArray->getDescription(), N); + auto R = llvm::make_unique( + *BT_polyArray, BT_polyArray->getDescription(), N); R->addRange(E->getSourceRange()); R->markInteresting(ArrayRegion); C.emitReport(std::move(R)); @@ -196,8 +196,8 @@ void PointerArithChecker::reportPointerArithMisuse(const Expr *E, "Pointer arithmetic on non-array " "variables relies on memory layout, " "which is dangerous.")); - auto R = llvm::make_unique(*BT_pointerArith, - BT_pointerArith->getDescription(), N); + auto R = llvm::make_unique( + *BT_pointerArith, BT_pointerArith->getDescription(), N); R->addRange(SR); R->markInteresting(Region); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp index c9512f4fc42ff..321aa914e2dbf 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PointerSubChecker.cpp @@ -63,7 +63,8 @@ void PointerSubChecker::checkPreStmt(const BinaryOperator *B, new BuiltinBug(this, "Pointer subtraction", "Subtraction of two pointers that do not point to " "the same memory chunk may cause incorrect result.")); - auto R = llvm::make_unique(*BT, BT->getDescription(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); R->addRange(B->getSourceRange()); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp index 33f677e1c2583..e1786bff8887e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/PthreadLockChecker.cpp @@ -240,7 +240,7 @@ void PthreadLockChecker::AcquireLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_doublelock, "This lock has already been acquired", N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); @@ -305,7 +305,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_doubleunlock, "This lock has already been unlocked", N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); @@ -328,7 +328,7 @@ void PthreadLockChecker::ReleaseLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto report = llvm::make_unique( + auto report = llvm::make_unique( *BT_lor, "This was not the most recently acquired lock. Possible " "lock order reversal", N); report->addRange(CE->getArg(0)->getSourceRange()); @@ -399,7 +399,8 @@ void PthreadLockChecker::DestroyLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique(*BT_destroylock, Message, N); + auto Report = + llvm::make_unique(*BT_destroylock, Message, N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); } @@ -438,7 +439,8 @@ void PthreadLockChecker::InitLock(CheckerContext &C, const CallExpr *CE, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique(*BT_initlock, Message, N); + auto Report = + llvm::make_unique(*BT_initlock, Message, N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); } @@ -451,7 +453,7 @@ void PthreadLockChecker::reportUseDestroyedBug(CheckerContext &C, ExplodedNode *N = C.generateErrorNode(); if (!N) return; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_destroylock, "This lock has already been destroyed", N); Report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(Report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp index b7cbcc7d53bb5..31d2d7c125e26 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.cpp @@ -886,14 +886,19 @@ void RetainCountChecker::processNonLeakError(ProgramStateRef St, // Handle the return values of retain-count-related functions. //===----------------------------------------------------------------------===// -bool RetainCountChecker::evalCall(const CallExpr *CE, CheckerContext &C) const { +bool RetainCountChecker::evalCall(const CallEvent &Call, + CheckerContext &C) const { ProgramStateRef state = C.getState(); - const FunctionDecl *FD = C.getCalleeDecl(CE); + const auto *FD = dyn_cast_or_null(Call.getDecl()); if (!FD) return false; + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + RetainSummaryManager &SmrMgr = getSummaryManager(C); - QualType ResultTy = CE->getCallReturnType(C.getASTContext()); + QualType ResultTy = Call.getResultType(); // See if the function has 'rc_ownership_trusted_implementation' // annotate attribute. If it does, we will not inline it. diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h index 506ece1e57858..dd79bbef321c3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountChecker.h @@ -21,12 +21,12 @@ #include "clang/AST/DeclObjC.h" #include "clang/AST/ParentMap.h" #include "clang/Analysis/DomainSpecific/CocoaConventions.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/RetainSummaryManager.h" #include "clang/Basic/LangOptions.h" #include "clang/Basic/SourceManager.h" #include "clang/Analysis/SelectorExtras.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" @@ -310,7 +310,7 @@ class RetainCountChecker const CallEvent &Call, CheckerContext &C) const; - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; ProgramStateRef evalAssume(ProgramStateRef state, SVal Cond, bool Assumption) const; diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp index 927e9ae443609..2de29918bd4af 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.cpp @@ -325,22 +325,22 @@ class RefCountReportVisitor : public BugReporterVisitor { ID.AddPointer(Sym); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; - std::shared_ptr getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) override; + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + PathSensitiveBugReport &BR) override; }; class RefLeakReportVisitor : public RefCountReportVisitor { public: RefLeakReportVisitor(SymbolRef sym) : RefCountReportVisitor(sym) {} - std::shared_ptr getEndPath(BugReporterContext &BRC, - const ExplodedNode *N, - BugReport &BR) override; + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *N, + PathSensitiveBugReport &BR) override; }; } // end namespace retaincountchecker @@ -448,9 +448,9 @@ annotateStartParameter(const ExplodedNode *N, SymbolRef Sym, return std::make_shared(L, os.str()); } -std::shared_ptr -RefCountReportVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +RefCountReportVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { const auto &BT = static_cast(BR.getBugType()); const auto *Checker = @@ -709,21 +709,22 @@ static AllocationInfo GetAllocationSite(ProgramStateManager &StateMgr, LeakContext) FirstBinding = nullptr; - return AllocationInfo(AllocationNodeInCurrentOrParentContext, - FirstBinding, + return AllocationInfo(AllocationNodeInCurrentOrParentContext, FirstBinding, InterestingMethodContext); } -std::shared_ptr +PathDiagnosticPieceRef RefCountReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { + const ExplodedNode *EndN, + PathSensitiveBugReport &BR) { BR.markInteresting(Sym); return BugReporterVisitor::getDefaultEndPath(BRC, EndN, BR); } -std::shared_ptr +PathDiagnosticPieceRef RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, - const ExplodedNode *EndN, BugReport &BR) { + const ExplodedNode *EndN, + PathSensitiveBugReport &BR) { // Tell the BugReporterContext to report cases when the tracked symbol is // assigned to different variables, etc. @@ -737,13 +738,7 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, const MemRegion* FirstBinding = AllocI.R; BR.markInteresting(AllocI.InterestingMethodContext); - SourceManager& SM = BRC.getSourceManager(); - - // Compute an actual location for the leak. Sometimes a leak doesn't - // occur at an actual statement (e.g., transition between blocks; end - // of function) so we need to walk the graph and compute a real location. - const ExplodedNode *LeakN = EndN; - PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath(LeakN, SM); + PathDiagnosticLocation L = cast(BR).getEndOfPath(); std::string sbuf; llvm::raw_string_ostream os(sbuf); @@ -814,9 +809,9 @@ RefLeakReportVisitor::getEndPath(BugReporterContext &BRC, } RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, - ExplodedNode *n, SymbolRef sym, - bool isLeak) - : BugReport(D, D.getDescription(), n), Sym(sym), isLeak(isLeak) { + ExplodedNode *n, SymbolRef sym, bool isLeak) + : PathSensitiveBugReport(D, D.getDescription(), n), Sym(sym), + isLeak(isLeak) { if (!isLeak) addVisitor(llvm::make_unique(sym)); } @@ -824,7 +819,7 @@ RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, RefCountReport::RefCountReport(const RefCountBug &D, const LangOptions &LOpts, ExplodedNode *n, SymbolRef sym, StringRef endText) - : BugReport(D, D.getDescription(), endText, n) { + : PathSensitiveBugReport(D, D.getDescription(), endText, n) { addVisitor(llvm::make_unique(sym)); } @@ -873,7 +868,7 @@ void RefLeakReport::deriveAllocLocation(CheckerContext &Ctx, // FIXME: This will crash the analyzer if an allocation comes from an // implicit call (ex: a destructor call). // (Currently there are no such allocations in Cocoa, though.) - AllocStmt = PathDiagnosticLocation::getStmt(AllocNode); + AllocStmt = AllocNode->getStmtForDiagnostics(); if (!AllocStmt) { AllocBinding = nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h index ef3c75f87af5d..e9e2777540548 100644 --- a/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h +++ b/clang/lib/StaticAnalyzer/Checkers/RetainCountChecker/RetainCountDiagnostics.h @@ -14,10 +14,10 @@ #ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H #define LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_RETAINCOUNTCHECKER_DIAGNOSTICS_H +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/RetainSummaryManager.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" namespace clang { @@ -53,7 +53,7 @@ class RefCountBug : public BugType { static StringRef bugTypeToName(RefCountBugType BT); }; -class RefCountReport : public BugReport { +class RefCountReport : public PathSensitiveBugReport { protected: SymbolRef Sym; bool isLeak = false; @@ -67,16 +67,17 @@ class RefCountReport : public BugReport { ExplodedNode *n, SymbolRef sym, StringRef endText); - llvm::iterator_range getRanges() override { + ArrayRef getRanges() const override { if (!isLeak) - return BugReport::getRanges(); - return llvm::make_range(ranges_iterator(), ranges_iterator()); + return PathSensitiveBugReport::getRanges(); + return {}; } }; class RefLeakReport : public RefCountReport { const MemRegion* AllocBinding; const Stmt *AllocStmt; + PathDiagnosticLocation Location; // Finds the function declaration where a leak warning for the parameter // 'sym' should be raised. @@ -89,11 +90,14 @@ class RefLeakReport : public RefCountReport { public: RefLeakReport(const RefCountBug &D, const LangOptions &LOpts, ExplodedNode *n, SymbolRef sym, CheckerContext &Ctx); - - PathDiagnosticLocation getLocation(const SourceManager &SM) const override { + PathDiagnosticLocation getLocation() const override { assert(Location.isValid()); return Location; } + + PathDiagnosticLocation getEndOfPath() const { + return PathSensitiveBugReport::getLocation(); + } }; } // end namespace retaincountchecker diff --git a/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp index 9eb47e0526dc5..b4845f3e64f9e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReturnPointerRangeChecker.cpp @@ -79,7 +79,8 @@ void ReturnPointerRangeChecker::checkPreStmt(const ReturnStmt *RS, // reference is outside the range. // Generate a report for this bug. - auto report = llvm::make_unique(*BT, BT->getDescription(), N); + auto report = + llvm::make_unique(*BT, BT->getDescription(), N); report->addRange(RetE->getSourceRange()); C.emitReport(std::move(report)); diff --git a/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp index f55c369da67eb..97c5a8b7c153c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ReturnUndefChecker.cpp @@ -83,7 +83,8 @@ static void emitBug(CheckerContext &C, BuiltinBug &BT, const Expr *RetE, if (!N) return; - auto Report = llvm::make_unique(BT, BT.getDescription(), N); + auto Report = + llvm::make_unique(BT, BT.getDescription(), N); Report->addRange(RetE->getSourceRange()); bugreporter::trackExpressionValue(N, TrackingE ? TrackingE : RetE, *Report); diff --git a/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp new file mode 100644 index 0000000000000..103208d8b5a51 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/ReturnValueChecker.cpp @@ -0,0 +1,170 @@ +//===- ReturnValueChecker - Applies guaranteed return values ----*- 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 +// +//===----------------------------------------------------------------------===// +// +// This defines ReturnValueChecker, which checks for calls with guaranteed +// boolean return value. It ensures the return value of each function call. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" +#include "llvm/ADT/Optional.h" +#include "llvm/ADT/SmallVector.h" + +using namespace clang; +using namespace ento; + +namespace { +class ReturnValueChecker : public Checker { +public: + // It sets the predefined invariant ('CDM') if the current call not break it. + void checkPostCall(const CallEvent &Call, CheckerContext &C) const; + + // It reports whether a predefined invariant ('CDM') is broken. + void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; + +private: + // The pairs are in the following form: {{{class, call}}, return value} + const CallDescriptionMap CDM = { + // These are known in the LLVM project: 'Error()' + {{{"ARMAsmParser", "Error"}}, true}, + {{{"HexagonAsmParser", "Error"}}, true}, + {{{"LLLexer", "Error"}}, true}, + {{{"LLParser", "Error"}}, true}, + {{{"MCAsmParser", "Error"}}, true}, + {{{"MCAsmParserExtension", "Error"}}, true}, + {{{"TGParser", "Error"}}, true}, + {{{"X86AsmParser", "Error"}}, true}, + // 'TokError()' + {{{"LLParser", "TokError"}}, true}, + {{{"MCAsmParser", "TokError"}}, true}, + {{{"MCAsmParserExtension", "TokError"}}, true}, + {{{"TGParser", "TokError"}}, true}, + // 'error()' + {{{"MIParser", "error"}}, true}, + {{{"WasmAsmParser", "error"}}, true}, + {{{"WebAssemblyAsmParser", "error"}}, true}, + // Other + {{{"AsmParser", "printError"}}, true}}; +}; +} // namespace + +static std::string getName(const CallEvent &Call) { + std::string Name = ""; + if (const auto *MD = dyn_cast(Call.getDecl())) + if (const CXXRecordDecl *RD = MD->getParent()) + Name += RD->getNameAsString() + "::"; + + Name += Call.getCalleeIdentifier()->getName(); + return Name; +} + +// The predefinitions ('CDM') could break due to the ever growing code base. +// Check for the expected invariants and see whether they apply. +static Optional isInvariantBreak(bool ExpectedValue, SVal ReturnV, + CheckerContext &C) { + auto ReturnDV = ReturnV.getAs(); + if (!ReturnDV) + return None; + + if (ExpectedValue) + return C.getState()->isNull(*ReturnDV).isConstrainedTrue(); + + return C.getState()->isNull(*ReturnDV).isConstrainedFalse(); +} + +void ReturnValueChecker::checkPostCall(const CallEvent &Call, + CheckerContext &C) const { + const bool *RawExpectedValue = CDM.lookup(Call); + if (!RawExpectedValue) + return; + + SVal ReturnV = Call.getReturnValue(); + bool ExpectedValue = *RawExpectedValue; + Optional IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); + if (!IsInvariantBreak) + return; + + // If the invariant is broken it is reported by 'checkEndFunction()'. + if (*IsInvariantBreak) + return; + + std::string Name = getName(Call); + const NoteTag *CallTag = C.getNoteTag( + [Name, ExpectedValue](BugReport &) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + Out << '\'' << Name << "' returns " + << (ExpectedValue ? "true" : "false"); + return Out.str(); + }, + /*IsPrunable=*/true); + + ProgramStateRef State = C.getState(); + State = State->assume(ReturnV.castAs(), ExpectedValue); + C.addTransition(State, CallTag); +} + +void ReturnValueChecker::checkEndFunction(const ReturnStmt *RS, + CheckerContext &C) const { + if (!RS || !RS->getRetValue()) + return; + + // We cannot get the caller in the top-frame. + const StackFrameContext *SFC = C.getStackFrame(); + if (C.getStackFrame()->inTopFrame()) + return; + + ProgramStateRef State = C.getState(); + CallEventManager &CMgr = C.getStateManager().getCallEventManager(); + CallEventRef<> Call = CMgr.getCaller(SFC, State); + if (!Call) + return; + + const bool *RawExpectedValue = CDM.lookup(*Call); + if (!RawExpectedValue) + return; + + SVal ReturnV = State->getSVal(RS->getRetValue(), C.getLocationContext()); + bool ExpectedValue = *RawExpectedValue; + Optional IsInvariantBreak = isInvariantBreak(ExpectedValue, ReturnV, C); + if (!IsInvariantBreak) + return; + + // If the invariant is appropriate it is reported by 'checkPostCall()'. + if (!*IsInvariantBreak) + return; + + std::string Name = getName(*Call); + const NoteTag *CallTag = C.getNoteTag( + [Name, ExpectedValue](BugReport &BR) -> std::string { + SmallString<128> Msg; + llvm::raw_svector_ostream Out(Msg); + + // The following is swapped because the invariant is broken. + Out << '\'' << Name << "' returns " + << (ExpectedValue ? "false" : "true"); + + return Out.str(); + }, + /*IsPrunable=*/false); + + C.addTransition(State, CallTag); +} + +void ento::registerReturnValueChecker(CheckerManager &Mgr) { + Mgr.registerChecker(); +} + +bool ento::shouldRegisterReturnValueChecker(const LangOptions &LO) { + return true; +} diff --git a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp index ec5e9622c236f..e4485ec298e49 100644 --- a/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/SimpleStreamChecker.cpp @@ -206,8 +206,8 @@ void SimpleStreamChecker::reportDoubleClose(SymbolRef FileDescSym, return; // Generate the report. - auto R = llvm::make_unique(*DoubleCloseBugType, - "Closing a previously closed file stream", ErrNode); + auto R = llvm::make_unique( + *DoubleCloseBugType, "Closing a previously closed file stream", ErrNode); R->addRange(Call.getSourceRange()); R->markInteresting(FileDescSym); C.emitReport(std::move(R)); @@ -219,8 +219,9 @@ void SimpleStreamChecker::reportLeaks(ArrayRef LeakedStreams, // Attach bug reports to the leak node. // TODO: Identify the leaked file descriptor. for (SymbolRef LeakedStream : LeakedStreams) { - auto R = llvm::make_unique(*LeakBugType, - "Opened file is never closed; potential resource leak", ErrNode); + auto R = llvm::make_unique( + *LeakBugType, "Opened file is never closed; potential resource leak", + ErrNode); R->markInteresting(LeakedStream); C.emitReport(std::move(R)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp index 4b321f0f6aa99..fd372aafa50d9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/SmartPtrModeling.cpp @@ -26,32 +26,30 @@ using namespace ento; namespace { class SmartPtrModeling : public Checker { - bool isNullAfterMoveMethod(const CXXInstanceCall *Call) const; + bool isNullAfterMoveMethod(const CallEvent &Call) const; public: - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; }; } // end of anonymous namespace -bool SmartPtrModeling::isNullAfterMoveMethod( - const CXXInstanceCall *Call) const { +bool SmartPtrModeling::isNullAfterMoveMethod(const CallEvent &Call) const { // TODO: Update CallDescription to support anonymous calls? // TODO: Handle other methods, such as .get() or .release(). // But once we do, we'd need a visitor to explain null dereferences // that are found via such modeling. - const auto *CD = dyn_cast_or_null(Call->getDecl()); + const auto *CD = dyn_cast_or_null(Call.getDecl()); return CD && CD->getConversionType()->isBooleanType(); } -bool SmartPtrModeling::evalCall(const CallExpr *CE, CheckerContext &C) const { - CallEventRef<> CallRef = C.getStateManager().getCallEventManager().getCall( - CE, C.getState(), C.getLocationContext()); - const auto *Call = dyn_cast_or_null(CallRef); - if (!Call || !isNullAfterMoveMethod(Call)) +bool SmartPtrModeling::evalCall(const CallEvent &Call, + CheckerContext &C) const { + if (!isNullAfterMoveMethod(Call)) return false; ProgramStateRef State = C.getState(); - const MemRegion *ThisR = Call->getCXXThisVal().getAsRegion(); + const MemRegion *ThisR = + cast(&Call)->getCXXThisVal().getAsRegion(); if (!move::isMovedFrom(State, ThisR)) { // TODO: Model this case as well. At least, avoid invalidation of globals. @@ -60,8 +58,8 @@ bool SmartPtrModeling::evalCall(const CallExpr *CE, CheckerContext &C) const { // TODO: Add a note to bug reports describing this decision. C.addTransition( - State->BindExpr(CE, C.getLocationContext(), - C.getSValBuilder().makeZeroVal(CE->getType()))); + State->BindExpr(Call.getOriginExpr(), C.getLocationContext(), + C.getSValBuilder().makeZeroVal(Call.getResultType()))); return true; } diff --git a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp index b93bed5c30973..95bfc008aedd4 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StackAddrEscapeChecker.cpp @@ -162,7 +162,8 @@ void StackAddrEscapeChecker::EmitStackError(CheckerContext &C, llvm::raw_svector_ostream os(buf); SourceRange range = genName(os, R, C.getASTContext()); os << " returned to caller"; - auto report = llvm::make_unique(*BT_returnstack, os.str(), N); + auto report = + llvm::make_unique(*BT_returnstack, os.str(), N); report->addRange(RetE->getSourceRange()); if (range.isValid()) report->addRange(range); @@ -199,8 +200,8 @@ void StackAddrEscapeChecker::checkAsyncExecutedBlockCaptures( llvm::raw_svector_ostream Out(Buf); SourceRange Range = genName(Out, Region, C.getASTContext()); Out << " is captured by an asynchronously-executed block"; - auto Report = - llvm::make_unique(*BT_capturedstackasync, Out.str(), N); + auto Report = llvm::make_unique( + *BT_capturedstackasync, Out.str(), N); if (Range.isValid()) Report->addRange(Range); C.emitReport(std::move(Report)); @@ -222,8 +223,8 @@ void StackAddrEscapeChecker::checkReturnedBlockCaptures( llvm::raw_svector_ostream Out(Buf); SourceRange Range = genName(Out, Region, C.getASTContext()); Out << " is captured by a returned block"; - auto Report = - llvm::make_unique(*BT_capturedstackret, Out.str(), N); + auto Report = llvm::make_unique(*BT_capturedstackret, + Out.str(), N); if (Range.isValid()) Report->addRange(Range); C.emitReport(std::move(Report)); @@ -351,7 +352,8 @@ void StackAddrEscapeChecker::checkEndFunction(const ReturnStmt *RS, const VarRegion *VR = cast(P.first->getBaseRegion()); Out << *VR->getDecl() << "' upon returning to the caller. This will be a dangling reference"; - auto Report = llvm::make_unique(*BT_stackleak, Out.str(), N); + auto Report = + llvm::make_unique(*BT_stackleak, Out.str(), N); if (Range.isValid()) Report->addRange(Range); diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp index 13f39bd8e72c3..2cdee8da375e7 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp @@ -224,7 +224,7 @@ class StdLibraryFunctionsChecker : public Checker { public: void checkPostCall(const CallEvent &Call, CheckerContext &C) const; - bool evalCall(const CallExpr *CE, CheckerContext &C) const; + bool evalCall(const CallEvent &Call, CheckerContext &C) const; private: Optional findFunctionSummary(const FunctionDecl *FD, @@ -367,12 +367,16 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call, } } -bool StdLibraryFunctionsChecker::evalCall(const CallExpr *CE, +bool StdLibraryFunctionsChecker::evalCall(const CallEvent &Call, CheckerContext &C) const { - const FunctionDecl *FD = dyn_cast_or_null(CE->getCalleeDecl()); + const auto *FD = dyn_cast_or_null(Call.getDecl()); if (!FD) return false; + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + Optional FoundSummary = findFunctionSummary(FD, CE, C); if (!FoundSummary) return false; diff --git a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp index 1e690bc6ca4ed..a9d9ab3ae8e76 100644 --- a/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/StreamChecker.cpp @@ -14,6 +14,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" @@ -71,7 +72,7 @@ class StreamChecker : public Checker(Call.getDecl()); if (!FD || FD->getKind() != Decl::Function) return false; + const auto *CE = dyn_cast_or_null(Call.getOriginExpr()); + if (!CE) + return false; + ASTContext &Ctx = C.getASTContext(); if (!II_fopen) II_fopen = &Ctx.Idents.get("fopen"); @@ -272,7 +277,7 @@ void StreamChecker::Fseek(CheckerContext &C, const CallExpr *CE) const { new BuiltinBug(this, "Illegal whence argument", "The whence argument to fseek() should be " "SEEK_SET, SEEK_END, or SEEK_CUR.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_illegalwhence, BT_illegalwhence->getDescription(), N)); } } @@ -340,7 +345,7 @@ ProgramStateRef StreamChecker::CheckNullStream(SVal SV, ProgramStateRef state, if (!BT_nullfp) BT_nullfp.reset(new BuiltinBug(this, "NULL stream pointer", "Stream pointer might be NULL.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_nullfp, BT_nullfp->getDescription(), N)); } return nullptr; @@ -370,7 +375,7 @@ ProgramStateRef StreamChecker::CheckDoubleClose(const CallExpr *CE, BT_doubleclose.reset(new BuiltinBug( this, "Double fclose", "Try to close a file Descriptor already" " closed. Cause undefined behaviour.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_doubleclose, BT_doubleclose->getDescription(), N)); } return nullptr; @@ -400,7 +405,7 @@ void StreamChecker::checkDeadSymbols(SymbolReaper &SymReaper, BT_ResourceLeak.reset( new BuiltinBug(this, "Resource Leak", "Opened File never closed. Potential Resource leak.")); - C.emitReport(llvm::make_unique( + C.emitReport(llvm::make_unique( *BT_ResourceLeak, BT_ResourceLeak->getDescription(), N)); } } diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp index bc120949ee4f1..574d4ed1e600c 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.cpp @@ -204,16 +204,16 @@ bool taint::isTainted(ProgramStateRef State, SymbolRef Sym, TaintTagType Kind) { return false; } -std::shared_ptr -TaintBugVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &BR) { +PathDiagnosticPieceRef TaintBugVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { // Find the ExplodedNode where the taint was first introduced if (!isTainted(N->getState(), V) || isTainted(N->getFirstPred()->getState(), V)) return nullptr; - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; diff --git a/clang/lib/StaticAnalyzer/Checkers/Taint.h b/clang/lib/StaticAnalyzer/Checkers/Taint.h index 72cf6a79d52c4..8940916c19331 100644 --- a/clang/lib/StaticAnalyzer/Checkers/Taint.h +++ b/clang/lib/StaticAnalyzer/Checkers/Taint.h @@ -89,9 +89,9 @@ class TaintBugVisitor final : public BugReporterVisitor { TaintBugVisitor(const SVal V) : V(V) {} void Profile(llvm::FoldingSetNodeID &ID) const override { ID.Add(V); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; }; } // namespace taint diff --git a/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp index 094762e2faf83..db104274d0942 100644 --- a/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/TaintTesterChecker.cpp @@ -52,7 +52,7 @@ void TaintTesterChecker::checkPostStmt(const Expr *E, if (isTainted(State, E, C.getLocationContext())) { if (ExplodedNode *N = C.generateNonFatalErrorNode()) { initBugType(); - auto report = llvm::make_unique(*BT, "tainted",N); + auto report = llvm::make_unique(*BT, "tainted", N); report->addRange(E->getSourceRange()); C.emitReport(std::move(report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp index 7a33845a6a266..3b40ec8e9631f 100644 --- a/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/TestAfterDivZeroChecker.cpp @@ -69,9 +69,9 @@ class DivisionBRVisitor : public BugReporterVisitor { ID.Add(SFC); } - std::shared_ptr VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *Succ, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; }; class TestAfterDivZeroChecker @@ -92,9 +92,9 @@ class TestAfterDivZeroChecker REGISTER_SET_WITH_PROGRAMSTATE(DivZeroMap, ZeroState) -std::shared_ptr -DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +DivisionBRVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { if (Satisfied) return nullptr; @@ -167,7 +167,7 @@ void TestAfterDivZeroChecker::reportBug(SVal Val, CheckerContext &C) const { if (!DivZeroBug) DivZeroBug.reset(new BuiltinBug(this, "Division by zero")); - auto R = llvm::make_unique( + auto R = llvm::make_unique( *DivZeroBug, "Value being compared against zero has already been used " "for division", N); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp index 3a4a1dbf641bb..cb974b0b23116 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefBranchChecker.cpp @@ -96,7 +96,8 @@ void UndefBranchChecker::checkBranchCondition(const Stmt *Condition, Ex = FindIt.FindExpr(Ex); // Emit the bug report. - auto R = llvm::make_unique(*BT, BT->getDescription(), N); + auto R = llvm::make_unique( + *BT, BT->getDescription(), N); bugreporter::trackExpressionValue(N, Ex, *R); R->addRange(Ex->getSourceRange()); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp index c787ef58660ab..0a4dc86ae34ac 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefCapturedBlockVarChecker.cpp @@ -83,11 +83,12 @@ UndefCapturedBlockVarChecker::checkPostStmt(const BlockExpr *BE, os << "Variable '" << VD->getName() << "' is uninitialized when captured by block"; - auto R = llvm::make_unique(*BT, os.str(), N); + auto R = llvm::make_unique(*BT, os.str(), N); if (const Expr *Ex = FindBlockDeclRefExpr(BE->getBody(), VD)) R->addRange(Ex->getSourceRange()); R->addVisitor(llvm::make_unique( - *V, VR, /*EnableNullFPSuppression*/ false)); + *V, VR, /*EnableNullFPSuppression*/ false, + bugreporter::TrackingKind::Thorough)); R->disablePathPruning(); // need location of block C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp index 1ae287d39f11a..fd31f537c5c4d 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefResultChecker.cpp @@ -170,7 +170,7 @@ void UndefResultChecker::checkPostStmt(const BinaryOperator *B, << "' expression is undefined"; } } - auto report = llvm::make_unique(*BT, OS.str(), N); + auto report = llvm::make_unique(*BT, OS.str(), N); if (Ex) { report->addRange(Ex->getSourceRange()); bugreporter::trackExpressionValue(N, Ex, *report); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp index 4c517d6f05622..c91e22b50fbc3 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedArraySubscriptChecker.cpp @@ -52,7 +52,8 @@ UndefinedArraySubscriptChecker::checkPreStmt(const ArraySubscriptExpr *A, BT.reset(new BuiltinBug(this, "Array subscript is undefined")); // Generate a report for this bug. - auto R = llvm::make_unique(*BT, BT->getName(), N); + auto R = + llvm::make_unique(*BT, BT->getDescription(), N); R->addRange(A->getIdx()->getSourceRange()); bugreporter::trackExpressionValue(N, A->getIdx(), *R); C.emitReport(std::move(R)); diff --git a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp index d32d2a4042de7..347483f7f109b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UndefinedAssignmentChecker.cpp @@ -85,7 +85,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, } if (const DeclStmt *DS = dyn_cast(StoreE)) { - const VarDecl *VD = dyn_cast(DS->getSingleDecl()); + const VarDecl *VD = cast(DS->getSingleDecl()); ex = VD->getInit(); } @@ -108,7 +108,7 @@ void UndefinedAssignmentChecker::checkBind(SVal location, SVal val, if (OS.str().empty()) OS << DefaultMsg; - auto R = llvm::make_unique(*BT, OS.str(), N); + auto R = llvm::make_unique(*BT, OS.str(), N); if (ex) { R->addRange(ex->getSourceRange()); bugreporter::trackExpressionValue(N, ex, *R); diff --git a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp index 9d608c12d19be..9f0122a1da7a9 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedObjectChecker.cpp @@ -24,7 +24,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" using namespace clang; using namespace clang::ento; @@ -187,7 +187,7 @@ void UninitializedObjectChecker::checkEndFunction( if (Opts.ShouldConvertNotesToWarnings) { for (const auto &Pair : UninitFields) { - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_uninitField, Pair.second, Node, LocUsedForUniqueing, Node->getLocationContext()->getDecl()); Context.emitReport(std::move(Report)); @@ -201,7 +201,7 @@ void UninitializedObjectChecker::checkEndFunction( << (UninitFields.size() == 1 ? "" : "s") << " at the end of the constructor call"; - auto Report = llvm::make_unique( + auto Report = llvm::make_unique( *BT_uninitField, WarningOS.str(), Node, LocUsedForUniqueing, Node->getLocationContext()->getDecl()); diff --git a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp index a5dc250104f32..f0dd0bf813aff 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UninitializedObject/UninitializedPointee.cpp @@ -18,7 +18,7 @@ #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" using namespace clang; using namespace clang::ento; @@ -260,12 +260,13 @@ static llvm::Optional dereference(ProgramStateRef State, break; } - while (R->getAs()) { + while (isa(R)) { NeedsCastBack = true; - - if (!isa(R->getSuperRegion())) + const auto *SuperR = dyn_cast(R->getSuperRegion()); + if (!SuperR) break; - R = R->getSuperRegion()->getAs(); + + R = SuperR; } return DereferenceInfo{R, NeedsCastBack, /*IsCyclic*/ false}; diff --git a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp index 2ccb519891f37..96ec8d90af50e 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnixAPIChecker.cpp @@ -135,7 +135,7 @@ void UnixAPIMisuseChecker::ReportOpenBug(CheckerContext &C, LazyInitialize(this, BT_open, "Improper use of 'open'"); - auto Report = llvm::make_unique(*BT_open, Msg, N); + auto Report = llvm::make_unique(*BT_open, Msg, N); Report->addRange(SR); C.emitReport(std::move(Report)); } @@ -304,7 +304,8 @@ void UnixAPIMisuseChecker::CheckPthreadOnce(CheckerContext &C, LazyInitialize(this, BT_pthreadOnce, "Improper use of 'pthread_once'"); - auto report = llvm::make_unique(*BT_pthreadOnce, os.str(), N); + auto report = + llvm::make_unique(*BT_pthreadOnce, os.str(), N); report->addRange(CE->getArg(0)->getSourceRange()); C.emitReport(std::move(report)); } @@ -347,7 +348,8 @@ bool UnixAPIPortabilityChecker::ReportZeroByteAllocation( SmallString<256> S; llvm::raw_svector_ostream os(S); os << "Call to '" << fn_name << "' has an allocation size of 0 bytes"; - auto report = llvm::make_unique(*BT_mallocZero, os.str(), N); + auto report = + llvm::make_unique(*BT_mallocZero, os.str(), N); report->addRange(arg->getSourceRange()); bugreporter::trackExpressionValue(N, arg, *report); diff --git a/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp index 0b0bf8465c9dd..65dd82675df9b 100644 --- a/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/UnreachableCodeChecker.cpp @@ -55,7 +55,7 @@ void UnreachableCodeChecker::checkEndAnalysis(ExplodedGraph &G, const Decl *D = nullptr; CFG *C = nullptr; - ParentMap *PM = nullptr; + const ParentMap *PM = nullptr; const LocationContext *LC = nullptr; // Iterate over ExplodedGraph for (ExplodedGraph::node_iterator I = G.nodes_begin(), E = G.nodes_end(); diff --git a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp index 1630896c3b607..632beb9feee41 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VLASizeChecker.cpp @@ -72,7 +72,7 @@ void VLASizeChecker::reportBug( break; } - auto report = llvm::make_unique(*BT, os.str(), N); + auto report = llvm::make_unique(*BT, os.str(), N); report->addVisitor(std::move(Visitor)); report->addRange(SizeE->getSourceRange()); bugreporter::trackExpressionValue(N, SizeE, *report); diff --git a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp index 13ad3d98e8ebb..7e78d52f3d223 100644 --- a/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/ValistChecker.cpp @@ -46,7 +46,7 @@ class ValistChecker : public Checker, }; DefaultBool ChecksEnabled[CK_NumCheckKinds]; - CheckName CheckNames[CK_NumCheckKinds]; + CheckerNameRef CheckNames[CK_NumCheckKinds]; void checkPreStmt(const VAArgExpr *VAA, CheckerContext &C) const; void checkPreCall(const CallEvent &Call, CheckerContext &C) const; @@ -77,20 +77,20 @@ class ValistChecker : public Checker, ID.AddPointer(&X); ID.AddPointer(Reg); } - std::shared_ptr - getEndPath(BugReporterContext &BRC, const ExplodedNode *EndPathNode, - BugReport &BR) override { + PathDiagnosticPieceRef getEndPath(BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + PathSensitiveBugReport &BR) override { if (!IsLeak) return nullptr; - PathDiagnosticLocation L = PathDiagnosticLocation::createEndOfPath( - EndPathNode, BRC.getSourceManager()); + PathDiagnosticLocation L = BR.getLocation(); // Do not add the statement itself as a range in case of leak. - return std::make_shared(L, BR.getDescription(), false); + return std::make_shared(L, BR.getDescription(), + false); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; private: const MemRegion *Reg; @@ -115,7 +115,9 @@ const SmallVector // vswprintf is the wide version of vsnprintf, // vsprintf has no wide version {{"vswscanf", 3}, 2}}; -const CallDescription ValistChecker::VaStart("__builtin_va_start", 2), + +const CallDescription + ValistChecker::VaStart("__builtin_va_start", /*Args=*/2, /*Params=*/1), ValistChecker::VaCopy("__builtin_va_copy", 2), ValistChecker::VaEnd("__builtin_va_end", 1); } // end anonymous namespace @@ -253,7 +255,7 @@ void ValistChecker::reportUninitializedAccess(const MemRegion *VAList, BT_uninitaccess.reset(new BugType(CheckNames[CK_Uninitialized], "Uninitialized va_list", categories::MemoryError)); - auto R = llvm::make_unique(*BT_uninitaccess, Msg, N); + auto R = llvm::make_unique(*BT_uninitaccess, Msg, N); R->markInteresting(VAList); R->addVisitor(llvm::make_unique(VAList)); C.emitReport(std::move(R)); @@ -282,7 +284,7 @@ void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists, const ExplodedNode *StartNode = getStartCallSite(N, Reg); PathDiagnosticLocation LocUsedForUniqueing; - if (const Stmt *StartCallStmt = PathDiagnosticLocation::getStmt(StartNode)) + if (const Stmt *StartCallStmt = StartNode->getStmtForDiagnostics()) LocUsedForUniqueing = PathDiagnosticLocation::createBegin( StartCallStmt, C.getSourceManager(), StartNode->getLocationContext()); @@ -294,7 +296,7 @@ void ValistChecker::reportLeakedVALists(const RegionVector &LeakedVALists, OS << " " << VariableName; OS << Msg2; - auto R = llvm::make_unique( + auto R = llvm::make_unique( *BT_leakedvalist, OS.str(), N, LocUsedForUniqueing, StartNode->getLocationContext()->getDecl()); R->markInteresting(Reg); @@ -373,13 +375,12 @@ void ValistChecker::checkVAListEndCall(const CallEvent &Call, C.addTransition(State); } -std::shared_ptr ValistChecker::ValistBugVisitor::VisitNode( - const ExplodedNode *N, BugReporterContext &BRC, - BugReport &) { +PathDiagnosticPieceRef ValistChecker::ValistBugVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { ProgramStateRef State = N->getState(); ProgramStateRef StatePrev = N->getFirstPred()->getState(); - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); if (!S) return nullptr; @@ -411,7 +412,8 @@ bool ento::shouldRegisterValistBase(const LangOptions &LO) { void ento::register##name##Checker(CheckerManager &mgr) { \ ValistChecker *checker = mgr.getChecker(); \ checker->ChecksEnabled[ValistChecker::CK_##name] = true; \ - checker->CheckNames[ValistChecker::CK_##name] = mgr.getCurrentCheckName(); \ + checker->CheckNames[ValistChecker::CK_##name] = \ + mgr.getCurrentCheckerName(); \ } \ \ bool ento::shouldRegister##name##Checker(const LangOptions &LO) { \ diff --git a/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp index 40d14aa5c7d44..3f8c040536944 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VforkChecker.cpp @@ -132,7 +132,7 @@ void VforkChecker::reportBug(const char *What, CheckerContext &C, if (Details) os << "; " << Details; - auto Report = llvm::make_unique(*BT, os.str(), N); + auto Report = llvm::make_unique(*BT, os.str(), N); // TODO: mark vfork call in BugReportVisitor C.emitReport(std::move(Report)); } diff --git a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp index 762c9c1c8d7aa..cd3af0534a841 100644 --- a/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp +++ b/clang/lib/StaticAnalyzer/Checkers/VirtualCallChecker.cpp @@ -6,7 +6,7 @@ // //===----------------------------------------------------------------------===// // -// This file defines a checker that checks virtual function calls during +// This file defines a checker that checks virtual method calls during // construction or destruction of C++ objects. // //===----------------------------------------------------------------------===// @@ -40,11 +40,10 @@ template <> struct FoldingSetTrait { namespace { class VirtualCallChecker : public Checker { - mutable std::unique_ptr BT; - public: - // The flag to determine if pure virtual functions should be issued only. - DefaultBool IsPureOnly; + // These are going to be null if the respective check is disabled. + mutable std::unique_ptr BT_Pure, BT_Impure; + bool ShowFixIts = false; void checkBeginFunction(CheckerContext &C) const; void checkEndFunction(const ReturnStmt *RS, CheckerContext &C) const; @@ -53,87 +52,13 @@ class VirtualCallChecker private: void registerCtorDtorCallInState(bool IsBeginFunction, CheckerContext &C) const; - void reportBug(StringRef Msg, bool PureError, const MemRegion *Reg, - CheckerContext &C) const; - - class VirtualBugVisitor : public BugReporterVisitor { - private: - const MemRegion *ObjectRegion; - bool Found; - - public: - VirtualBugVisitor(const MemRegion *R) : ObjectRegion(R), Found(false) {} - - void Profile(llvm::FoldingSetNodeID &ID) const override { - static int X = 0; - ID.AddPointer(&X); - ID.AddPointer(ObjectRegion); - } - - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override; - }; }; } // end namespace // GDM (generic data map) to the memregion of this for the ctor and dtor. REGISTER_MAP_WITH_PROGRAMSTATE(CtorDtorMap, const MemRegion *, ObjectState) -std::shared_ptr -VirtualCallChecker::VirtualBugVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &) { - // We need the last ctor/dtor which call the virtual function. - // The visitor walks the ExplodedGraph backwards. - if (Found) - return nullptr; - - ProgramStateRef State = N->getState(); - const LocationContext *LCtx = N->getLocationContext(); - const CXXConstructorDecl *CD = - dyn_cast_or_null(LCtx->getDecl()); - const CXXDestructorDecl *DD = - dyn_cast_or_null(LCtx->getDecl()); - - if (!CD && !DD) - return nullptr; - - ProgramStateManager &PSM = State->getStateManager(); - auto &SVB = PSM.getSValBuilder(); - const auto *MD = dyn_cast(LCtx->getDecl()); - if (!MD) - return nullptr; - auto ThiSVal = - State->getSVal(SVB.getCXXThis(MD, LCtx->getStackFrame())); - const MemRegion *Reg = ThiSVal.castAs().getRegion(); - if (!Reg) - return nullptr; - if (Reg != ObjectRegion) - return nullptr; - - const Stmt *S = PathDiagnosticLocation::getStmt(N); - if (!S) - return nullptr; - Found = true; - - std::string InfoText; - if (CD) - InfoText = "This constructor of an object of type '" + - CD->getNameAsString() + - "' has not returned when the virtual method was called"; - else - InfoText = "This destructor of an object of type '" + - DD->getNameAsString() + - "' has not returned when the virtual method was called"; - - // Generate the extra diagnostic. - PathDiagnosticLocation Pos(S, BRC.getSourceManager(), - N->getLocationContext()); - return std::make_shared(Pos, InfoText, true); -} - -// The function to check if a callexpr is a virtual function. +// The function to check if a callexpr is a virtual method call. static bool isVirtualCall(const CallExpr *CE) { bool CallIsNonVirtual = false; @@ -178,11 +103,10 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, const CXXMethodDecl *MD = dyn_cast_or_null(Call.getDecl()); if (!MD) return; - ProgramStateRef State = C.getState(); - const CallExpr *CE = dyn_cast_or_null(Call.getOriginExpr()); - if (IsPureOnly && !MD->isPure()) - return; + ProgramStateRef State = C.getState(); + // Member calls are always represented by a call-expression. + const auto *CE = cast(Call.getOriginExpr()); if (!isVirtualCall(CE)) return; @@ -190,29 +114,51 @@ void VirtualCallChecker::checkPreCall(const CallEvent &Call, const ObjectState *ObState = State->get(Reg); if (!ObState) return; - // Check if a virtual method is called. - // The GDM of constructor and destructor should be true. - if (*ObState == ObjectState::CtorCalled) { - if (IsPureOnly && MD->isPure()) - reportBug("Call to pure virtual function during construction", true, Reg, - C); - else if (!MD->isPure()) - reportBug("Call to virtual function during construction", false, Reg, C); - else - reportBug("Call to pure virtual function during construction", false, Reg, - C); + + bool IsPure = MD->isPure(); + + // At this point we're sure that we're calling a virtual method + // during construction or destruction, so we'll emit a report. + SmallString<128> Msg; + llvm::raw_svector_ostream OS(Msg); + OS << "Call to "; + if (IsPure) + OS << "pure "; + OS << "virtual method '" << MD->getParent()->getNameAsString() + << "::" << MD->getNameAsString() << "' during "; + if (*ObState == ObjectState::CtorCalled) + OS << "construction "; + else + OS << "destruction "; + if (IsPure) + OS << "has undefined behavior"; + else + OS << "bypasses virtual dispatch"; + + ExplodedNode *N = + IsPure ? C.generateErrorNode() : C.generateNonFatalErrorNode(); + if (!N) + return; + + const std::unique_ptr &BT = IsPure ? BT_Pure : BT_Impure; + if (!BT) { + // The respective check is disabled. + return; } - if (*ObState == ObjectState::DtorCalled) { - if (IsPureOnly && MD->isPure()) - reportBug("Call to pure virtual function during destruction", true, Reg, - C); - else if (!MD->isPure()) - reportBug("Call to virtual function during destruction", false, Reg, C); - else - reportBug("Call to pure virtual function during construction", false, Reg, - C); + auto Report = llvm::make_unique(*BT, OS.str(), N); + + if (ShowFixIts && !IsPure) { + // FIXME: These hints are valid only when the virtual call is made + // directly from the constructor/destructor. Otherwise the dispatch + // will work just fine from other callees, and the fix may break + // the otherwise correct program. + FixItHint Fixit = FixItHint::CreateInsertion( + CE->getBeginLoc(), MD->getParent()->getNameAsString() + "::"); + Report->addFixItHint(Fixit); } + + C.emitReport(std::move(Report)); } void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, @@ -254,34 +200,37 @@ void VirtualCallChecker::registerCtorDtorCallInState(bool IsBeginFunction, } } -void VirtualCallChecker::reportBug(StringRef Msg, bool IsSink, - const MemRegion *Reg, - CheckerContext &C) const { - ExplodedNode *N; - if (IsSink) - N = C.generateErrorNode(); - else - N = C.generateNonFatalErrorNode(); +void ento::registerVirtualCallModeling(CheckerManager &Mgr) { + Mgr.registerChecker(); +} - if (!N) - return; - if (!BT) - BT.reset(new BugType( - this, "Call to virtual function during construction or destruction", - "C++ Object Lifecycle")); - - auto Reporter = llvm::make_unique(*BT, Msg, N); - Reporter->addVisitor(llvm::make_unique(Reg)); - C.emitReport(std::move(Reporter)); +void ento::registerPureVirtualCallChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.getChecker(); + Chk->BT_Pure = llvm::make_unique(Mgr.getCurrentCheckerName(), + "Pure virtual method call", + categories::CXXObjectLifecycle); } -void ento::registerVirtualCallChecker(CheckerManager &mgr) { - VirtualCallChecker *checker = mgr.registerChecker(); +void ento::registerVirtualCallChecker(CheckerManager &Mgr) { + auto *Chk = Mgr.getChecker(); + if (!Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Mgr.getCurrentCheckerName(), "PureOnly")) { + Chk->BT_Impure = llvm::make_unique( + Mgr.getCurrentCheckerName(), "Unexpected loss of virtual dispatch", + categories::CXXObjectLifecycle); + Chk->ShowFixIts = Mgr.getAnalyzerOptions().getCheckerBooleanOption( + Mgr.getCurrentCheckerName(), "ShowFixIts"); + } +} + +bool ento::shouldRegisterVirtualCallModeling(const LangOptions &LO) { + return LO.CPlusPlus; +} - checker->IsPureOnly = - mgr.getAnalyzerOptions().getCheckerBooleanOption(checker, "PureOnly"); +bool ento::shouldRegisterPureVirtualCallChecker(const LangOptions &LO) { + return LO.CPlusPlus; } bool ento::shouldRegisterVirtualCallChecker(const LangOptions &LO) { - return true; + return LO.CPlusPlus; } diff --git a/clang/lib/StaticAnalyzer/Checkers/Yaml.h b/clang/lib/StaticAnalyzer/Checkers/Yaml.h new file mode 100755 index 0000000000000..968c50e33f6d6 --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/Yaml.h @@ -0,0 +1,59 @@ +//== Yaml.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 +// +//===----------------------------------------------------------------------===// +// +// This file defines convenience functions for handling YAML configuration files +// for checkers/packages. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_LIB_STATICANALYZER_CHECKER_YAML_H +#define LLVM_CLANG_LIB_STATICANALYZER_CHECKER_YAML_H + +#include "clang/StaticAnalyzer/Core/CheckerManager.h" +#include "llvm/Support/YAMLTraits.h" + +namespace clang { +namespace ento { + +/// Read the given file from the filesystem and parse it as a yaml file. The +/// template parameter must have a yaml MappingTraits. +/// Emit diagnostic error in case of any failure. +template +llvm::Optional getConfiguration(CheckerManager &Mgr, Checker *Chk, + StringRef Option, StringRef ConfigFile) { + if (ConfigFile.trim().empty()) + return None; + + llvm::vfs::FileSystem *FS = llvm::vfs::getRealFileSystem().get(); + llvm::ErrorOr> Buffer = + FS->getBufferForFile(ConfigFile.str()); + + if (std::error_code ec = Buffer.getError()) { + Mgr.reportInvalidCheckerOptionValue(Chk, Option, + "a valid filename instead of '" + + std::string(ConfigFile) + "'"); + return None; + } + + llvm::yaml::Input Input(Buffer.get()->getBuffer()); + T Config; + Input >> Config; + + if (std::error_code ec = Input.error()) { + Mgr.reportInvalidCheckerOptionValue(Chk, Option, + "a valid yaml file: " + ec.message()); + return None; + } + + return Config; +} + +} // namespace ento +} // namespace clang + +#endif // LLVM_CLANG_LIB_STATICANALYZER_CHECKERS_MOVE_H diff --git a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp index 95f2b703cdd65..87069e5d19ea0 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalysisManager.cpp @@ -13,7 +13,7 @@ using namespace ento; void AnalysisManager::anchor() { } -AnalysisManager::AnalysisManager(ASTContext &ASTCtx, DiagnosticsEngine &diags, +AnalysisManager::AnalysisManager(ASTContext &ASTCtx, const PathDiagnosticConsumers &PDC, StoreManagerCreator storemgr, ConstraintManagerCreator constraintmgr, @@ -38,7 +38,7 @@ AnalysisManager::AnalysisManager(ASTContext &ASTCtx, DiagnosticsEngine &diags, Options.ShouldElideConstructors, /*addVirtualBaseBranches=*/true, injector), - Ctx(ASTCtx), Diags(diags), LangOpts(ASTCtx.getLangOpts()), + Ctx(ASTCtx), LangOpts(ASTCtx.getLangOpts()), PathConsumers(PDC), CreateStoreMgr(storemgr), CreateConstraintMgr(constraintmgr), CheckerMgr(checkerMgr), options(Options) { diff --git a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp index 71abe2ae6c0e8..01ac2bc83bb6b 100644 --- a/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp +++ b/clang/lib/StaticAnalyzer/Core/AnalyzerOptions.cpp @@ -30,25 +30,6 @@ using namespace clang; using namespace ento; using namespace llvm; -std::vector -AnalyzerOptions::getRegisteredCheckers(bool IncludeExperimental /* = false */) { - static const StringRef StaticAnalyzerChecks[] = { -#define GET_CHECKERS -#define CHECKER(FULLNAME, CLASS, HELPTEXT, DOC_URI, IS_HIDDEN) \ - FULLNAME, -#include "clang/StaticAnalyzer/Checkers/Checkers.inc" -#undef CHECKER -#undef GET_CHECKERS - }; - std::vector Result; - for (StringRef CheckName : StaticAnalyzerChecks) { - if (!CheckName.startswith("debug.") && - (IncludeExperimental || !CheckName.startswith("alpha."))) - Result.push_back(CheckName); - } - return Result; -} - void AnalyzerOptions::printFormattedEntry( llvm::raw_ostream &Out, std::pair EntryDescPair, diff --git a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp index 6627633f39332..9d7e942abe017 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporter.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporter.cpp @@ -24,6 +24,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/LLVM.h" #include "clang/Basic/SourceLocation.h" @@ -31,7 +32,6 @@ #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporterVisitors.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/Checker.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" @@ -71,6 +71,7 @@ using namespace clang; using namespace ento; +using namespace llvm; #define DEBUG_TYPE "BugReporter" @@ -85,23 +86,252 @@ BugReporterVisitor::~BugReporterVisitor() = default; void BugReporterContext::anchor() {} //===----------------------------------------------------------------------===// -// Helper routines for walking the ExplodedGraph and fetching statements. +// PathDiagnosticBuilder and its associated routines and helper objects. //===----------------------------------------------------------------------===// -static const Stmt *GetPreviousStmt(const ExplodedNode *N) { - for (N = N->getFirstPred(); N; N = N->getFirstPred()) - if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) - return S; +namespace { - return nullptr; +/// A (CallPiece, node assiciated with its CallEnter) pair. +using CallWithEntry = + std::pair; +using CallWithEntryStack = SmallVector; + +/// Map from each node to the diagnostic pieces visitors emit for them. +using VisitorsDiagnosticsTy = + llvm::DenseMap>; + +/// A map from PathDiagnosticPiece to the LocationContext of the inlined +/// function call it represents. +using LocationContextMap = + llvm::DenseMap; + +/// A helper class that contains everything needed to construct a +/// PathDiagnostic object. It does no much more then providing convenient +/// getters and some well placed asserts for extra security. +class PathDiagnosticConstruct { + /// The consumer we're constructing the bug report for. + const PathDiagnosticConsumer *Consumer; + /// Our current position in the bug path, which is owned by + /// PathDiagnosticBuilder. + const ExplodedNode *CurrentNode; + /// A mapping from parts of the bug path (for example, a function call, which + /// would span backwards from a CallExit to a CallEnter with the nodes in + /// between them) with the location contexts it is associated with. + LocationContextMap LCM; + const SourceManager &SM; + +public: + /// We keep stack of calls to functions as we're ascending the bug path. + /// TODO: PathDiagnostic has a stack doing the same thing, shouldn't we use + /// that instead? + CallWithEntryStack CallStack; + /// The bug report we're constructing. For ease of use, this field is kept + /// public, though some "shortcut" getters are provided for commonly used + /// methods of PathDiagnostic. + std::unique_ptr PD; + +public: + PathDiagnosticConstruct(const PathDiagnosticConsumer *PDC, + const ExplodedNode *ErrorNode, + const PathSensitiveBugReport *R); + + /// \returns the location context associated with the current position in the + /// bug path. + const LocationContext *getCurrLocationContext() const { + assert(CurrentNode && "Already reached the root!"); + return CurrentNode->getLocationContext(); + } + + /// Same as getCurrLocationContext (they should always return the same + /// location context), but works after reaching the root of the bug path as + /// well. + const LocationContext *getLocationContextForActivePath() const { + return LCM.find(&PD->getActivePath())->getSecond(); + } + + const ExplodedNode *getCurrentNode() const { return CurrentNode; } + + /// Steps the current node to its predecessor. + /// \returns whether we reached the root of the bug path. + bool ascendToPrevNode() { + CurrentNode = CurrentNode->getFirstPred(); + return static_cast(CurrentNode); + } + + const ParentMap &getParentMap() const { + return getCurrLocationContext()->getParentMap(); + } + + const SourceManager &getSourceManager() const { return SM; } + + const Stmt *getParent(const Stmt *S) const { + return getParentMap().getParent(S); + } + + void updateLocCtxMap(const PathPieces *Path, const LocationContext *LC) { + assert(Path && LC); + LCM[Path] = LC; + } + + const LocationContext *getLocationContextFor(const PathPieces *Path) const { + assert(LCM.count(Path) && + "Failed to find the context associated with these pieces!"); + return LCM.find(Path)->getSecond(); + } + + bool isInLocCtxMap(const PathPieces *Path) const { return LCM.count(Path); } + + PathPieces &getActivePath() { return PD->getActivePath(); } + PathPieces &getMutablePieces() { return PD->getMutablePieces(); } + + bool shouldAddPathEdges() const { return Consumer->shouldAddPathEdges(); } + bool shouldGenerateDiagnostics() const { + return Consumer->shouldGenerateDiagnostics(); + } + bool supportsLogicalOpControlFlow() const { + return Consumer->supportsLogicalOpControlFlow(); + } +}; + +/// Contains every contextual information needed for constructing a +/// PathDiagnostic object for a given bug report. This class and its fields are +/// immutable, and passes a BugReportConstruct object around during the +/// construction. +class PathDiagnosticBuilder : public BugReporterContext { + /// A linear path from the error node to the root. + std::unique_ptr BugPath; + /// The bug report we're describing. Visitors create their diagnostics with + /// them being the last entities being able to modify it (for example, + /// changing interestingness here would cause inconsistencies as to how this + /// file and visitors construct diagnostics), hence its const. + const PathSensitiveBugReport *R; + /// The leaf of the bug path. This isn't the same as the bug reports error + /// node, which refers to the *original* graph, not the bug path. + const ExplodedNode *const ErrorNode; + /// The diagnostic pieces visitors emitted, which is expected to be collected + /// by the time this builder is constructed. + std::unique_ptr VisitorsDiagnostics; + +public: + /// Find a non-invalidated report for a given equivalence class, and returns + /// a PathDiagnosticBuilder able to construct bug reports for different + /// consumers. Returns None if no valid report is found. + static Optional + findValidReport(ArrayRef &bugReports, + PathSensitiveBugReporter &Reporter); + + PathDiagnosticBuilder( + BugReporterContext BRC, std::unique_ptr BugPath, + PathSensitiveBugReport *r, const ExplodedNode *ErrorNode, + std::unique_ptr VisitorsDiagnostics); + + /// This function is responsible for generating diagnostic pieces that are + /// *not* provided by bug report visitors. + /// These diagnostics may differ depending on the consumer's settings, + /// and are therefore constructed separately for each consumer. + /// + /// There are two path diagnostics generation modes: with adding edges (used + /// for plists) and without (used for HTML and text). When edges are added, + /// the path is modified to insert artificially generated edges. + /// Otherwise, more detailed diagnostics is emitted for block edges, + /// explaining the transitions in words. + std::unique_ptr + generate(const PathDiagnosticConsumer *PDC) const; + +private: + void updateStackPiecesWithMessage(PathDiagnosticPieceRef P, + const CallWithEntryStack &CallStack) const; + void generatePathDiagnosticsForNode(PathDiagnosticConstruct &C, + PathDiagnosticLocation &PrevLoc) const; + + void generateMinimalDiagForBlockEdge(PathDiagnosticConstruct &C, + BlockEdge BE) const; + + PathDiagnosticPieceRef + generateDiagForGotoOP(const PathDiagnosticConstruct &C, const Stmt *S, + PathDiagnosticLocation &Start) const; + + PathDiagnosticPieceRef + generateDiagForSwitchOP(const PathDiagnosticConstruct &C, const CFGBlock *Dst, + PathDiagnosticLocation &Start) const; + + PathDiagnosticPieceRef + generateDiagForBinaryOP(const PathDiagnosticConstruct &C, const Stmt *T, + const CFGBlock *Src, const CFGBlock *DstC) const; + + PathDiagnosticLocation + ExecutionContinues(const PathDiagnosticConstruct &C) const; + + PathDiagnosticLocation + ExecutionContinues(llvm::raw_string_ostream &os, + const PathDiagnosticConstruct &C) const; + + const PathSensitiveBugReport *getBugReport() const { return R; } +}; + +} // namespace + +//===----------------------------------------------------------------------===// +// Base implementation of stack hint generators. +//===----------------------------------------------------------------------===// + +StackHintGenerator::~StackHintGenerator() = default; + +std::string StackHintGeneratorForSymbol::getMessage(const ExplodedNode *N){ + if (!N) + return getMessageForSymbolNotFound(); + + ProgramPoint P = N->getLocation(); + CallExitEnd CExit = P.castAs(); + + // FIXME: Use CallEvent to abstract this over all calls. + const Stmt *CallSite = CExit.getCalleeContext()->getCallSite(); + const auto *CE = dyn_cast_or_null(CallSite); + if (!CE) + return {}; + + // Check if one of the parameters are set to the interesting symbol. + unsigned ArgIndex = 0; + for (CallExpr::const_arg_iterator I = CE->arg_begin(), + E = CE->arg_end(); I != E; ++I, ++ArgIndex){ + SVal SV = N->getSVal(*I); + + // Check if the variable corresponding to the symbol is passed by value. + SymbolRef AS = SV.getAsLocSymbol(); + if (AS == Sym) { + return getMessageForArg(*I, ArgIndex); + } + + // Check if the parameter is a pointer to the symbol. + if (Optional Reg = SV.getAs()) { + // Do not attempt to dereference void*. + if ((*I)->getType()->isVoidPointerType()) + continue; + SVal PSV = N->getState()->getSVal(Reg->getRegion()); + SymbolRef AS = PSV.getAsLocSymbol(); + if (AS == Sym) { + return getMessageForArg(*I, ArgIndex); + } + } + } + + // Check if we are returning the interesting symbol. + SVal SV = N->getSVal(CE); + SymbolRef RetSym = SV.getAsLocSymbol(); + if (RetSym == Sym) { + return getMessageForReturn(CE); + } + + return getMessageForSymbolNotFound(); } -static inline const Stmt* -GetCurrentOrPreviousStmt(const ExplodedNode *N) { - if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) - return S; +std::string StackHintGeneratorForSymbol::getMessageForArg(const Expr *ArgE, + unsigned ArgIndex) { + // Printed parameters start at 1, not 0. + ++ArgIndex; - return GetPreviousStmt(N); + return (llvm::Twine(Msg) + " via " + std::to_string(ArgIndex) + + llvm::getOrdinalSuffix(ArgIndex) + " parameter").str(); } //===----------------------------------------------------------------------===// @@ -182,16 +412,12 @@ static void removeRedundantMsgs(PathPieces &path) { } } -/// A map from PathDiagnosticPiece to the LocationContext of the inlined -/// function call it represents. -using LocationContextMap = - llvm::DenseMap; - /// Recursively scan through a path and prune out calls and macros pieces /// that aren't needed. Return true if afterwards the path contains /// "interesting stuff" which means it shouldn't be pruned from the parent path. -static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, - LocationContextMap &LCM, +static bool removeUnneededCalls(const PathDiagnosticConstruct &C, + PathPieces &pieces, + const PathSensitiveBugReport *R, bool IsInteresting = false) { bool containsSomethingInteresting = IsInteresting; const unsigned N = pieces.size(); @@ -206,9 +432,9 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, case PathDiagnosticPiece::Call: { auto &call = cast(*piece); // Check if the location context is interesting. - assert(LCM.count(&call.path)); - if (!removeUnneededCalls(call.path, R, LCM, - R->isInteresting(LCM[&call.path]))) + if (!removeUnneededCalls( + C, call.path, R, + R->isInteresting(C.getLocationContextFor(&call.path)))) continue; containsSomethingInteresting = true; @@ -216,7 +442,7 @@ static bool removeUnneededCalls(PathPieces &pieces, BugReport *R, } case PathDiagnosticPiece::Macro: { auto ¯o = cast(*piece); - if (!removeUnneededCalls(macro.subPieces, R, LCM, IsInteresting)) + if (!removeUnneededCalls(C, macro.subPieces, R, IsInteresting)) continue; containsSomethingInteresting = true; break; @@ -345,70 +571,23 @@ static void removePiecesWithInvalidLocations(PathPieces &Pieces) { } } -//===----------------------------------------------------------------------===// -// PathDiagnosticBuilder and its associated routines and helper objects. -//===----------------------------------------------------------------------===// - -namespace { - -class PathDiagnosticBuilder : public BugReporterContext { - BugReport *R; - PathDiagnosticConsumer *PDC; - -public: - const LocationContext *LC; - - PathDiagnosticBuilder(GRBugReporter &br, - BugReport *r, InterExplodedGraphMap &Backmap, - PathDiagnosticConsumer *pdc) - : BugReporterContext(br, Backmap), R(r), PDC(pdc), - LC(r->getErrorNode()->getLocationContext()) {} +PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues( + const PathDiagnosticConstruct &C) const { + if (const Stmt *S = C.getCurrentNode()->getNextStmtForDiagnostics()) + return PathDiagnosticLocation(S, getSourceManager(), + C.getCurrLocationContext()); - PathDiagnosticLocation ExecutionContinues(const ExplodedNode *N); - - PathDiagnosticLocation ExecutionContinues(llvm::raw_string_ostream &os, - const ExplodedNode *N); - - BugReport *getBugReport() { return R; } - - Decl const &getCodeDecl() { return R->getErrorNode()->getCodeDecl(); } - - ParentMap& getParentMap() { return LC->getParentMap(); } - - const Stmt *getParent(const Stmt *S) { - return getParentMap().getParent(S); - } - - PathDiagnosticLocation getEnclosingStmtLocation(const Stmt *S); - - PathDiagnosticConsumer::PathGenerationScheme getGenerationScheme() const { - return PDC ? PDC->getGenerationScheme() : PathDiagnosticConsumer::Minimal; - } - - bool supportsLogicalOpControlFlow() const { - return PDC ? PDC->supportsLogicalOpControlFlow() : true; - } -}; - -} // namespace - -PathDiagnosticLocation -PathDiagnosticBuilder::ExecutionContinues(const ExplodedNode *N) { - if (const Stmt *S = PathDiagnosticLocation::getNextStmt(N)) - return PathDiagnosticLocation(S, getSourceManager(), LC); - - return PathDiagnosticLocation::createDeclEnd(N->getLocationContext(), + return PathDiagnosticLocation::createDeclEnd(C.getCurrLocationContext(), getSourceManager()); } -PathDiagnosticLocation -PathDiagnosticBuilder::ExecutionContinues(llvm::raw_string_ostream &os, - const ExplodedNode *N) { +PathDiagnosticLocation PathDiagnosticBuilder::ExecutionContinues( + llvm::raw_string_ostream &os, const PathDiagnosticConstruct &C) const { // Slow, but probably doesn't matter. if (os.str().empty()) os << ' '; - const PathDiagnosticLocation &Loc = ExecutionContinues(N); + const PathDiagnosticLocation &Loc = ExecutionContinues(C); if (Loc.asStmt()) os << "Execution continues on line " @@ -416,7 +595,7 @@ PathDiagnosticBuilder::ExecutionContinues(llvm::raw_string_ostream &os, << '.'; else { os << "Execution jumps to the end of the "; - const Decl *D = N->getLocationContext()->getDecl(); + const Decl *D = C.getCurrLocationContext()->getDecl(); if (isa(D)) os << "method"; else if (isa(D)) @@ -454,12 +633,14 @@ static const Stmt *getEnclosingParent(const Stmt *S, const ParentMap &PM) { } static PathDiagnosticLocation -getEnclosingStmtLocation(const Stmt *S, SourceManager &SMgr, const ParentMap &P, - const LocationContext *LC, bool allowNestedContexts) { +getEnclosingStmtLocation(const Stmt *S, const LocationContext *LC, + bool allowNestedContexts = false) { if (!S) return {}; - while (const Stmt *Parent = getEnclosingParent(S, P)) { + const SourceManager &SMgr = LC->getDecl()->getASTContext().getSourceManager(); + + while (const Stmt *Parent = getEnclosingParent(S, LC->getParentMap())) { switch (Parent->getStmtClass()) { case Stmt::BinaryOperatorClass: { const auto *B = cast(Parent); @@ -520,59 +701,51 @@ getEnclosingStmtLocation(const Stmt *S, SourceManager &SMgr, const ParentMap &P, return PathDiagnosticLocation(S, SMgr, LC); } -PathDiagnosticLocation -PathDiagnosticBuilder::getEnclosingStmtLocation(const Stmt *S) { - assert(S && "Null Stmt passed to getEnclosingStmtLocation"); - return ::getEnclosingStmtLocation(S, getSourceManager(), getParentMap(), LC, - /*allowNestedContexts=*/false); -} - //===----------------------------------------------------------------------===// // "Minimal" path diagnostic generation algorithm. //===----------------------------------------------------------------------===// -using StackDiagPair = - std::pair; -using StackDiagVector = SmallVector; - -static void updateStackPiecesWithMessage(PathDiagnosticPiece &P, - StackDiagVector &CallStack) { - // If the piece contains a special message, add it to all the call - // pieces on the active stack. - if (auto *ep = dyn_cast(&P)) { - if (ep->hasCallStackHint()) - for (const auto &I : CallStack) { - PathDiagnosticCallPiece *CP = I.first; - const ExplodedNode *N = I.second; - std::string stackMsg = ep->getCallStackMessage(N); - - // The last message on the path to final bug is the most important - // one. Since we traverse the path backwards, do not add the message - // if one has been previously added. - if (!CP->hasCallStackMessage()) - CP->setCallStackMessage(stackMsg); - } - } + +/// If the piece contains a special message, add it to all the call pieces on +/// the active stack. For example, my_malloc allocated memory, so MallocChecker +/// will construct an event at the call to malloc(), and add a stack hint that +/// an allocated memory was returned. We'll use this hint to construct a message +/// when returning from the call to my_malloc +/// +/// void *my_malloc() { return malloc(sizeof(int)); } +/// void fishy() { +/// void *ptr = my_malloc(); // returned allocated memory +/// } // leak +void PathDiagnosticBuilder::updateStackPiecesWithMessage( + PathDiagnosticPieceRef P, const CallWithEntryStack &CallStack) const { + if (R->hasCallStackHint(P)) + for (const auto &I : CallStack) { + PathDiagnosticCallPiece *CP = I.first; + const ExplodedNode *N = I.second; + std::string stackMsg = R->getCallStackMessage(P, N); + + // The last message on the path to final bug is the most important + // one. Since we traverse the path backwards, do not add the message + // if one has been previously added. + if (!CP->hasCallStackMessage()) + CP->setCallStackMessage(stackMsg); + } } static void CompactMacroExpandedPieces(PathPieces &path, const SourceManager& SM); +PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForSwitchOP( + const PathDiagnosticConstruct &C, const CFGBlock *Dst, + PathDiagnosticLocation &Start) const { -std::shared_ptr generateDiagForSwitchOP( - const ExplodedNode *N, - const CFGBlock *Dst, - const SourceManager &SM, - const LocationContext *LC, - PathDiagnosticBuilder &PDB, - PathDiagnosticLocation &Start - ) { + const SourceManager &SM = getSourceManager(); // Figure out what case arm we took. std::string sbuf; llvm::raw_string_ostream os(sbuf); PathDiagnosticLocation End; if (const Stmt *S = Dst->getLabel()) { - End = PathDiagnosticLocation(S, SM, LC); + End = PathDiagnosticLocation(S, SM, C.getCurrLocationContext()); switch (S->getStmtClass()) { default: @@ -605,7 +778,7 @@ std::shared_ptr generateDiagForSwitchOP( } if (GetRawInt) - os << LHS->EvaluateKnownConstInt(PDB.getASTContext()); + os << LHS->EvaluateKnownConstInt(getASTContext()); os << ":' at line " << End.asLocation().getExpansionLineNumber(); break; @@ -613,33 +786,29 @@ std::shared_ptr generateDiagForSwitchOP( } } else { os << "'Default' branch taken. "; - End = PDB.ExecutionContinues(os, N); + End = ExecutionContinues(os, C); } return std::make_shared(Start, End, os.str()); } +PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForGotoOP( + const PathDiagnosticConstruct &C, const Stmt *S, + PathDiagnosticLocation &Start) const { + std::string sbuf; + llvm::raw_string_ostream os(sbuf); + const PathDiagnosticLocation &End = + getEnclosingStmtLocation(S, C.getCurrLocationContext()); + os << "Control jumps to line " << End.asLocation().getExpansionLineNumber(); + return std::make_shared(Start, End, os.str()); +} -std::shared_ptr generateDiagForGotoOP( - const Stmt *S, - PathDiagnosticBuilder &PDB, - PathDiagnosticLocation &Start) { - std::string sbuf; - llvm::raw_string_ostream os(sbuf); - const PathDiagnosticLocation &End = PDB.getEnclosingStmtLocation(S); - os << "Control jumps to line " << End.asLocation().getExpansionLineNumber(); - return std::make_shared(Start, End, os.str()); +PathDiagnosticPieceRef PathDiagnosticBuilder::generateDiagForBinaryOP( + const PathDiagnosticConstruct &C, const Stmt *T, const CFGBlock *Src, + const CFGBlock *Dst) const { -} + const SourceManager &SM = getSourceManager(); -std::shared_ptr generateDiagForBinaryOP( - const ExplodedNode *N, - const Stmt *T, - const CFGBlock *Src, - const CFGBlock *Dst, - const SourceManager &SM, - PathDiagnosticBuilder &PDB, - const LocationContext *LC) { const auto *B = cast(T); std::string sbuf; llvm::raw_string_ostream os(sbuf); @@ -652,13 +821,14 @@ std::shared_ptr generateDiagForBinaryOP( if (*(Src->succ_begin() + 1) == Dst) { os << "false"; - End = PathDiagnosticLocation(B->getLHS(), SM, LC); + End = PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); Start = PathDiagnosticLocation::createOperatorLoc(B, SM); } else { os << "true"; - Start = PathDiagnosticLocation(B->getLHS(), SM, LC); - End = PDB.ExecutionContinues(N); + Start = + PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); + End = ExecutionContinues(C); } } else { assert(B->getOpcode() == BO_LOr); @@ -667,11 +837,12 @@ std::shared_ptr generateDiagForBinaryOP( if (*(Src->succ_begin() + 1) == Dst) { os << "false"; - Start = PathDiagnosticLocation(B->getLHS(), SM, LC); - End = PDB.ExecutionContinues(N); + Start = + PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); + End = ExecutionContinues(C); } else { os << "true"; - End = PathDiagnosticLocation(B->getLHS(), SM, LC); + End = PathDiagnosticLocation(B->getLHS(), SM, C.getCurrLocationContext()); Start = PathDiagnosticLocation::createOperatorLoc(B, SM); } @@ -680,11 +851,10 @@ std::shared_ptr generateDiagForBinaryOP( os.str()); } -void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, - const SourceManager &SM, - PathDiagnosticBuilder &PDB, - PathDiagnostic &PD) { - const LocationContext *LC = N->getLocationContext(); +void PathDiagnosticBuilder::generateMinimalDiagForBlockEdge( + PathDiagnosticConstruct &C, BlockEdge BE) const { + const SourceManager &SM = getSourceManager(); + const LocationContext *LC = C.getCurrLocationContext(); const CFGBlock *Src = BE.getSrc(); const CFGBlock *Dst = BE.getDst(); const Stmt *T = Src->getTerminatorStmt(); @@ -698,14 +868,13 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, case Stmt::GotoStmtClass: case Stmt::IndirectGotoStmtClass: { - if (const Stmt *S = PathDiagnosticLocation::getNextStmt(N)) - PD.getActivePath().push_front(generateDiagForGotoOP(S, PDB, Start)); + if (const Stmt *S = C.getCurrentNode()->getNextStmtForDiagnostics()) + C.getActivePath().push_front(generateDiagForGotoOP(C, S, Start)); break; } case Stmt::SwitchStmtClass: { - PD.getActivePath().push_front( - generateDiagForSwitchOP(N, Dst, SM, LC, PDB, Start)); + C.getActivePath().push_front(generateDiagForSwitchOP(C, Dst, Start)); break; } @@ -713,8 +882,8 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, case Stmt::ContinueStmtClass: { std::string sbuf; llvm::raw_string_ostream os(sbuf); - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); - PD.getActivePath().push_front( + PathDiagnosticLocation End = ExecutionContinues(os, C); + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); break; } @@ -731,24 +900,22 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, else os << "true"; - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); break; } // Determine control-flow for short-circuited '&&' and '||'. case Stmt::BinaryOperatorClass: { - if (!PDB.supportsLogicalOpControlFlow()) + if (!C.supportsLogicalOpControlFlow()) break; - std::shared_ptr Diag = - generateDiagForBinaryOP(N, T, Src, Dst, SM, PDB, LC); - PD.getActivePath().push_front(Diag); + C.getActivePath().push_front(generateDiagForBinaryOP(C, T, Src, Dst)); break; } @@ -758,21 +925,21 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, llvm::raw_string_ostream os(sbuf); os << "Loop condition is true. "; - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); + PathDiagnosticLocation End = ExecutionContinues(os, C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); } else { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Loop condition is false. Exiting loop")); } @@ -785,19 +952,19 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, llvm::raw_string_ostream os(sbuf); os << "Loop condition is false. "; - PathDiagnosticLocation End = PDB.ExecutionContinues(os, N); + PathDiagnosticLocation End = ExecutionContinues(os, C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared(Start, End, os.str())); } else { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Loop condition is true. Entering loop body")); } @@ -805,17 +972,17 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, break; case Stmt::IfStmtClass: { - PathDiagnosticLocation End = PDB.ExecutionContinues(N); + PathDiagnosticLocation End = ExecutionContinues(C); if (const Stmt *S = End.asStmt()) - End = PDB.getEnclosingStmtLocation(S); + End = getEnclosingStmtLocation(S, C.getCurrLocationContext()); if (*(Src->succ_begin() + 1) == Dst) - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Taking false branch")); else - PD.getActivePath().push_front( + C.getActivePath().push_front( std::make_shared( Start, End, "Taking true branch")); @@ -824,75 +991,6 @@ void generateMinimalDiagForBlockEdge(const ExplodedNode *N, BlockEdge BE, } } -// Cone-of-influence: support the reverse propagation of "interesting" symbols -// and values by tracing interesting calculations backwards through evaluated -// expressions along a path. This is probably overly complicated, but the idea -// is that if an expression computed an "interesting" value, the child -// expressions are also likely to be "interesting" as well (which then -// propagates to the values they in turn compute). This reverse propagation -// is needed to track interesting correlations across function call boundaries, -// where formal arguments bind to actual arguments, etc. This is also needed -// because the constraint solver sometimes simplifies certain symbolic values -// into constants when appropriate, and this complicates reasoning about -// interesting values. -using InterestingExprs = llvm::DenseSet; - -static void reversePropagateIntererstingSymbols(BugReport &R, - InterestingExprs &IE, - const ProgramState *State, - const Expr *Ex, - const LocationContext *LCtx) { - SVal V = State->getSVal(Ex, LCtx); - if (!(R.isInteresting(V) || IE.count(Ex))) - return; - - switch (Ex->getStmtClass()) { - default: - if (!isa(Ex)) - break; - LLVM_FALLTHROUGH; - case Stmt::BinaryOperatorClass: - case Stmt::UnaryOperatorClass: { - for (const Stmt *SubStmt : Ex->children()) { - if (const auto *child = dyn_cast_or_null(SubStmt)) { - IE.insert(child); - SVal ChildV = State->getSVal(child, LCtx); - R.markInteresting(ChildV); - } - } - break; - } - } - - R.markInteresting(V); -} - -static void reversePropagateInterestingSymbols(BugReport &R, - InterestingExprs &IE, - const ProgramState *State, - const LocationContext *CalleeCtx) -{ - // FIXME: Handle non-CallExpr-based CallEvents. - const StackFrameContext *Callee = CalleeCtx->getStackFrame(); - const Stmt *CallSite = Callee->getCallSite(); - if (const auto *CE = dyn_cast_or_null(CallSite)) { - if (const auto *FD = dyn_cast(CalleeCtx->getDecl())) { - FunctionDecl::param_const_iterator PI = FD->param_begin(), - PE = FD->param_end(); - CallExpr::const_arg_iterator AI = CE->arg_begin(), AE = CE->arg_end(); - for (; AI != AE && PI != PE; ++AI, ++PI) { - if (const Expr *ArgE = *AI) { - if (const ParmVarDecl *PD = *PI) { - Loc LV = State->getLValue(PD, CalleeCtx); - if (R.isInteresting(LV) || R.isInteresting(State->getRawSVal(LV))) - IE.insert(ArgE); - } - } - } - } - } -} - //===----------------------------------------------------------------------===// // Functions for determining if a loop was executed 0 times. //===----------------------------------------------------------------------===// @@ -916,7 +1014,8 @@ static bool isJumpToFalseBranch(const BlockEdge *BE) { return (*(Src->succ_begin()+1) == BE->getDst()); } -static bool isContainedByStmt(ParentMap &PM, const Stmt *S, const Stmt *SubS) { +static bool isContainedByStmt(const ParentMap &PM, const Stmt *S, + const Stmt *SubS) { while (SubS) { if (SubS == S) return true; @@ -925,7 +1024,7 @@ static bool isContainedByStmt(ParentMap &PM, const Stmt *S, const Stmt *SubS) { return false; } -static const Stmt *getStmtBeforeCond(ParentMap &PM, const Stmt *Term, +static const Stmt *getStmtBeforeCond(const ParentMap &PM, const Stmt *Term, const ExplodedNode *N) { while (N) { Optional SP = N->getLocation().getAs(); @@ -939,7 +1038,7 @@ static const Stmt *getStmtBeforeCond(ParentMap &PM, const Stmt *Term, return nullptr; } -static bool isInLoopBody(ParentMap &PM, const Stmt *S, const Stmt *Term) { +static bool isInLoopBody(const ParentMap &PM, const Stmt *S, const Stmt *Term) { const Stmt *LoopBody = nullptr; switch (Term->getStmtClass()) { case Stmt::CXXForRangeStmtClass: { @@ -1007,30 +1106,19 @@ static const Stmt *getTerminatorCondition(const CFGBlock *B) { return S; } -static const char StrEnteringLoop[] = "Entering loop body"; -static const char StrLoopBodyZero[] = "Loop body executed 0 times"; -static const char StrLoopRangeEmpty[] = - "Loop body skipped when range is empty"; -static const char StrLoopCollectionEmpty[] = - "Loop body skipped when collection is empty"; +llvm::StringLiteral StrEnteringLoop = "Entering loop body"; +llvm::StringLiteral StrLoopBodyZero = "Loop body executed 0 times"; +llvm::StringLiteral StrLoopRangeEmpty = "Loop body skipped when range is empty"; +llvm::StringLiteral StrLoopCollectionEmpty = + "Loop body skipped when collection is empty"; static std::unique_ptr -findExecutedLines(SourceManager &SM, const ExplodedNode *N); - -/// Generate diagnostics for the node \p N, -/// and write it into \p PD. -/// \p AddPathEdges Whether diagnostic consumer can generate path arrows -/// showing both row and column. -static void generatePathDiagnosticsForNode(const ExplodedNode *N, - PathDiagnostic &PD, - PathDiagnosticLocation &PrevLoc, - PathDiagnosticBuilder &PDB, - LocationContextMap &LCM, - StackDiagVector &CallStack, - InterestingExprs &IE, - bool AddPathEdges) { - ProgramPoint P = N->getLocation(); - const SourceManager& SM = PDB.getSourceManager(); +findExecutedLines(const SourceManager &SM, const ExplodedNode *N); + +void PathDiagnosticBuilder::generatePathDiagnosticsForNode( + PathDiagnosticConstruct &C, PathDiagnosticLocation &PrevLoc) const { + ProgramPoint P = C.getCurrentNode()->getLocation(); + const SourceManager &SM = getSourceManager(); // Have we encountered an entrance to a call? It may be // the case that we have not encountered a matching @@ -1038,7 +1126,7 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, // terminated within the call itself. if (auto CE = P.getAs()) { - if (AddPathEdges) { + if (C.shouldAddPathEdges()) { // Add an edge to the start of the function. const StackFrameContext *CalleeLC = CE->getCalleeContext(); const Decl *D = CalleeLC->getDecl(); @@ -1049,139 +1137,105 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, // because the exit edge comes from a statement (i.e. return), // not from declaration. if (D->hasBody()) - addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createBegin(D, SM)); + addEdgeToPath(C.getActivePath(), PrevLoc, + PathDiagnosticLocation::createBegin(D, SM)); } // Did we visit an entire call? - bool VisitedEntireCall = PD.isWithinCall(); - PD.popActivePath(); + bool VisitedEntireCall = C.PD->isWithinCall(); + C.PD->popActivePath(); - PathDiagnosticCallPiece *C; + PathDiagnosticCallPiece *Call; if (VisitedEntireCall) { - C = cast(PD.getActivePath().front().get()); + Call = cast(C.getActivePath().front().get()); } else { + // The path terminated within a nested location context, create a new + // call piece to encapsulate the rest of the path pieces. const Decl *Caller = CE->getLocationContext()->getDecl(); - C = PathDiagnosticCallPiece::construct(PD.getActivePath(), Caller); - - if (AddPathEdges) { - // Since we just transferred the path over to the call piece, - // reset the mapping from active to location context. - assert(PD.getActivePath().size() == 1 && - PD.getActivePath().front().get() == C); - LCM[&PD.getActivePath()] = nullptr; - } - - // Record the location context mapping for the path within - // the call. - assert(LCM[&C->path] == nullptr || - LCM[&C->path] == CE->getCalleeContext()); - LCM[&C->path] = CE->getCalleeContext(); - - // If this is the first item in the active path, record - // the new mapping from active path to location context. - const LocationContext *&NewLC = LCM[&PD.getActivePath()]; - if (!NewLC) - NewLC = N->getLocationContext(); - - PDB.LC = NewLC; - } - C->setCallee(*CE, SM); + Call = PathDiagnosticCallPiece::construct(C.getActivePath(), Caller); + assert(C.getActivePath().size() == 1 && + C.getActivePath().front().get() == Call); + + // Since we just transferred the path over to the call piece, reset the + // mapping of the active path to the current location context. + assert(C.isInLocCtxMap(&C.getActivePath()) && + "When we ascend to a previously unvisited call, the active path's " + "address shouldn't change, but rather should be compacted into " + "a single CallEvent!"); + C.updateLocCtxMap(&C.getActivePath(), C.getCurrLocationContext()); + + // Record the location context mapping for the path within the call. + assert(!C.isInLocCtxMap(&Call->path) && + "When we ascend to a previously unvisited call, this must be the " + "first time we encounter the caller context!"); + C.updateLocCtxMap(&Call->path, CE->getCalleeContext()); + } + Call->setCallee(*CE, SM); // Update the previous location in the active path. - PrevLoc = C->getLocation(); + PrevLoc = Call->getLocation(); - if (!CallStack.empty()) { - assert(CallStack.back().first == C); - CallStack.pop_back(); + if (!C.CallStack.empty()) { + assert(C.CallStack.back().first == Call); + C.CallStack.pop_back(); } return; } - - if (AddPathEdges) { - // Query the location context here and the previous location - // as processing CallEnter may change the active path. - PDB.LC = N->getLocationContext(); - - // Record the mapping from the active path to the location - // context. - assert(!LCM[&PD.getActivePath()] || LCM[&PD.getActivePath()] == PDB.LC); - LCM[&PD.getActivePath()] = PDB.LC; - } + assert(C.getCurrLocationContext() == C.getLocationContextForActivePath() && + "The current position in the bug path is out of sync with the " + "location context associated with the active path!"); // Have we encountered an exit from a function call? if (Optional CE = P.getAs()) { // We are descending into a call (backwards). Construct // a new call piece to contain the path pieces for that call. - auto C = PathDiagnosticCallPiece::construct(*CE, SM); + auto Call = PathDiagnosticCallPiece::construct(*CE, SM); // Record the mapping from call piece to LocationContext. - LCM[&C->path] = CE->getCalleeContext(); - - if (AddPathEdges) { - const Stmt *S = CE->getCalleeContext()->getCallSite(); - // Propagate the interesting symbols accordingly. - if (const auto *Ex = dyn_cast_or_null(S)) { - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); - } + assert(!C.isInLocCtxMap(&Call->path) && + "We just entered a call, this must've been the first time we " + "encounter its context!"); + C.updateLocCtxMap(&Call->path, CE->getCalleeContext()); + + if (C.shouldAddPathEdges()) { // Add the edge to the return site. - addEdgeToPath(PD.getActivePath(), PrevLoc, C->callReturn); + addEdgeToPath(C.getActivePath(), PrevLoc, Call->callReturn); PrevLoc.invalidate(); } - auto *P = C.get(); - PD.getActivePath().push_front(std::move(C)); + auto *P = Call.get(); + C.getActivePath().push_front(std::move(Call)); // Make the contents of the call the active path for now. - PD.pushActivePath(&P->path); - CallStack.push_back(StackDiagPair(P, N)); + C.PD->pushActivePath(&P->path); + C.CallStack.push_back(CallWithEntry(P, C.getCurrentNode())); return; } if (auto PS = P.getAs()) { - if (!AddPathEdges) + if (!C.shouldAddPathEdges()) return; - // For expressions, make sure we propagate the - // interesting symbols correctly. - if (const Expr *Ex = PS->getStmtAs()) - reversePropagateIntererstingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), Ex, - N->getLocationContext()); - // Add an edge. If this is an ObjCForCollectionStmt do // not add an edge here as it appears in the CFG both // as a terminator and as a terminator condition. if (!isa(PS->getStmt())) { PathDiagnosticLocation L = - PathDiagnosticLocation(PS->getStmt(), SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L); + PathDiagnosticLocation(PS->getStmt(), SM, C.getCurrLocationContext()); + addEdgeToPath(C.getActivePath(), PrevLoc, L); } } else if (auto BE = P.getAs()) { - if (!AddPathEdges) { - generateMinimalDiagForBlockEdge(N, *BE, SM, PDB, PD); + if (!C.shouldAddPathEdges()) { + generateMinimalDiagForBlockEdge(C, *BE); return; } - // Does this represent entering a call? If so, look at propagating - // interesting symbols across call boundaries. - if (const ExplodedNode *NextNode = N->getFirstPred()) { - const LocationContext *CallerCtx = NextNode->getLocationContext(); - const LocationContext *CalleeCtx = PDB.LC; - if (CallerCtx != CalleeCtx && AddPathEdges) { - reversePropagateInterestingSymbols(*PDB.getBugReport(), IE, - N->getState().get(), CalleeCtx); - } - } - // Are we jumping to the head of a loop? Add a special diagnostic. if (const Stmt *Loop = BE->getSrc()->getLoopTarget()) { - PathDiagnosticLocation L(Loop, SM, PDB.LC); + PathDiagnosticLocation L(Loop, SM, C.getCurrLocationContext()); const Stmt *Body = nullptr; if (const auto *FS = dyn_cast(Loop)) @@ -1200,27 +1254,27 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, "of the loop"); p->setPrunable(true); - addEdgeToPath(PD.getActivePath(), PrevLoc, p->getLocation()); - PD.getActivePath().push_front(std::move(p)); + addEdgeToPath(C.getActivePath(), PrevLoc, p->getLocation()); + C.getActivePath().push_front(std::move(p)); if (const auto *CS = dyn_cast_or_null(Body)) { - addEdgeToPath(PD.getActivePath(), PrevLoc, - PathDiagnosticLocation::createEndBrace(CS, SM)); + addEdgeToPath(C.getActivePath(), PrevLoc, + PathDiagnosticLocation::createEndBrace(CS, SM)); } } const CFGBlock *BSrc = BE->getSrc(); - ParentMap &PM = PDB.getParentMap(); + const ParentMap &PM = C.getParentMap(); if (const Stmt *Term = BSrc->getTerminatorStmt()) { // Are we jumping past the loop body without ever executing the // loop (because the condition was false)? if (isLoop(Term)) { const Stmt *TermCond = getTerminatorCondition(BSrc); - bool IsInLoopBody = - isInLoopBody(PM, getStmtBeforeCond(PM, TermCond, N), Term); + bool IsInLoopBody = isInLoopBody( + PM, getStmtBeforeCond(PM, TermCond, C.getCurrentNode()), Term); - const char *str = nullptr; + StringRef str; if (isJumpToFalseBranch(&*BE)) { if (!IsInLoopBody) { @@ -1236,31 +1290,41 @@ static void generatePathDiagnosticsForNode(const ExplodedNode *N, str = StrEnteringLoop; } - if (str) { - PathDiagnosticLocation L(TermCond ? TermCond : Term, SM, PDB.LC); + if (!str.empty()) { + PathDiagnosticLocation L(TermCond ? TermCond : Term, SM, + C.getCurrLocationContext()); auto PE = std::make_shared(L, str); PE->setPrunable(true); - addEdgeToPath(PD.getActivePath(), PrevLoc, - PE->getLocation()); - PD.getActivePath().push_front(std::move(PE)); + addEdgeToPath(C.getActivePath(), PrevLoc, PE->getLocation()); + C.getActivePath().push_front(std::move(PE)); } } else if (isa(Term) || isa(Term) || isa(Term)) { - PathDiagnosticLocation L(Term, SM, PDB.LC); - addEdgeToPath(PD.getActivePath(), PrevLoc, L); + PathDiagnosticLocation L(Term, SM, C.getCurrLocationContext()); + addEdgeToPath(C.getActivePath(), PrevLoc, L); } } } } static std::unique_ptr -generateEmptyDiagnosticForReport(BugReport *R, SourceManager &SM) { +generateDiagnosticForBasicReport(const BasicBugReport *R) { + const BugType &BT = R->getBugType(); + return llvm::make_unique( + BT.getCheckerName(), R->getDeclWithIssue(), BT.getDescription(), + R->getDescription(), R->getShortDescription(/*UseFallback=*/false), + BT.getCategory(), R->getUniqueingLocation(), R->getUniqueingDecl(), + llvm::make_unique()); +} + +static std::unique_ptr +generateEmptyDiagnosticForReport(const PathSensitiveBugReport *R, + const SourceManager &SM) { const BugType &BT = R->getBugType(); return llvm::make_unique( - R->getBugType().getCheckName(), R->getDeclWithIssue(), - R->getBugType().getName(), R->getDescription(), - R->getShortDescription(/*Fallback=*/false), BT.getCategory(), - R->getUniqueingLocation(), R->getUniqueingDecl(), + BT.getCheckerName(), R->getDeclWithIssue(), BT.getDescription(), + R->getDescription(), R->getShortDescription(/*UseFallback=*/false), + BT.getCategory(), R->getUniqueingLocation(), R->getUniqueingDecl(), findExecutedLines(SM, R->getErrorNode())); } @@ -1342,8 +1406,8 @@ using OptimizedCallsSet = llvm::DenseSet; /// This avoids a "swoosh" effect, where an edge from a top-level statement A /// points to a sub-expression B.1 that's not at the start of B. In these cases, /// we'd like to see an edge from A to B, then another one from B to B.1. -static void addContextEdges(PathPieces &pieces, SourceManager &SM, - const ParentMap &PM, const LocationContext *LCtx) { +static void addContextEdges(PathPieces &pieces, const LocationContext *LC) { + const ParentMap &PM = LC->getParentMap(); PathPieces::iterator Prev = pieces.end(); for (PathPieces::iterator I = pieces.begin(), E = Prev; I != E; Prev = I, ++I) { @@ -1360,7 +1424,7 @@ static void addContextEdges(PathPieces &pieces, SourceManager &SM, while (NextSrcContext.isValid() && NextSrcContext.asStmt() != InnerStmt) { SrcContexts.push_back(NextSrcContext); InnerStmt = NextSrcContext.asStmt(); - NextSrcContext = getEnclosingStmtLocation(InnerStmt, SM, PM, LCtx, + NextSrcContext = getEnclosingStmtLocation(InnerStmt, LC, /*allowNested=*/true); } @@ -1373,7 +1437,7 @@ static void addContextEdges(PathPieces &pieces, SourceManager &SM, // We are looking at an edge. Is the destination within a larger // expression? PathDiagnosticLocation DstContext = - getEnclosingStmtLocation(Dst, SM, PM, LCtx, /*allowNested=*/true); + getEnclosingStmtLocation(Dst, LC, /*allowNested=*/true); if (!DstContext.isValid() || DstContext.asStmt() == Dst) break; @@ -1493,7 +1557,7 @@ static void simplifySimpleBranches(PathPieces &pieces) { /// If the locations in the range are not on the same line, returns None. /// /// Note that this does not do a precise user-visible character or column count. -static Optional getLengthOnSingleLine(SourceManager &SM, +static Optional getLengthOnSingleLine(const SourceManager &SM, SourceRange Range) { SourceRange ExpansionRange(SM.getExpansionLoc(Range.getBegin()), SM.getExpansionRange(Range.getEnd()).getEnd()); @@ -1523,7 +1587,7 @@ static Optional getLengthOnSingleLine(SourceManager &SM, } /// \sa getLengthOnSingleLine(SourceManager, SourceRange) -static Optional getLengthOnSingleLine(SourceManager &SM, +static Optional getLengthOnSingleLine(const SourceManager &SM, const Stmt *S) { return getLengthOnSingleLine(SM, S->getSourceRange()); } @@ -1544,7 +1608,7 @@ static Optional getLengthOnSingleLine(SourceManager &SM, /// - if there is an inlined call between the edges instead of a single event. /// - if the whole statement is large enough that having subexpression arrows /// might be helpful. -static void removeContextCycles(PathPieces &Path, SourceManager &SM) { +static void removeContextCycles(PathPieces &Path, const SourceManager &SM) { for (PathPieces::iterator I = Path.begin(), E = Path.end(); I != E; ) { // Pattern match the current piece and its successor. const auto *PieceI = dyn_cast(I->get()); @@ -1599,7 +1663,7 @@ static void removeContextCycles(PathPieces &Path, SourceManager &SM) { } /// Return true if X is contained by Y. -static bool lexicalContains(ParentMap &PM, const Stmt *X, const Stmt *Y) { +static bool lexicalContains(const ParentMap &PM, const Stmt *X, const Stmt *Y) { while (X) { if (X == Y) return true; @@ -1609,8 +1673,8 @@ static bool lexicalContains(ParentMap &PM, const Stmt *X, const Stmt *Y) { } // Remove short edges on the same line less than 3 columns in difference. -static void removePunyEdges(PathPieces &path, SourceManager &SM, - ParentMap &PM) { +static void removePunyEdges(PathPieces &path, const SourceManager &SM, + const ParentMap &PM) { bool erased = false; for (PathPieces::iterator I = path.begin(), E = path.end(); I != E; @@ -1685,13 +1749,13 @@ static void removeIdenticalEvents(PathPieces &path) { } } -static bool optimizeEdges(PathPieces &path, SourceManager &SM, - OptimizedCallsSet &OCS, - LocationContextMap &LCM) { +static bool optimizeEdges(const PathDiagnosticConstruct &C, PathPieces &path, + OptimizedCallsSet &OCS) { bool hasChanges = false; - const LocationContext *LC = LCM[&path]; + const LocationContext *LC = C.getLocationContextFor(&path); assert(LC); - ParentMap &PM = LC->getParentMap(); + const ParentMap &PM = LC->getParentMap(); + const SourceManager &SM = C.getSourceManager(); for (PathPieces::iterator I = path.begin(), E = path.end(); I != E; ) { // Optimize subpaths. @@ -1699,7 +1763,8 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, // Record the fact that a call has been optimized so we only do the // effort once. if (!OCS.count(CallI)) { - while (optimizeEdges(CallI->path, SM, OCS, LCM)) {} + while (optimizeEdges(C, CallI->path, OCS)) { + } OCS.insert(CallI); } ++I; @@ -1845,7 +1910,7 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, if (!hasChanges) { // Adjust edges into subexpressions to make them more uniform // and aesthetically pleasing. - addContextEdges(path, SM, PM, LC); + addContextEdges(path, LC); // Remove "cyclical" edges that include one or more context edges. removeContextCycles(path, SM); // Hoist edges originating from branch conditions to branches @@ -1866,27 +1931,24 @@ static bool optimizeEdges(PathPieces &path, SourceManager &SM, /// statement had an invalid source location), this function does nothing. // FIXME: We should just generate invalid edges anyway and have the optimizer // deal with them. -static void dropFunctionEntryEdge(PathPieces &Path, LocationContextMap &LCM, - SourceManager &SM) { +static void dropFunctionEntryEdge(const PathDiagnosticConstruct &C, + PathPieces &Path) { const auto *FirstEdge = dyn_cast(Path.front().get()); if (!FirstEdge) return; - const Decl *D = LCM[&Path]->getDecl(); - PathDiagnosticLocation EntryLoc = PathDiagnosticLocation::createBegin(D, SM); + const Decl *D = C.getLocationContextFor(&Path)->getDecl(); + PathDiagnosticLocation EntryLoc = + PathDiagnosticLocation::createBegin(D, C.getSourceManager()); if (FirstEdge->getStartLocation() != EntryLoc) return; Path.pop_front(); } -using VisitorsDiagnosticsTy = llvm::DenseMap>>; - /// Populate executes lines with lines containing at least one diagnostics. -static void updateExecutedLinesWithDiagnosticPieces( - PathDiagnostic &PD) { +static void updateExecutedLinesWithDiagnosticPieces(PathDiagnostic &PD) { PathPieces path = PD.path.flatten(/*ShouldFlattenMacros=*/true); FilesToLineNumsMap &ExecutedLines = PD.getExecutedLines(); @@ -1900,56 +1962,61 @@ static void updateExecutedLinesWithDiagnosticPieces( } } -/// This function is responsible for generating diagnostic pieces that are -/// *not* provided by bug report visitors. -/// These diagnostics may differ depending on the consumer's settings, -/// and are therefore constructed separately for each consumer. -/// -/// There are two path diagnostics generation modes: with adding edges (used -/// for plists) and without (used for HTML and text). -/// When edges are added (\p ActiveScheme is Extensive), -/// the path is modified to insert artificially generated -/// edges. -/// Otherwise, more detailed diagnostics is emitted for block edges, explaining -/// the transitions in words. -static std::unique_ptr generatePathDiagnosticForConsumer( - PathDiagnosticConsumer::PathGenerationScheme ActiveScheme, - PathDiagnosticBuilder &PDB, - const ExplodedNode *ErrorNode, - const VisitorsDiagnosticsTy &VisitorsDiagnostics) { - - bool GenerateDiagnostics = (ActiveScheme != PathDiagnosticConsumer::None); - bool AddPathEdges = (ActiveScheme == PathDiagnosticConsumer::Extensive); - SourceManager &SM = PDB.getSourceManager(); - BugReport *R = PDB.getBugReport(); - AnalyzerOptions &Opts = PDB.getBugReporter().getAnalyzerOptions(); - StackDiagVector CallStack; - InterestingExprs IE; - LocationContextMap LCM; - std::unique_ptr PD = generateEmptyDiagnosticForReport(R, SM); - - if (GenerateDiagnostics) { - auto EndNotes = VisitorsDiagnostics.find(ErrorNode); - std::shared_ptr LastPiece; - if (EndNotes != VisitorsDiagnostics.end()) { - assert(!EndNotes->second.empty()); - LastPiece = EndNotes->second[0]; - } else { - LastPiece = BugReporterVisitor::getDefaultEndPath(PDB, ErrorNode, *R); - } - PD->setEndOfPath(LastPiece); +PathDiagnosticConstruct::PathDiagnosticConstruct( + const PathDiagnosticConsumer *PDC, const ExplodedNode *ErrorNode, + const PathSensitiveBugReport *R) + : Consumer(PDC), CurrentNode(ErrorNode), + SM(CurrentNode->getCodeDecl().getASTContext().getSourceManager()), + PD(generateEmptyDiagnosticForReport(R, getSourceManager())) { + LCM[&PD->getActivePath()] = ErrorNode->getLocationContext(); +} + +PathDiagnosticBuilder::PathDiagnosticBuilder( + BugReporterContext BRC, std::unique_ptr BugPath, + PathSensitiveBugReport *r, const ExplodedNode *ErrorNode, + std::unique_ptr VisitorsDiagnostics) + : BugReporterContext(BRC), BugPath(std::move(BugPath)), R(r), + ErrorNode(ErrorNode), + VisitorsDiagnostics(std::move(VisitorsDiagnostics)) {} + +std::unique_ptr +PathDiagnosticBuilder::generate(const PathDiagnosticConsumer *PDC) const { + PathDiagnosticConstruct Construct(PDC, ErrorNode, R); + + const SourceManager &SM = getSourceManager(); + const AnalyzerOptions &Opts = getAnalyzerOptions(); + StringRef ErrorTag = ErrorNode->getLocation().getTag()->getTagDescription(); + + // See whether we need to silence the checker/package. + // FIXME: This will not work if the report was emitted with an incorrect tag. + for (const std::string &CheckerOrPackage : Opts.SilencedCheckersAndPackages) { + if (ErrorTag.startswith(CheckerOrPackage)) + return nullptr; } - PathDiagnosticLocation PrevLoc = PD->getLocation(); - const ExplodedNode *NextNode = ErrorNode->getFirstPred(); - while (NextNode) { - if (GenerateDiagnostics) - generatePathDiagnosticsForNode( - NextNode, *PD, PrevLoc, PDB, LCM, CallStack, IE, AddPathEdges); + if (!PDC->shouldGenerateDiagnostics()) + return generateEmptyDiagnosticForReport(R, getSourceManager()); + + // Construct the final (warning) event for the bug report. + auto EndNotes = VisitorsDiagnostics->find(ErrorNode); + PathDiagnosticPieceRef LastPiece; + if (EndNotes != VisitorsDiagnostics->end()) { + assert(!EndNotes->second.empty()); + LastPiece = EndNotes->second[0]; + } else { + LastPiece = BugReporterVisitor::getDefaultEndPath(*this, ErrorNode, + *getBugReport()); + } + Construct.PD->setEndOfPath(LastPiece); + + PathDiagnosticLocation PrevLoc = Construct.PD->getLocation(); + // From the error node to the root, ascend the bug path and construct the bug + // report. + while (Construct.ascendToPrevNode()) { + generatePathDiagnosticsForNode(Construct, PrevLoc); - auto VisitorNotes = VisitorsDiagnostics.find(NextNode); - NextNode = NextNode->getFirstPred(); - if (!GenerateDiagnostics || VisitorNotes == VisitorsDiagnostics.end()) + auto VisitorNotes = VisitorsDiagnostics->find(Construct.getCurrentNode()); + if (VisitorNotes == VisitorsDiagnostics->end()) continue; // This is a workaround due to inability to put shared PathDiagnosticPiece @@ -1957,74 +2024,74 @@ static std::unique_ptr generatePathDiagnosticForConsumer( std::set DeduplicationSet; // Add pieces from custom visitors. - for (const auto &Note : VisitorNotes->second) { + for (const PathDiagnosticPieceRef &Note : VisitorNotes->second) { llvm::FoldingSetNodeID ID; Note->Profile(ID); - auto P = DeduplicationSet.insert(ID); - if (!P.second) + if (!DeduplicationSet.insert(ID).second) continue; - if (AddPathEdges) - addEdgeToPath(PD->getActivePath(), PrevLoc, Note->getLocation()); - updateStackPiecesWithMessage(*Note, CallStack); - PD->getActivePath().push_front(Note); + if (PDC->shouldAddPathEdges()) + addEdgeToPath(Construct.getActivePath(), PrevLoc, Note->getLocation()); + updateStackPiecesWithMessage(Note, Construct.CallStack); + Construct.getActivePath().push_front(Note); } } - if (AddPathEdges) { + if (PDC->shouldAddPathEdges()) { // Add an edge to the start of the function. // We'll prune it out later, but it helps make diagnostics more uniform. - const StackFrameContext *CalleeLC = PDB.LC->getStackFrame(); + const StackFrameContext *CalleeLC = + Construct.getLocationContextForActivePath()->getStackFrame(); const Decl *D = CalleeLC->getDecl(); - addEdgeToPath(PD->getActivePath(), PrevLoc, + addEdgeToPath(Construct.getActivePath(), PrevLoc, PathDiagnosticLocation::createBegin(D, SM)); } // Finally, prune the diagnostic path of uninteresting stuff. - if (!PD->path.empty()) { + if (!Construct.PD->path.empty()) { if (R->shouldPrunePath() && Opts.ShouldPrunePaths) { bool stillHasNotes = - removeUnneededCalls(PD->getMutablePieces(), R, LCM); + removeUnneededCalls(Construct, Construct.getMutablePieces(), R); assert(stillHasNotes); (void)stillHasNotes; } // Remove pop-up notes if needed. if (!Opts.ShouldAddPopUpNotes) - removePopUpNotes(PD->getMutablePieces()); + removePopUpNotes(Construct.getMutablePieces()); // Redirect all call pieces to have valid locations. - adjustCallLocations(PD->getMutablePieces()); - removePiecesWithInvalidLocations(PD->getMutablePieces()); + adjustCallLocations(Construct.getMutablePieces()); + removePiecesWithInvalidLocations(Construct.getMutablePieces()); - if (AddPathEdges) { + if (PDC->shouldAddPathEdges()) { // Reduce the number of edges from a very conservative set // to an aesthetically pleasing subset that conveys the // necessary information. OptimizedCallsSet OCS; - while (optimizeEdges(PD->getMutablePieces(), SM, OCS, LCM)) {} + while (optimizeEdges(Construct, Construct.getMutablePieces(), OCS)) { + } // Drop the very first function-entry edge. It's not really necessary // for top-level functions. - dropFunctionEntryEdge(PD->getMutablePieces(), LCM, SM); + dropFunctionEntryEdge(Construct, Construct.getMutablePieces()); } // Remove messages that are basically the same, and edges that may not // make sense. // We have to do this after edge optimization in the Extensive mode. - removeRedundantMsgs(PD->getMutablePieces()); - removeEdgesToDefaultInitializers(PD->getMutablePieces()); + removeRedundantMsgs(Construct.getMutablePieces()); + removeEdgesToDefaultInitializers(Construct.getMutablePieces()); } - if (GenerateDiagnostics && Opts.ShouldDisplayMacroExpansions) - CompactMacroExpandedPieces(PD->getMutablePieces(), SM); + if (Opts.ShouldDisplayMacroExpansions) + CompactMacroExpandedPieces(Construct.getMutablePieces(), SM); - return PD; + return std::move(Construct.PD); } - //===----------------------------------------------------------------------===// // Methods for BugType and subclasses. //===----------------------------------------------------------------------===// @@ -2037,9 +2104,8 @@ void BuiltinBug::anchor() {} // Methods for BugReport and subclasses. //===----------------------------------------------------------------------===// -void BugReport::NodeResolver::anchor() {} - -void BugReport::addVisitor(std::unique_ptr visitor) { +void PathSensitiveBugReport::addVisitor( + std::unique_ptr visitor) { if (!visitor) return; @@ -2054,20 +2120,11 @@ void BugReport::addVisitor(std::unique_ptr visitor) { Callbacks.push_back(std::move(visitor)); } -void BugReport::clearVisitors() { +void PathSensitiveBugReport::clearVisitors() { Callbacks.clear(); } -BugReport::~BugReport() { - while (!interestingSymbols.empty()) { - popInterestingSymbolsAndRegions(); - } -} - -const Decl *BugReport::getDeclWithIssue() const { - if (DeclWithIssue) - return DeclWithIssue; - +const Decl *PathSensitiveBugReport::getDeclWithIssue() const { const ExplodedNode *N = getErrorNode(); if (!N) return nullptr; @@ -2076,17 +2133,34 @@ const Decl *BugReport::getDeclWithIssue() const { return LC->getStackFrame()->getDecl(); } -void BugReport::Profile(llvm::FoldingSetNodeID& hash) const { +void BasicBugReport::Profile(llvm::FoldingSetNodeID& hash) const { + hash.AddInteger(static_cast(getKind())); + hash.AddPointer(&BT); + hash.AddString(Description); + assert(Location.isValid()); + Location.Profile(hash); + + for (SourceRange range : Ranges) { + if (!range.isValid()) + continue; + hash.AddInteger(range.getBegin().getRawEncoding()); + hash.AddInteger(range.getEnd().getRawEncoding()); + } +} + +void PathSensitiveBugReport::Profile(llvm::FoldingSetNodeID &hash) const { + hash.AddInteger(static_cast(getKind())); hash.AddPointer(&BT); hash.AddString(Description); PathDiagnosticLocation UL = getUniqueingLocation(); if (UL.isValid()) { UL.Profile(hash); - } else if (Location.isValid()) { - Location.Profile(hash); } else { - assert(ErrorNode); - hash.AddPointer(GetCurrentOrPreviousStmt(ErrorNode)); + // TODO: The statement may be null if the report was emitted before any + // statements were executed. In particular, some checkers by design + // occasionally emit their reports in empty functions (that have no + // statements in their body). Do we profile correctly in this case? + hash.AddPointer(ErrorNode->getCurrentOrPreviousStmtForDiagnostics()); } for (SourceRange range : Ranges) { @@ -2097,96 +2171,141 @@ void BugReport::Profile(llvm::FoldingSetNodeID& hash) const { } } -void BugReport::markInteresting(SymbolRef sym) { +template +static void insertToInterestingnessMap( + llvm::DenseMap &InterestingnessMap, T Val, + bugreporter::TrackingKind TKind) { + auto Result = InterestingnessMap.insert({Val, TKind}); + + if (Result.second) + return; + + // Even if this symbol/region was already marked as interesting as a + // condition, if we later mark it as interesting again but with + // thorough tracking, overwrite it. Entities marked with thorough + // interestiness are the most important (or most interesting, if you will), + // and we wouldn't like to downplay their importance. + + switch (TKind) { + case bugreporter::TrackingKind::Thorough: + Result.first->getSecond() = bugreporter::TrackingKind::Thorough; + return; + case bugreporter::TrackingKind::Condition: + return; + } + + llvm_unreachable( + "BugReport::markInteresting currently can only handle 2 different " + "tracking kinds! Please define what tracking kind should this entitiy" + "have, if it was already marked as interesting with a different kind!"); +} + +void PathSensitiveBugReport::markInteresting(SymbolRef sym, + bugreporter::TrackingKind TKind) { if (!sym) return; - getInterestingSymbols().insert(sym); + insertToInterestingnessMap(InterestingSymbols, sym, TKind); if (const auto *meta = dyn_cast(sym)) - getInterestingRegions().insert(meta->getRegion()); + markInteresting(meta->getRegion(), TKind); } -void BugReport::markInteresting(const MemRegion *R) { +void PathSensitiveBugReport::markInteresting(const MemRegion *R, + bugreporter::TrackingKind TKind) { if (!R) return; R = R->getBaseRegion(); - getInterestingRegions().insert(R); + insertToInterestingnessMap(InterestingRegions, R, TKind); if (const auto *SR = dyn_cast(R)) - getInterestingSymbols().insert(SR->getSymbol()); + markInteresting(SR->getSymbol(), TKind); } -void BugReport::markInteresting(SVal V) { - markInteresting(V.getAsRegion()); - markInteresting(V.getAsSymbol()); +void PathSensitiveBugReport::markInteresting(SVal V, + bugreporter::TrackingKind TKind) { + markInteresting(V.getAsRegion(), TKind); + markInteresting(V.getAsSymbol(), TKind); } -void BugReport::markInteresting(const LocationContext *LC) { +void PathSensitiveBugReport::markInteresting(const LocationContext *LC) { if (!LC) return; InterestingLocationContexts.insert(LC); } -bool BugReport::isInteresting(SVal V) { - return isInteresting(V.getAsRegion()) || isInteresting(V.getAsSymbol()); +Optional +PathSensitiveBugReport::getInterestingnessKind(SVal V) const { + auto RKind = getInterestingnessKind(V.getAsRegion()); + auto SKind = getInterestingnessKind(V.getAsSymbol()); + if (!RKind) + return SKind; + if (!SKind) + return RKind; + + // If either is marked with throrough tracking, return that, we wouldn't like + // to downplay a note's importance by 'only' mentioning it as a condition. + switch(*RKind) { + case bugreporter::TrackingKind::Thorough: + return RKind; + case bugreporter::TrackingKind::Condition: + return SKind; + } + + llvm_unreachable( + "BugReport::getInterestingnessKind currently can only handle 2 different " + "tracking kinds! Please define what tracking kind should we return here " + "when the kind of getAsRegion() and getAsSymbol() is different!"); + return None; } -bool BugReport::isInteresting(SymbolRef sym) { +Optional +PathSensitiveBugReport::getInterestingnessKind(SymbolRef sym) const { if (!sym) - return false; + return None; // We don't currently consider metadata symbols to be interesting // even if we know their region is interesting. Is that correct behavior? - return getInterestingSymbols().count(sym); + auto It = InterestingSymbols.find(sym); + if (It == InterestingSymbols.end()) + return None; + return It->getSecond(); } -bool BugReport::isInteresting(const MemRegion *R) { +Optional +PathSensitiveBugReport::getInterestingnessKind(const MemRegion *R) const { if (!R) - return false; - R = R->getBaseRegion(); - bool b = getInterestingRegions().count(R); - if (b) - return true; - if (const auto *SR = dyn_cast(R)) - return getInterestingSymbols().count(SR->getSymbol()); - return false; -} + return None; -bool BugReport::isInteresting(const LocationContext *LC) { - if (!LC) - return false; - return InterestingLocationContexts.count(LC); -} + R = R->getBaseRegion(); + auto It = InterestingRegions.find(R); + if (It != InterestingRegions.end()) + return It->getSecond(); -void BugReport::lazyInitializeInterestingSets() { - if (interestingSymbols.empty()) { - interestingSymbols.push_back(new Symbols()); - interestingRegions.push_back(new Regions()); - } + if (const auto *SR = dyn_cast(R)) + return getInterestingnessKind(SR->getSymbol()); + return None; } -BugReport::Symbols &BugReport::getInterestingSymbols() { - lazyInitializeInterestingSets(); - return *interestingSymbols.back(); +bool PathSensitiveBugReport::isInteresting(SVal V) const { + return getInterestingnessKind(V).hasValue(); } -BugReport::Regions &BugReport::getInterestingRegions() { - lazyInitializeInterestingSets(); - return *interestingRegions.back(); +bool PathSensitiveBugReport::isInteresting(SymbolRef sym) const { + return getInterestingnessKind(sym).hasValue(); } -void BugReport::pushInterestingSymbolsAndRegions() { - interestingSymbols.push_back(new Symbols(getInterestingSymbols())); - interestingRegions.push_back(new Regions(getInterestingRegions())); +bool PathSensitiveBugReport::isInteresting(const MemRegion *R) const { + return getInterestingnessKind(R).hasValue(); } -void BugReport::popInterestingSymbolsAndRegions() { - delete interestingSymbols.pop_back_val(); - delete interestingRegions.pop_back_val(); +bool PathSensitiveBugReport::isInteresting(const LocationContext *LC) const { + if (!LC) + return false; + return InterestingLocationContexts.count(LC); } -const Stmt *BugReport::getStmt() const { +const Stmt *PathSensitiveBugReport::getStmt() const { if (!ErrorNode) return nullptr; @@ -2196,59 +2315,83 @@ const Stmt *BugReport::getStmt() const { if (Optional BE = ProgP.getAs()) { CFGBlock &Exit = ProgP.getLocationContext()->getCFG()->getExit(); if (BE->getBlock() == &Exit) - S = GetPreviousStmt(ErrorNode); + S = ErrorNode->getPreviousStmtForDiagnostics(); } if (!S) - S = PathDiagnosticLocation::getStmt(ErrorNode); + S = ErrorNode->getStmtForDiagnostics(); return S; } -llvm::iterator_range BugReport::getRanges() { +ArrayRef +PathSensitiveBugReport::getRanges() const { // If no custom ranges, add the range of the statement corresponding to // the error node. - if (Ranges.empty()) { - if (const auto *E = dyn_cast_or_null(getStmt())) - addRange(E->getSourceRange()); - else - return llvm::make_range(ranges_iterator(), ranges_iterator()); + if (Ranges.empty() && isa_and_nonnull(getStmt())) + return ErrorNodeRange; + + return Ranges; +} + +PathDiagnosticLocation +PathSensitiveBugReport::getLocation() const { + assert(ErrorNode && "Cannot create a location with a null node."); + const Stmt *S = ErrorNode->getStmtForDiagnostics(); + ProgramPoint P = ErrorNode->getLocation(); + const LocationContext *LC = P.getLocationContext(); + SourceManager &SM = + ErrorNode->getState()->getStateManager().getContext().getSourceManager(); + + if (!S) { + // If this is an implicit call, return the implicit call point location. + if (Optional PIE = P.getAs()) + return PathDiagnosticLocation(PIE->getLocation(), SM); + if (auto FE = P.getAs()) { + if (const ReturnStmt *RS = FE->getStmt()) + return PathDiagnosticLocation::createBegin(RS, SM, LC); + } + S = ErrorNode->getNextStmtForDiagnostics(); } - // User-specified absence of range info. - if (Ranges.size() == 1 && !Ranges.begin()->isValid()) - return llvm::make_range(ranges_iterator(), ranges_iterator()); + if (S) { + // For member expressions, return the location of the '.' or '->'. + if (const auto *ME = dyn_cast(S)) + return PathDiagnosticLocation::createMemberLoc(ME, SM); - return llvm::make_range(Ranges.begin(), Ranges.end()); -} + // For binary operators, return the location of the operator. + if (const auto *B = dyn_cast(S)) + return PathDiagnosticLocation::createOperatorLoc(B, SM); + + if (P.getAs()) + return PathDiagnosticLocation::createEnd(S, SM, LC); -PathDiagnosticLocation BugReport::getLocation(const SourceManager &SM) const { - if (ErrorNode) { - assert(!Location.isValid() && - "Either Location or ErrorNode should be specified but not both."); - return PathDiagnosticLocation::createEndOfPath(ErrorNode, SM); + if (S->getBeginLoc().isValid()) + return PathDiagnosticLocation(S, SM, LC); + + return PathDiagnosticLocation( + PathDiagnosticLocation::getValidSourceLocation(S, LC), SM); } - assert(Location.isValid()); - return Location; + return PathDiagnosticLocation::createDeclEnd(ErrorNode->getLocationContext(), + SM); } //===----------------------------------------------------------------------===// // Methods for BugReporter and subclasses. //===----------------------------------------------------------------------===// -BugReportEquivClass::~BugReportEquivClass() = default; - -GRBugReporter::~GRBugReporter() = default; - -BugReporterData::~BugReporterData() = default; - -ExplodedGraph &GRBugReporter::getGraph() { return Eng.getGraph(); } +const ExplodedGraph &PathSensitiveBugReporter::getGraph() const { + return Eng.getGraph(); +} -ProgramStateManager& -GRBugReporter::getStateManager() { return Eng.getStateManager(); } +ProgramStateManager &PathSensitiveBugReporter::getStateManager() const { + return Eng.getStateManager(); +} BugReporter::~BugReporter() { - FlushReports(); + // Make sure reports are flushed. + assert(StrBugTypes.empty() && + "Destroying BugReporter before diagnostics are emitted!"); // Free the bug reports we are tracking. for (const auto I : EQClassesVector) @@ -2256,9 +2399,6 @@ BugReporter::~BugReporter() { } void BugReporter::FlushReports() { - if (BugTypes.isEmpty()) - return; - // We need to flush reports in deterministic order to ensure the order // of the reports is consistent between runs. for (const auto EQ : EQClassesVector) @@ -2269,9 +2409,6 @@ void BugReporter::FlushReports() { // FIXME: There are leaks from checkers that assume that the BugTypes they // create will be destroyed by the BugReporter. llvm::DeleteContainerSeconds(StrBugTypes); - - // Remove all references to the BugType objects. - BugTypes = F.getEmptySet(); } //===----------------------------------------------------------------------===// @@ -2280,29 +2417,32 @@ void BugReporter::FlushReports() { namespace { -/// A wrapper around a report graph, which contains only a single path, and its -/// node maps. -class ReportGraph { +/// A wrapper around an ExplodedGraph that contains a single path from the root +/// to the error node. +class BugPathInfo { public: - InterExplodedGraphMap BackMap; - std::unique_ptr Graph; + std::unique_ptr BugPath; + PathSensitiveBugReport *Report; const ExplodedNode *ErrorNode; - size_t Index; }; -/// A wrapper around a trimmed graph and its node maps. -class TrimmedGraph { - InterExplodedGraphMap InverseMap; +/// A wrapper around an ExplodedGraph whose leafs are all error nodes. Can +/// conveniently retrieve bug paths from a single error node to the root. +class BugPathGetter { + std::unique_ptr TrimmedGraph; using PriorityMapTy = llvm::DenseMap; + /// Assign each node with its distance from the root. PriorityMapTy PriorityMap; - using NodeIndexPair = std::pair; + /// Since the getErrorNode() or BugReport refers to the original ExplodedGraph, + /// we need to pair it to the error node of the constructed trimmed graph. + using ReportNewNodePair = + std::pair; + SmallVector ReportNodes; - SmallVector ReportNodes; - - std::unique_ptr G; + BugPathInfo CurrentBugPath; /// A helper class for sorting ExplodedNodes by priority. template @@ -2326,37 +2466,48 @@ class TrimmedGraph { : LI->second < RI->second; } - bool operator()(const NodeIndexPair &LHS, const NodeIndexPair &RHS) const { - return (*this)(LHS.first, RHS.first); + bool operator()(const ReportNewNodePair &LHS, + const ReportNewNodePair &RHS) const { + return (*this)(LHS.second, RHS.second); } }; public: - TrimmedGraph(const ExplodedGraph *OriginalGraph, - ArrayRef Nodes); + BugPathGetter(const ExplodedGraph *OriginalGraph, + ArrayRef &bugReports); - bool popNextReportGraph(ReportGraph &GraphWrapper); + BugPathInfo *getNextBugPath(); }; } // namespace -TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, - ArrayRef Nodes) { +BugPathGetter::BugPathGetter(const ExplodedGraph *OriginalGraph, + ArrayRef &bugReports) { + SmallVector Nodes; + for (const auto I : bugReports) { + assert(I->isValid() && + "We only allow BugReporterVisitors and BugReporter itself to " + "invalidate reports!"); + Nodes.emplace_back(I->getErrorNode()); + } + // The trimmed graph is created in the body of the constructor to ensure // that the DenseMaps have been initialized already. InterExplodedGraphMap ForwardMap; - G = OriginalGraph->trim(Nodes, &ForwardMap, &InverseMap); + TrimmedGraph = OriginalGraph->trim(Nodes, &ForwardMap); // Find the (first) error node in the trimmed graph. We just need to consult // the node map which maps from nodes in the original graph to nodes // in the new graph. llvm::SmallPtrSet RemainingNodes; - for (unsigned i = 0, count = Nodes.size(); i < count; ++i) { - if (const ExplodedNode *NewNode = ForwardMap.lookup(Nodes[i])) { - ReportNodes.push_back(std::make_pair(NewNode, i)); - RemainingNodes.insert(NewNode); - } + for (PathSensitiveBugReport *Report : bugReports) { + const ExplodedNode *NewNode = ForwardMap.lookup(Report->getErrorNode()); + assert(NewNode && + "Failed to construct a trimmed graph that contains this error " + "node!"); + ReportNodes.emplace_back(Report, NewNode); + RemainingNodes.insert(NewNode); } assert(!RemainingNodes.empty() && "No error node found in the trimmed graph"); @@ -2364,8 +2515,8 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, // Perform a forward BFS to find all the shortest paths. std::queue WS; - assert(G->num_roots() == 1); - WS.push(*G->roots_begin()); + assert(TrimmedGraph->num_roots() == 1); + WS.push(*TrimmedGraph->roots_begin()); unsigned Priority = 0; while (!WS.empty()) { @@ -2374,8 +2525,7 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, PriorityMapTy::iterator PriorityEntry; bool IsNew; - std::tie(PriorityEntry, IsNew) = - PriorityMap.insert(std::make_pair(Node, Priority)); + std::tie(PriorityEntry, IsNew) = PriorityMap.insert({Node, Priority}); ++Priority; if (!IsNew) { @@ -2387,29 +2537,26 @@ TrimmedGraph::TrimmedGraph(const ExplodedGraph *OriginalGraph, if (RemainingNodes.empty()) break; - for (ExplodedNode::const_pred_iterator I = Node->succ_begin(), - E = Node->succ_end(); - I != E; ++I) - WS.push(*I); + for (const ExplodedNode *Succ : Node->succs()) + WS.push(Succ); } // Sort the error paths from longest to shortest. llvm::sort(ReportNodes, PriorityCompare(PriorityMap)); } -bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { +BugPathInfo *BugPathGetter::getNextBugPath() { if (ReportNodes.empty()) - return false; + return nullptr; const ExplodedNode *OrigN; - std::tie(OrigN, GraphWrapper.Index) = ReportNodes.pop_back_val(); + std::tie(CurrentBugPath.Report, OrigN) = ReportNodes.pop_back_val(); assert(PriorityMap.find(OrigN) != PriorityMap.end() && "error node not accessible from root"); - // Create a new graph with a single path. This is the graph - // that will be returned to the caller. + // Create a new graph with a single path. This is the graph that will be + // returned to the caller. auto GNew = llvm::make_unique(); - GraphWrapper.BackMap.clear(); // Now walk from the error node up the BFS path, always taking the // predeccessor with the lowest number. @@ -2417,19 +2564,15 @@ bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { while (true) { // Create the equivalent node in the new graph with the same state // and location. - ExplodedNode *NewN = GNew->createUncachedNode(OrigN->getLocation(), OrigN->getState(), - OrigN->isSink()); - - // Store the mapping to the original node. - InterExplodedGraphMap::const_iterator IMitr = InverseMap.find(OrigN); - assert(IMitr != InverseMap.end() && "No mapping to original node."); - GraphWrapper.BackMap[NewN] = IMitr->second; + ExplodedNode *NewN = GNew->createUncachedNode( + OrigN->getLocation(), OrigN->getState(), + OrigN->getID(), OrigN->isSink()); // Link up the new node with the previous node. if (Succ) Succ->addPredecessor(NewN, *GNew); else - GraphWrapper.ErrorNode = NewN; + CurrentBugPath.ErrorNode = NewN; Succ = NewN; @@ -2442,23 +2585,22 @@ bool TrimmedGraph::popNextReportGraph(ReportGraph &GraphWrapper) { // Find the next predeccessor node. We choose the node that is marked // with the lowest BFS number. OrigN = *std::min_element(OrigN->pred_begin(), OrigN->pred_end(), - PriorityCompare(PriorityMap)); + PriorityCompare(PriorityMap)); } - GraphWrapper.Graph = std::move(GNew); + CurrentBugPath.BugPath = std::move(GNew); - return true; + return &CurrentBugPath; } /// CompactMacroExpandedPieces - This function postprocesses a PathDiagnostic /// object and collapses PathDiagosticPieces that are expanded by macros. static void CompactMacroExpandedPieces(PathPieces &path, const SourceManager& SM) { - using MacroStackTy = - std::vector< - std::pair, SourceLocation>>; + using MacroStackTy = std::vector< + std::pair, SourceLocation>>; - using PiecesTy = std::vector>; + using PiecesTy = std::vector; MacroStackTy MacroStack; PiecesTy Pieces; @@ -2548,33 +2690,38 @@ static void CompactMacroExpandedPieces(PathPieces &path, /// Notes associated with {@code ErrorNode} are generated using /// {@code getEndPath}, and the rest are generated with {@code VisitNode}. static std::unique_ptr -generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, +generateVisitorsDiagnostics(PathSensitiveBugReport *R, + const ExplodedNode *ErrorNode, BugReporterContext &BRC) { auto Notes = llvm::make_unique(); - BugReport::VisitorList visitors; + PathSensitiveBugReport::VisitorList visitors; // Run visitors on all nodes starting from the node *before* the last one. // The last node is reserved for notes generated with {@code getEndPath}. const ExplodedNode *NextNode = ErrorNode->getFirstPred(); while (NextNode) { - // At each iteration, move all visitors from report to visitor list. - for (BugReport::visitor_iterator I = R->visitor_begin(), - E = R->visitor_end(); - I != E; ++I) { - visitors.push_back(std::move(*I)); - } + // At each iteration, move all visitors from report to visitor list. This is + // important, because the Profile() functions of the visitors make sure that + // a visitor isn't added multiple times for the same node, but it's fine + // to add the a visitor with Profile() for different nodes (e.g. tracking + // a region at different points of the symbolic execution). + for (std::unique_ptr &Visitor : R->visitors()) + visitors.push_back(std::move(Visitor)); + R->clearVisitors(); const ExplodedNode *Pred = NextNode->getFirstPred(); if (!Pred) { - std::shared_ptr LastPiece; + PathDiagnosticPieceRef LastPiece; for (auto &V : visitors) { V->finalizeVisitor(BRC, ErrorNode, *R); if (auto Piece = V->getEndPath(BRC, ErrorNode, *R)) { assert(!LastPiece && "There can only be one final piece in a diagnostic."); + assert(Piece->getKind() == PathDiagnosticPiece::Kind::Event && + "The final piece must contain a message!"); LastPiece = std::move(Piece); (*Notes)[ErrorNode].push_back(LastPiece); } @@ -2597,24 +2744,18 @@ generateVisitorsDiagnostics(BugReport *R, const ExplodedNode *ErrorNode, return Notes; } -/// Find a non-invalidated report for a given equivalence class, -/// and return together with a cache of visitors notes. -/// If none found, return a nullptr paired with an empty cache. -static -std::pair> findValidReport( - TrimmedGraph &TrimG, - ReportGraph &ErrorGraph, - ArrayRef &bugReports, - AnalyzerOptions &Opts, - GRBugReporter &Reporter) { +Optional PathDiagnosticBuilder::findValidReport( + ArrayRef &bugReports, + PathSensitiveBugReporter &Reporter) { + + BugPathGetter BugGraph(&Reporter.getGraph(), bugReports); - while (TrimG.popNextReportGraph(ErrorGraph)) { + while (BugPathInfo *BugPath = BugGraph.getNextBugPath()) { // Find the BugReport with the original location. - assert(ErrorGraph.Index < bugReports.size()); - BugReport *R = bugReports[ErrorGraph.Index]; + PathSensitiveBugReport *R = BugPath->Report; assert(R && "No original report found for sliced graph."); assert(R->isValid() && "Report selected by trimmed graph marked invalid."); - const ExplodedNode *ErrorNode = ErrorGraph.ErrorNode; + const ExplodedNode *ErrorNode = BugPath->ErrorNode; // Register refutation visitors first, if they mark the bug invalid no // further analysis is required @@ -2625,14 +2766,14 @@ std::pair> findValidReport( R->addVisitor(llvm::make_unique()); R->addVisitor(llvm::make_unique()); - BugReporterContext BRC(Reporter, ErrorGraph.BackMap); + BugReporterContext BRC(Reporter); // Run all visitors on a given graph, once. std::unique_ptr visitorNotes = generateVisitorsDiagnostics(R, ErrorNode, BRC); if (R->isValid()) { - if (Opts.ShouldCrosscheckWithZ3) { + if (Reporter.getAnalyzerOptions().ShouldCrosscheckWithZ3) { // If crosscheck is enabled, remove all visitors, add the refutation // visitor and check again R->clearVisitors(); @@ -2640,84 +2781,44 @@ std::pair> findValidReport( // We don't overrite the notes inserted by other visitors because the // refutation manager does not add any new note to the path - generateVisitorsDiagnostics(R, ErrorGraph.ErrorNode, BRC); + generateVisitorsDiagnostics(R, BugPath->ErrorNode, BRC); } // Check if the bug is still valid if (R->isValid()) - return std::make_pair(R, std::move(visitorNotes)); + return PathDiagnosticBuilder( + std::move(BRC), std::move(BugPath->BugPath), BugPath->Report, + BugPath->ErrorNode, std::move(visitorNotes)); } } - return std::make_pair(nullptr, llvm::make_unique()); + return {}; } std::unique_ptr -GRBugReporter::generatePathDiagnostics( +PathSensitiveBugReporter::generatePathDiagnostics( ArrayRef consumers, - ArrayRef &bugReports) { + ArrayRef &bugReports) { assert(!bugReports.empty()); auto Out = llvm::make_unique(); - bool HasValid = false; - SmallVector errorNodes; - for (const auto I : bugReports) { - if (I->isValid()) { - HasValid = true; - errorNodes.push_back(I->getErrorNode()); - } else { - // Keep the errorNodes list in sync with the bugReports list. - errorNodes.push_back(nullptr); - } - } - // If all the reports have been marked invalid by a previous path generation, - // we're done. - if (!HasValid) - return Out; + Optional PDB = + PathDiagnosticBuilder::findValidReport(bugReports, *this); - TrimmedGraph TrimG(&getGraph(), errorNodes); - ReportGraph ErrorGraph; - auto ReportInfo = findValidReport(TrimG, ErrorGraph, bugReports, - getAnalyzerOptions(), *this); - BugReport *R = ReportInfo.first; - - if (R && R->isValid()) { - const ExplodedNode *ErrorNode = ErrorGraph.ErrorNode; + if (PDB) { for (PathDiagnosticConsumer *PC : consumers) { - PathDiagnosticBuilder PDB(*this, R, ErrorGraph.BackMap, PC); - std::unique_ptr PD = generatePathDiagnosticForConsumer( - PC->getGenerationScheme(), PDB, ErrorNode, *ReportInfo.second); - (*Out)[PC] = std::move(PD); + if (std::unique_ptr PD = PDB->generate(PC)) { + (*Out)[PC] = std::move(PD); + } } } return Out; } -void BugReporter::Register(const BugType *BT) { - BugTypes = F.add(BugTypes, BT); -} - void BugReporter::emitReport(std::unique_ptr R) { - if (const ExplodedNode *E = R->getErrorNode()) { - // An error node must either be a sink or have a tag, otherwise - // it could get reclaimed before the path diagnostic is created. - assert((E->isSink() || E->getLocation().getTag()) && - "Error node must either be a sink or have a tag"); - - const AnalysisDeclContext *DeclCtx = - E->getLocationContext()->getAnalysisDeclContext(); - // The source of autosynthesized body can be handcrafted AST or a model - // file. The locations from handcrafted ASTs have no valid source locations - // and have to be discarded. Locations from model files should be preserved - // for processing and reporting. - if (DeclCtx->isBodyAutosynthesized() && - !DeclCtx->isBodyAutosynthesizedFromModelFile()) - return; - } - - bool ValidSourceLoc = R->getLocation(getSourceManager()).isValid(); + bool ValidSourceLoc = R->getLocation().isValid(); assert(ValidSourceLoc); // If we mess up in a release build, we'd still prefer to just drop the bug // instead of trying to go on. @@ -2729,8 +2830,6 @@ void BugReporter::emitReport(std::unique_ptr R) { R->Profile(ID); // Lookup the equivance class. If there isn't one, create it. - const BugType& BT = R->getBugType(); - Register(&BT); void *InsertPos; BugReportEquivClass* EQ = EQClasses.FindNodeOrInsertPos(ID, InsertPos); @@ -2742,6 +2841,28 @@ void BugReporter::emitReport(std::unique_ptr R) { EQ->AddReport(std::move(R)); } +void PathSensitiveBugReporter::emitReport(std::unique_ptr R) { + if (auto PR = dyn_cast(R.get())) + if (const ExplodedNode *E = PR->getErrorNode()) { + // An error node must either be a sink or have a tag, otherwise + // it could get reclaimed before the path diagnostic is created. + assert((E->isSink() || E->getLocation().getTag()) && + "Error node must either be a sink or have a tag"); + + const AnalysisDeclContext *DeclCtx = + E->getLocationContext()->getAnalysisDeclContext(); + // The source of autosynthesized body can be handcrafted AST or a model + // file. The locations from handcrafted ASTs have no valid source + // locations and have to be discarded. Locations from model files should + // be preserved for processing and reporting. + if (DeclCtx->isBodyAutosynthesized() && + !DeclCtx->isBodyAutosynthesizedFromModelFile()) + return; + } + + BugReporter::emitReport(std::move(R)); +} + //===----------------------------------------------------------------------===// // Emitting reports in equivalence classes. //===----------------------------------------------------------------------===// @@ -2758,107 +2879,19 @@ struct FRIEC_WLItem { } // namespace -static const CFGBlock *findBlockForNode(const ExplodedNode *N) { - ProgramPoint P = N->getLocation(); - if (auto BEP = P.getAs()) - return BEP->getBlock(); - - // Find the node's current statement in the CFG. - if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) - return N->getLocationContext()->getAnalysisDeclContext() - ->getCFGStmtMap()->getBlock(S); - - return nullptr; -} - -// Returns true if by simply looking at the block, we can be sure that it -// results in a sink during analysis. This is useful to know when the analysis -// was interrupted, and we try to figure out if it would sink eventually. -// There may be many more reasons why a sink would appear during analysis -// (eg. checkers may generate sinks arbitrarily), but here we only consider -// sinks that would be obvious by looking at the CFG. -static bool isImmediateSinkBlock(const CFGBlock *Blk) { - if (Blk->hasNoReturnElement()) - return true; - - // FIXME: Throw-expressions are currently generating sinks during analysis: - // they're not supported yet, and also often used for actually terminating - // the program. So we should treat them as sinks in this analysis as well, - // at least for now, but once we have better support for exceptions, - // we'd need to carefully handle the case when the throw is being - // immediately caught. - if (std::any_of(Blk->begin(), Blk->end(), [](const CFGElement &Elm) { - if (Optional StmtElm = Elm.getAs()) - if (isa(StmtElm->getStmt())) - return true; - return false; - })) - return true; - - return false; -} - -// Returns true if by looking at the CFG surrounding the node's program -// point, we can be sure that any analysis starting from this point would -// eventually end with a sink. We scan the child CFG blocks in a depth-first -// manner and see if all paths eventually end up in an immediate sink block. -static bool isInevitablySinking(const ExplodedNode *N) { - const CFG &Cfg = N->getCFG(); - - const CFGBlock *StartBlk = findBlockForNode(N); - if (!StartBlk) - return false; - if (isImmediateSinkBlock(StartBlk)) - return true; - - llvm::SmallVector DFSWorkList; - llvm::SmallPtrSet Visited; - - DFSWorkList.push_back(StartBlk); - while (!DFSWorkList.empty()) { - const CFGBlock *Blk = DFSWorkList.back(); - DFSWorkList.pop_back(); - Visited.insert(Blk); - - // If at least one path reaches the CFG exit, it means that control is - // returned to the caller. For now, say that we are not sure what - // happens next. If necessary, this can be improved to analyze - // the parent StackFrameContext's call site in a similar manner. - if (Blk == &Cfg.getExit()) - return false; - - for (const auto &Succ : Blk->succs()) { - if (const CFGBlock *SuccBlk = Succ.getReachableBlock()) { - if (!isImmediateSinkBlock(SuccBlk) && !Visited.count(SuccBlk)) { - // If the block has reachable child blocks that aren't no-return, - // add them to the worklist. - DFSWorkList.push_back(SuccBlk); - } - } - } - } - - // Nothing reached the exit. It can only mean one thing: there's no return. - return true; -} - -static BugReport * -FindReportInEquivalenceClass(BugReportEquivClass& EQ, - SmallVectorImpl &bugReports) { - BugReportEquivClass::iterator I = EQ.begin(), E = EQ.end(); - assert(I != E); - const BugType& BT = I->getBugType(); - +BugReport *PathSensitiveBugReporter::findReportInEquivalenceClass( + BugReportEquivClass &EQ, SmallVectorImpl &bugReports) { // If we don't need to suppress any of the nodes because they are // post-dominated by a sink, simply add all the nodes in the equivalence class // to 'Nodes'. Any of the reports will serve as a "representative" report. + assert(EQ.getReports().size() > 0); + const BugType& BT = EQ.getReports()[0]->getBugType(); if (!BT.isSuppressOnSink()) { - BugReport *R = &*I; - for (auto &I : EQ) { - const ExplodedNode *N = I.getErrorNode(); - if (N) { - R = &I; - bugReports.push_back(R); + BugReport *R = EQ.getReports()[0].get(); + for (auto &J : EQ.getReports()) { + if (auto *PR = dyn_cast(J.get())) { + R = PR; + bugReports.push_back(PR); } } return R; @@ -2872,20 +2905,21 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, // stack for very long paths. BugReport *exampleReport = nullptr; - for (; I != E; ++I) { - const ExplodedNode *errorNode = I->getErrorNode(); - - if (!errorNode) + for (const auto &I: EQ.getReports()) { + auto *R = dyn_cast(I.get()); + if (!R) continue; + + const ExplodedNode *errorNode = R->getErrorNode(); if (errorNode->isSink()) { llvm_unreachable( "BugType::isSuppressSink() should not be 'true' for sink end nodes"); } // No successors? By definition this nodes isn't post-dominated by a sink. if (errorNode->succ_empty()) { - bugReports.push_back(&*I); + bugReports.push_back(R); if (!exampleReport) - exampleReport = &*I; + exampleReport = R; continue; } @@ -2893,8 +2927,9 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, // to being post-dominated by a sink. This works better when the analysis // is incomplete and we have never reached the no-return function call(s) // that we'd inevitably bump into on this path. - if (isInevitablySinking(errorNode)) - continue; + if (const CFGBlock *ErrorB = errorNode->getCFGBlock()) + if (ErrorB->isInevitablySinking()) + continue; // At this point we know that 'N' is not a sink and it has at least one // successor. Use a DFS worklist to find a non-sink end-of-path node. @@ -2917,9 +2952,9 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, if (Succ->succ_empty()) { // If we found an end-of-path node that is not a sink. if (!Succ->isSink()) { - bugReports.push_back(&*I); + bugReports.push_back(R); if (!exampleReport) - exampleReport = &*I; + exampleReport = R; WL.clear(); break; } @@ -2950,7 +2985,7 @@ FindReportInEquivalenceClass(BugReportEquivClass& EQ, void BugReporter::FlushReport(BugReportEquivClass& EQ) { SmallVector bugReports; - BugReport *report = FindReportInEquivalenceClass(EQ, bugReports); + BugReport *report = findReportInEquivalenceClass(EQ, bugReports); if (!report) return; @@ -2965,9 +3000,9 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { // If the path is empty, generate a single step path with the location // of the issue. if (PD->path.empty()) { - PathDiagnosticLocation L = report->getLocation(getSourceManager()); + PathDiagnosticLocation L = report->getLocation(); auto piece = llvm::make_unique( - L, report->getDescription()); + L, report->getDescription()); for (SourceRange Range : report->getRanges()) piece->addRange(Range); PD->setEndOfPath(std::move(piece)); @@ -2993,10 +3028,8 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { Pieces.push_front(*I); } - // Get the meta data. - const BugReport::ExtraTextList &Meta = report->getExtraText(); - for (const auto &i : Meta) - PD->addMeta(i); + for (const auto &I : report->getFixits()) + Pieces.back()->addFixit(I); updateExecutedLinesWithDiagnosticPieces(*PD); Consumer->HandlePathDiagnostic(std::move(PD)); @@ -3006,7 +3039,7 @@ void BugReporter::FlushReport(BugReportEquivClass& EQ) { /// Insert all lines participating in the function signature \p Signature /// into \p ExecutedLines. static void populateExecutedLinesWithFunctionSignature( - const Decl *Signature, SourceManager &SM, + const Decl *Signature, const SourceManager &SM, FilesToLineNumsMap &ExecutedLines) { SourceRange SignatureSourceRange; const Stmt* Body = Signature->getBody(); @@ -3031,7 +3064,7 @@ static void populateExecutedLinesWithFunctionSignature( } static void populateExecutedLinesWithStmt( - const Stmt *S, SourceManager &SM, + const Stmt *S, const SourceManager &SM, FilesToLineNumsMap &ExecutedLines) { SourceLocation Loc = S->getSourceRange().getBegin(); if (!Loc.isValid()) @@ -3045,7 +3078,7 @@ static void populateExecutedLinesWithStmt( /// \return all executed lines including function signatures on the path /// starting from \p N. static std::unique_ptr -findExecutedLines(SourceManager &SM, const ExplodedNode *N) { +findExecutedLines(const SourceManager &SM, const ExplodedNode *N) { auto ExecutedLines = llvm::make_unique(); while (N) { @@ -3057,7 +3090,7 @@ findExecutedLines(SourceManager &SM, const ExplodedNode *N) { // Inlined function: show signature. const Decl* D = CE->getCalleeContext()->getDecl(); populateExecutedLinesWithFunctionSignature(D, SM, *ExecutedLines); - } else if (const Stmt *S = PathDiagnosticLocation::getStmt(N)) { + } else if (const Stmt *S = N->getStmtForDiagnostics()) { populateExecutedLinesWithStmt(S, SM, *ExecutedLines); // Show extra context for some parent kinds. @@ -3082,16 +3115,89 @@ findExecutedLines(SourceManager &SM, const ExplodedNode *N) { std::unique_ptr BugReporter::generateDiagnosticForConsumerMap( - BugReport *report, ArrayRef consumers, + BugReport *exampleReport, ArrayRef consumers, ArrayRef bugReports) { + auto *basicReport = cast(exampleReport); + auto Out = llvm::make_unique(); + for (auto *Consumer : consumers) + (*Out)[Consumer] = generateDiagnosticForBasicReport(basicReport); + return Out; +} - if (!report->isPathSensitive()) { - auto Out = llvm::make_unique(); - for (auto *Consumer : consumers) - (*Out)[Consumer] = generateEmptyDiagnosticForReport(report, - getSourceManager()); - return Out; +static PathDiagnosticCallPiece * +getFirstStackedCallToHeaderFile(PathDiagnosticCallPiece *CP, + const SourceManager &SMgr) { + SourceLocation CallLoc = CP->callEnter.asLocation(); + + // If the call is within a macro, don't do anything (for now). + if (CallLoc.isMacroID()) + return nullptr; + + assert(AnalysisManager::isInCodeFile(CallLoc, SMgr) && + "The call piece should not be in a header file."); + + // Check if CP represents a path through a function outside of the main file. + if (!AnalysisManager::isInCodeFile(CP->callEnterWithin.asLocation(), SMgr)) + return CP; + + const PathPieces &Path = CP->path; + if (Path.empty()) + return nullptr; + + // Check if the last piece in the callee path is a call to a function outside + // of the main file. + if (auto *CPInner = dyn_cast(Path.back().get())) + return getFirstStackedCallToHeaderFile(CPInner, SMgr); + + // Otherwise, the last piece is in the main file. + return nullptr; +} + +static void resetDiagnosticLocationToMainFile(PathDiagnostic &PD) { + if (PD.path.empty()) + return; + + PathDiagnosticPiece *LastP = PD.path.back().get(); + assert(LastP); + const SourceManager &SMgr = LastP->getLocation().getManager(); + + // We only need to check if the report ends inside headers, if the last piece + // is a call piece. + if (auto *CP = dyn_cast(LastP)) { + CP = getFirstStackedCallToHeaderFile(CP, SMgr); + if (CP) { + // Mark the piece. + CP->setAsLastInMainSourceFile(); + + // Update the path diagnostic message. + const auto *ND = dyn_cast(CP->getCallee()); + if (ND) { + SmallString<200> buf; + llvm::raw_svector_ostream os(buf); + os << " (within a call to '" << ND->getDeclName() << "')"; + PD.appendToDesc(os.str()); + } + + // Reset the report containing declaration and location. + PD.setDeclWithIssue(CP->getCaller()); + PD.setLocation(CP->getLocation()); + + return; + } } +} + + + +std::unique_ptr +PathSensitiveBugReporter::generateDiagnosticForConsumerMap( + BugReport *exampleReport, ArrayRef consumers, + ArrayRef bugReports) { + std::vector BasicBugReports; + std::vector PathSensitiveBugReports; + if (isa(exampleReport)) + return BugReporter::generateDiagnosticForConsumerMap(exampleReport, + consumers, bugReports); // Generate the full path sensitive diagnostic, using the generation scheme // specified by the PathDiagnosticConsumer. Note that we have to generate @@ -3099,8 +3205,13 @@ BugReporter::generateDiagnosticForConsumerMap( // the BugReporterVisitors may mark this bug as a false positive. assert(!bugReports.empty()); MaxBugClassSize.updateMax(bugReports.size()); - std::unique_ptr Out = - generatePathDiagnostics(consumers, bugReports); + + // Avoid copying the whole array because there may be a lot of reports. + ArrayRef convertedArrayOfReports( + reinterpret_cast(&*bugReports.begin()), + reinterpret_cast(&*bugReports.end())); + std::unique_ptr Out = generatePathDiagnostics( + consumers, convertedArrayOfReports); if (Out->empty()) return Out; @@ -3109,40 +3220,43 @@ BugReporter::generateDiagnosticForConsumerMap( // Examine the report and see if the last piece is in a header. Reset the // report location to the last piece in the main source file. - AnalyzerOptions &Opts = getAnalyzerOptions(); + const AnalyzerOptions &Opts = getAnalyzerOptions(); for (auto const &P : *Out) if (Opts.ShouldReportIssuesInMainSourceFile && !Opts.AnalyzeAll) - P.second->resetDiagnosticLocationToMainFile(); + resetDiagnosticLocationToMainFile(*P.second); return Out; } void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, - const CheckerBase *Checker, - StringRef Name, StringRef Category, - StringRef Str, PathDiagnosticLocation Loc, - ArrayRef Ranges) { - EmitBasicReport(DeclWithIssue, Checker->getCheckName(), Name, Category, Str, - Loc, Ranges); + const CheckerBase *Checker, StringRef Name, + StringRef Category, StringRef Str, + PathDiagnosticLocation Loc, + ArrayRef Ranges, + ArrayRef Fixits) { + EmitBasicReport(DeclWithIssue, Checker->getCheckerName(), Name, Category, Str, + Loc, Ranges, Fixits); } void BugReporter::EmitBasicReport(const Decl *DeclWithIssue, - CheckName CheckName, + CheckerNameRef CheckName, StringRef name, StringRef category, StringRef str, PathDiagnosticLocation Loc, - ArrayRef Ranges) { + ArrayRef Ranges, + ArrayRef Fixits) { // 'BT' is owned by BugReporter. BugType *BT = getBugTypeForName(CheckName, name, category); - auto R = llvm::make_unique(*BT, str, Loc); + auto R = llvm::make_unique(*BT, str, Loc); R->setDeclWithIssue(DeclWithIssue); - for (ArrayRef::iterator I = Ranges.begin(), E = Ranges.end(); - I != E; ++I) - R->addRange(*I); + for (const auto &SR : Ranges) + R->addRange(SR); + for (const auto &FH : Fixits) + R->addFixItHint(FH); emitReport(std::move(R)); } -BugType *BugReporter::getBugTypeForName(CheckName CheckName, StringRef name, - StringRef category) { +BugType *BugReporter::getBugTypeForName(CheckerNameRef CheckName, + StringRef name, StringRef category) { SmallString<136> fullDesc; llvm::raw_svector_ostream(fullDesc) << CheckName.getName() << ":" << name << ":" << category; diff --git a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp index c830bfa98023b..4e88fe6497e71 100644 --- a/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp +++ b/clang/lib/StaticAnalyzer/Core/BugReporterVisitors.cpp @@ -22,9 +22,11 @@ #include "clang/AST/Stmt.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/Analyses/Dominators.h" #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Basic/IdentifierTable.h" #include "clang/Basic/LLVM.h" @@ -33,7 +35,6 @@ #include "clang/Lex/Lexer.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ExplodedGraph.h" @@ -179,21 +180,60 @@ static bool hasVisibleUpdate(const ExplodedNode *LeftNode, SVal LeftVal, RLCV->getStore() == RightNode->getState()->getStore(); } -static Optional -getConcreteIntegerValue(const Expr *CondVarExpr, const ExplodedNode *N) { +static Optional getSValForVar(const Expr *CondVarExpr, + const ExplodedNode *N) { ProgramStateRef State = N->getState(); const LocationContext *LCtx = N->getLocationContext(); + assert(CondVarExpr); + CondVarExpr = CondVarExpr->IgnoreImpCasts(); + // The declaration of the value may rely on a pointer so take its l-value. - if (const auto *DRE = dyn_cast_or_null(CondVarExpr)) { - if (const auto *VD = dyn_cast_or_null(DRE->getDecl())) { - SVal DeclSVal = State->getSVal(State->getLValue(VD, LCtx)); - if (auto DeclCI = DeclSVal.getAs()) - return &DeclCI->getValue(); - } - } + // FIXME: As seen in VisitCommonDeclRefExpr, sometimes DeclRefExpr may + // evaluate to a FieldRegion when it refers to a declaration of a lambda + // capture variable. We most likely need to duplicate that logic here. + if (const auto *DRE = dyn_cast(CondVarExpr)) + if (const auto *VD = dyn_cast(DRE->getDecl())) + return State->getSVal(State->getLValue(VD, LCtx)); + + if (const auto *ME = dyn_cast(CondVarExpr)) + if (const auto *FD = dyn_cast(ME->getMemberDecl())) + if (auto FieldL = State->getSVal(ME, LCtx).getAs()) + return State->getRawSVal(*FieldL, FD->getType()); - return {}; + return None; +} + +static Optional +getConcreteIntegerValue(const Expr *CondVarExpr, const ExplodedNode *N) { + + if (Optional V = getSValForVar(CondVarExpr, N)) + if (auto CI = V->getAs()) + return &CI->getValue(); + return None; +} + +static bool isVarAnInterestingCondition(const Expr *CondVarExpr, + const ExplodedNode *N, + const PathSensitiveBugReport *B) { + // Even if this condition is marked as interesting, it isn't *that* + // interesting if it didn't happen in a nested stackframe, the user could just + // follow the arrows. + if (!B->getErrorNode()->getStackFrame()->isParentOf(N->getStackFrame())) + return false; + + if (Optional V = getSValForVar(CondVarExpr, N)) + if (Optional K = B->getInterestingnessKind(*V)) + return *K == bugreporter::TrackingKind::Condition; + + return false; +} + +static bool isInterestingExpr(const Expr *E, const ExplodedNode *N, + const PathSensitiveBugReport *B) { + if (Optional V = getSValForVar(E, N)) + return B->getInterestingnessKind(*V).hasValue(); + return false; } /// \return name of the macro inside the location \p Loc. @@ -254,21 +294,21 @@ static bool wasRegionOfInterestModifiedAt(const SubRegion *RegionOfInterest, // Implementation of BugReporterVisitor. //===----------------------------------------------------------------------===// -std::shared_ptr -BugReporterVisitor::getEndPath(BugReporterContext &, - const ExplodedNode *, BugReport &) { +PathDiagnosticPieceRef BugReporterVisitor::getEndPath(BugReporterContext &, + const ExplodedNode *, + PathSensitiveBugReport &) { return nullptr; } -void -BugReporterVisitor::finalizeVisitor(BugReporterContext &, - const ExplodedNode *, BugReport &) {} - -std::shared_ptr BugReporterVisitor::getDefaultEndPath( - BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { - PathDiagnosticLocation L = - PathDiagnosticLocation::createEndOfPath(EndPathNode,BRC.getSourceManager()); +void BugReporterVisitor::finalizeVisitor(BugReporterContext &, + const ExplodedNode *, + PathSensitiveBugReport &) {} +PathDiagnosticPieceRef +BugReporterVisitor::getDefaultEndPath(const BugReporterContext &BRC, + const ExplodedNode *EndPathNode, + const PathSensitiveBugReport &BR) { + PathDiagnosticLocation L = BR.getLocation(); const auto &Ranges = BR.getRanges(); // Only add the statement itself as a range if we didn't specify any @@ -296,6 +336,7 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { MemRegionManager &MmrMgr; const SourceManager &SM; const PrintingPolicy &PP; + bugreporter::TrackingKind TKind; /// Recursion limit for dereferencing fields when looking for the /// region of interest. @@ -316,10 +357,10 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { using RegionVector = SmallVector; public: - NoStoreFuncVisitor(const SubRegion *R) + NoStoreFuncVisitor(const SubRegion *R, bugreporter::TrackingKind TKind) : RegionOfInterest(R), MmrMgr(*R->getMemRegionManager()), SM(MmrMgr.getContext().getSourceManager()), - PP(MmrMgr.getContext().getPrintingPolicy()) {} + PP(MmrMgr.getContext().getPrintingPolicy()), TKind(TKind) {} void Profile(llvm::FoldingSetNodeID &ID) const override { static int Tag = 0; @@ -332,9 +373,9 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { return static_cast(&Tag); } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BR, - BugReport &R) override; + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BR, + PathSensitiveBugReport &R) override; private: /// Attempts to find the region of interest in a given record decl, @@ -367,11 +408,11 @@ class NoStoreFuncVisitor final : public BugReporterVisitor { /// either emit a note or suppress the report enirely. /// \return Diagnostics piece for region not modified in the current function, /// if it decides to emit one. - std::shared_ptr - maybeEmitNote(BugReport &R, const CallEvent &Call, const ExplodedNode *N, - const RegionVector &FieldChain, const MemRegion *MatchedRegion, - StringRef FirstElement, bool FirstIsReferenceType, - unsigned IndirectionLevel); + PathDiagnosticPieceRef + maybeEmitNote(PathSensitiveBugReport &R, const CallEvent &Call, + const ExplodedNode *N, const RegionVector &FieldChain, + const MemRegion *MatchedRegion, StringRef FirstElement, + bool FirstIsReferenceType, unsigned IndirectionLevel); /// Pretty-print region \p MatchedRegion to \p os. /// \return Whether printing succeeded. @@ -500,9 +541,9 @@ NoStoreFuncVisitor::findRegionOfInterestInRecord( return None; } -std::shared_ptr +PathDiagnosticPieceRef NoStoreFuncVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BR, - BugReport &R) { + PathSensitiveBugReport &R) { const LocationContext *Ctx = N->getLocationContext(); const StackFrameContext *SCtx = Ctx->getStackFrame(); @@ -610,8 +651,11 @@ void NoStoreFuncVisitor::findModifyingFrames(const ExplodedNode *N) { } while (N); } -std::shared_ptr NoStoreFuncVisitor::maybeEmitNote( - BugReport &R, const CallEvent &Call, const ExplodedNode *N, +static llvm::StringLiteral WillBeUsedForACondition = + ", which participates in a condition later"; + +PathDiagnosticPieceRef NoStoreFuncVisitor::maybeEmitNote( + PathSensitiveBugReport &R, const CallEvent &Call, const ExplodedNode *N, const RegionVector &FieldChain, const MemRegion *MatchedRegion, StringRef FirstElement, bool FirstIsReferenceType, unsigned IndirectionLevel) { @@ -656,6 +700,8 @@ std::shared_ptr NoStoreFuncVisitor::maybeEmitNote( return nullptr; os << "'"; + if (TKind == bugreporter::TrackingKind::Condition) + os << WillBeUsedForACondition; return std::make_shared(L, os.str()); } @@ -751,13 +797,12 @@ class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { bool WasModified = false; public: - MacroNullReturnSuppressionVisitor(const SubRegion *R, - const SVal V) : RegionOfInterest(R), - ValueAtDereference(V) {} + MacroNullReturnSuppressionVisitor(const SubRegion *R, const SVal V) + : RegionOfInterest(R), ValueAtDereference(V) {} - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override { + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override { if (WasModified) return nullptr; @@ -783,7 +828,7 @@ class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { static void addMacroVisitorIfNecessary( const ExplodedNode *N, const MemRegion *R, - bool EnableNullFPSuppression, BugReport &BR, + bool EnableNullFPSuppression, PathSensitiveBugReport &BR, const SVal V) { AnalyzerOptions &Options = N->getState()->getAnalysisManager().options; if (EnableNullFPSuppression && @@ -805,7 +850,7 @@ class MacroNullReturnSuppressionVisitor final : public BugReporterVisitor { /// \return Source location of right hand side of an assignment /// into \c RegionOfInterest, empty optional if none found. Optional matchAssignment(const ExplodedNode *N) { - const Stmt *S = PathDiagnosticLocation::getStmt(N); + const Stmt *S = N->getStmtForDiagnostics(); ProgramStateRef State = N->getState(); auto *LCtx = N->getLocationContext(); if (!S) @@ -840,7 +885,7 @@ namespace { /// This visitor is intended to be used when another visitor discovers that an /// interesting value comes from an inlined function call. class ReturnVisitor : public BugReporterVisitor { - const StackFrameContext *StackFrame; + const StackFrameContext *CalleeSFC; enum { Initial, MaybeUnsuppress, @@ -850,13 +895,13 @@ class ReturnVisitor : public BugReporterVisitor { bool EnableNullFPSuppression; bool ShouldInvalidate = true; AnalyzerOptions& Options; + bugreporter::TrackingKind TKind; public: - ReturnVisitor(const StackFrameContext *Frame, - bool Suppressed, - AnalyzerOptions &Options) - : StackFrame(Frame), EnableNullFPSuppression(Suppressed), - Options(Options) {} + ReturnVisitor(const StackFrameContext *Frame, bool Suppressed, + AnalyzerOptions &Options, bugreporter::TrackingKind TKind) + : CalleeSFC(Frame), EnableNullFPSuppression(Suppressed), + Options(Options), TKind(TKind) {} static void *getTag() { static int Tag = 0; @@ -865,7 +910,7 @@ class ReturnVisitor : public BugReporterVisitor { void Profile(llvm::FoldingSetNodeID &ID) const override { ID.AddPointer(ReturnVisitor::getTag()); - ID.AddPointer(StackFrame); + ID.AddPointer(CalleeSFC); ID.AddBoolean(EnableNullFPSuppression); } @@ -877,8 +922,9 @@ class ReturnVisitor : public BugReporterVisitor { /// the statement is a call that was inlined, we add the visitor to the /// bug report, so it can print a note later. static void addVisitorIfNecessary(const ExplodedNode *Node, const Stmt *S, - BugReport &BR, - bool InEnableNullFPSuppression) { + PathSensitiveBugReport &BR, + bool InEnableNullFPSuppression, + bugreporter::TrackingKind TKind) { if (!CallEvent::isCallStmt(S)) return; @@ -949,17 +995,16 @@ class ReturnVisitor : public BugReporterVisitor { if (Optional RetLoc = RetVal.getAs()) EnableNullFPSuppression = State->isNull(*RetLoc).isConstrainedTrue(); - BR.markInteresting(CalleeContext); BR.addVisitor(llvm::make_unique(CalleeContext, EnableNullFPSuppression, - Options)); + Options, TKind)); } - std::shared_ptr - visitNodeInitial(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { + PathDiagnosticPieceRef visitNodeInitial(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { // Only print a message at the interesting return statement. - if (N->getLocationContext() != StackFrame) + if (N->getLocationContext() != CalleeSFC) return nullptr; Optional SP = N->getLocationAs(); @@ -973,7 +1018,7 @@ class ReturnVisitor : public BugReporterVisitor { // Okay, we're at the right return statement, but do we have the return // value available? ProgramStateRef State = N->getState(); - SVal V = State->getSVal(Ret, StackFrame); + SVal V = State->getSVal(Ret, CalleeSFC); if (V.isUnknownOrUndef()) return nullptr; @@ -1000,13 +1045,16 @@ class ReturnVisitor : public BugReporterVisitor { RetE = RetE->IgnoreParenCasts(); - // If we're returning 0, we should track where that 0 came from. - bugreporter::trackExpressionValue(N, RetE, BR, EnableNullFPSuppression); + // Let's track the return value. + bugreporter::trackExpressionValue( + N, RetE, BR, TKind, EnableNullFPSuppression); // Build an appropriate message based on the return value. SmallString<64> Msg; llvm::raw_svector_ostream Out(Msg); + bool WouldEventBeMeaningless = false; + if (State->isNull(V).isConstrainedTrue()) { if (V.getAs()) { @@ -1029,10 +1077,19 @@ class ReturnVisitor : public BugReporterVisitor { } else { if (auto CI = V.getAs()) { Out << "Returning the value " << CI->getValue(); - } else if (V.getAs()) { - Out << "Returning pointer"; } else { - Out << "Returning value"; + // There is nothing interesting about returning a value, when it is + // plain value without any constraints, and the function is guaranteed + // to return that every time. We could use CFG::isLinear() here, but + // constexpr branches are obvious to the compiler, not necesserily to + // the programmer. + if (N->getCFG().size() == 3) + WouldEventBeMeaningless = true; + + if (V.getAs()) + Out << "Returning pointer"; + else + Out << "Returning value"; } } @@ -1051,26 +1108,36 @@ class ReturnVisitor : public BugReporterVisitor { Out << " (loaded from '" << *DD << "')"; } - PathDiagnosticLocation L(Ret, BRC.getSourceManager(), StackFrame); + PathDiagnosticLocation L(Ret, BRC.getSourceManager(), CalleeSFC); if (!L.isValid() || !L.asLocation().isValid()) return nullptr; - return std::make_shared(L, Out.str()); + if (TKind == bugreporter::TrackingKind::Condition) + Out << WillBeUsedForACondition; + + auto EventPiece = std::make_shared(L, Out.str()); + + // If we determined that the note is meaningless, make it prunable, and + // don't mark the stackframe interesting. + if (WouldEventBeMeaningless) + EventPiece->setPrunable(true); + else + BR.markInteresting(CalleeSFC); + + return EventPiece; } - std::shared_ptr - visitNodeMaybeUnsuppress(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { -#ifndef NDEBUG + PathDiagnosticPieceRef visitNodeMaybeUnsuppress(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { assert(Options.ShouldAvoidSuppressingNullArgumentPaths); -#endif // Are we at the entry node for this call? Optional CE = N->getLocationAs(); if (!CE) return nullptr; - if (CE->getCalleeContext() != StackFrame) + if (CE->getCalleeContext() != CalleeSFC) return nullptr; Mode = Satisfied; @@ -1082,7 +1149,7 @@ class ReturnVisitor : public BugReporterVisitor { CallEventManager &CallMgr = StateMgr.getCallEventManager(); ProgramStateRef State = N->getState(); - CallEventRef<> Call = CallMgr.getCaller(StackFrame, State); + CallEventRef<> Call = CallMgr.getCaller(CalleeSFC, State); for (unsigned I = 0, E = Call->getNumArgs(); I != E; ++I) { Optional ArgV = Call->getArgSVal(I).getAs(); if (!ArgV) @@ -1096,7 +1163,7 @@ class ReturnVisitor : public BugReporterVisitor { if (!State->isNull(*ArgV).isConstrainedTrue()) continue; - if (bugreporter::trackExpressionValue(N, ArgE, BR, EnableNullFPSuppression)) + if (trackExpressionValue(N, ArgE, BR, TKind, EnableNullFPSuppression)) ShouldInvalidate = false; // If we /can't/ track the null pointer, we should err on the side of @@ -1107,9 +1174,9 @@ class ReturnVisitor : public BugReporterVisitor { return nullptr; } - std::shared_ptr VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, - BugReport &BR) override { + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override { switch (Mode) { case Initial: return visitNodeInitial(N, BRC, BR); @@ -1123,9 +1190,9 @@ class ReturnVisitor : public BugReporterVisitor { } void finalizeVisitor(BugReporterContext &, const ExplodedNode *, - BugReport &BR) override { + PathSensitiveBugReport &BR) override { if (EnableNullFPSuppression && ShouldInvalidate) - BR.markInvalid(ReturnVisitor::getTag(), StackFrame); + BR.markInvalid(ReturnVisitor::getTag(), CalleeSFC); } }; @@ -1140,6 +1207,7 @@ void FindLastStoreBRVisitor::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(&tag); ID.AddPointer(R); ID.Add(V); + ID.AddInteger(static_cast(TKind)); ID.AddBoolean(EnableNullFPSuppression); } @@ -1245,9 +1313,8 @@ static void showBRParamDiagnostics(llvm::raw_svector_ostream& os, } /// Show default diagnostics for storing bad region. -static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os, - const MemRegion *R, - SVal V) { +static void showBRDefaultDiagnostics(llvm::raw_svector_ostream &os, + const MemRegion *R, SVal V) { if (V.getAs()) { bool b = false; if (R->isBoundable()) { @@ -1290,9 +1357,10 @@ static void showBRDefaultDiagnostics(llvm::raw_svector_ostream& os, } } -std::shared_ptr +PathDiagnosticPieceRef FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { if (Satisfied) return nullptr; @@ -1313,7 +1381,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, // should track the initializer expression. if (Optional PIP = Pred->getLocationAs()) { const MemRegion *FieldReg = (const MemRegion *)PIP->getLocationValue(); - if (FieldReg && FieldReg == R) { + if (FieldReg == R) { StoreSite = Pred; InitE = PIP->getInitializer()->getInit(); } @@ -1370,22 +1438,23 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (!StoreSite) return nullptr; + Satisfied = true; // If we have an expression that provided the value, try to track where it // came from. if (InitE) { - if (V.isUndef() || - V.getAs() || V.getAs()) { - if (!IsParam) - InitE = InitE->IgnoreParenCasts(); - bugreporter::trackExpressionValue(StoreSite, InitE, BR, - EnableNullFPSuppression); - } - ReturnVisitor::addVisitorIfNecessary(StoreSite, InitE->IgnoreParenCasts(), - BR, EnableNullFPSuppression); + if (!IsParam) + InitE = InitE->IgnoreParenCasts(); + + bugreporter::trackExpressionValue( + StoreSite, InitE, BR, TKind, EnableNullFPSuppression); } + if (TKind == TrackingKind::Condition && + !OriginSFC->isParentOf(StoreSite->getStackFrame())) + return nullptr; + // Okay, we've found the binding. Emit an appropriate message. SmallString<256> sbuf; llvm::raw_svector_ostream os(sbuf); @@ -1411,7 +1480,7 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (const VarRegion *OriginalR = BDR->getOriginalRegion(VR)) { if (auto KV = State->getSVal(OriginalR).getAs()) BR.addVisitor(llvm::make_unique( - *KV, OriginalR, EnableNullFPSuppression)); + *KV, OriginalR, EnableNullFPSuppression, TKind, OriginSFC)); } } } @@ -1427,6 +1496,9 @@ FindLastStoreBRVisitor::VisitNode(const ExplodedNode *Succ, if (os.str().empty()) showBRDefaultDiagnostics(os, R, V); + if (TKind == bugreporter::TrackingKind::Condition) + os << WillBeUsedForACondition; + // Construct a new PathDiagnosticPiece. ProgramPoint P = StoreSite->getLocation(); PathDiagnosticLocation L; @@ -1466,9 +1538,8 @@ bool TrackConstraintBRVisitor::isUnderconstrained(const ExplodedNode *N) const { return (bool)N->getState()->assume(Constraint, !Assumption); } -std::shared_ptr -TrackConstraintBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &) { +PathDiagnosticPieceRef TrackConstraintBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &BRC, PathSensitiveBugReport &) { const ExplodedNode *PrevN = N->getFirstPred(); if (IsSatisfied) return nullptr; @@ -1546,10 +1617,10 @@ const char *SuppressInlineDefensiveChecksVisitor::getTag() { return "IDCVisitor"; } -std::shared_ptr +PathDiagnosticPieceRef SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, BugReporterContext &BRC, - BugReport &BR) { + PathSensitiveBugReport &BR) { const ExplodedNode *Pred = Succ->getFirstPred(); if (IsSatisfied) return nullptr; @@ -1618,6 +1689,139 @@ SuppressInlineDefensiveChecksVisitor::VisitNode(const ExplodedNode *Succ, return nullptr; } +//===----------------------------------------------------------------------===// +// TrackControlDependencyCondBRVisitor. +//===----------------------------------------------------------------------===// + +namespace { +/// Tracks the expressions that are a control dependency of the node that was +/// supplied to the constructor. +/// For example: +/// +/// cond = 1; +/// if (cond) +/// 10 / 0; +/// +/// An error is emitted at line 3. This visitor realizes that the branch +/// on line 2 is a control dependency of line 3, and tracks it's condition via +/// trackExpressionValue(). +class TrackControlDependencyCondBRVisitor final : public BugReporterVisitor { + const ExplodedNode *Origin; + ControlDependencyCalculator ControlDeps; + llvm::SmallSet VisitedBlocks; + +public: + TrackControlDependencyCondBRVisitor(const ExplodedNode *O) + : Origin(O), ControlDeps(&O->getCFG()) {} + + void Profile(llvm::FoldingSetNodeID &ID) const override { + static int x = 0; + ID.AddPointer(&x); + } + + PathDiagnosticPieceRef VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) override; +}; +} // end of anonymous namespace + +static std::shared_ptr +constructDebugPieceForTrackedCondition(const Expr *Cond, + const ExplodedNode *N, + BugReporterContext &BRC) { + + if (BRC.getAnalyzerOptions().AnalysisDiagOpt == PD_NONE || + !BRC.getAnalyzerOptions().ShouldTrackConditionsDebug) + return nullptr; + + std::string ConditionText = Lexer::getSourceText( + CharSourceRange::getTokenRange(Cond->getSourceRange()), + BRC.getSourceManager(), + BRC.getASTContext().getLangOpts()); + + return std::make_shared( + PathDiagnosticLocation::createBegin( + Cond, BRC.getSourceManager(), N->getLocationContext()), + (Twine() + "Tracking condition '" + ConditionText + "'").str()); +} + +static bool isAssertlikeBlock(const CFGBlock *B, ASTContext &Context) { + if (B->succ_size() != 2) + return false; + + const CFGBlock *Then = B->succ_begin()->getReachableBlock(); + const CFGBlock *Else = (B->succ_begin() + 1)->getReachableBlock(); + + if (!Then || !Else) + return false; + + if (Then->isInevitablySinking() != Else->isInevitablySinking()) + return true; + + // For the following condition the following CFG would be built: + // + // -------------> + // / \ + // [B1] -> [B2] -> [B3] -> [sink] + // assert(A && B || C); \ \ + // -----------> [go on with the execution] + // + // It so happens that CFGBlock::getTerminatorCondition returns 'A' for block + // B1, 'A && B' for B2, and 'A && B || C' for B3. Let's check whether we + // reached the end of the condition! + if (const Stmt *ElseCond = Else->getTerminatorCondition()) + if (const auto *BinOp = dyn_cast(ElseCond)) + if (BinOp->isLogicalOp()) + return isAssertlikeBlock(Else, Context); + + return false; +} + +PathDiagnosticPieceRef +TrackControlDependencyCondBRVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { + // We can only reason about control dependencies within the same stack frame. + if (Origin->getStackFrame() != N->getStackFrame()) + return nullptr; + + CFGBlock *NB = const_cast(N->getCFGBlock()); + + // Skip if we already inspected this block. + if (!VisitedBlocks.insert(NB).second) + return nullptr; + + CFGBlock *OriginB = const_cast(Origin->getCFGBlock()); + + // TODO: Cache CFGBlocks for each ExplodedNode. + if (!OriginB || !NB) + return nullptr; + + if (isAssertlikeBlock(NB, BRC.getASTContext())) + return nullptr; + + if (ControlDeps.isControlDependent(OriginB, NB)) { + // We don't really want to explain for range loops. Evidence suggests that + // the only thing that leads to is the addition of calls to operator!=. + if (llvm::isa_and_nonnull(NB->getTerminatorStmt())) + return nullptr; + + if (const Expr *Condition = NB->getLastCondition()) { + // Keeping track of the already tracked conditions on a visitor level + // isn't sufficient, because a new visitor is created for each tracked + // expression, hence the BugReport level set. + if (BR.addTrackedCondition(N)) { + bugreporter::trackExpressionValue( + N, Condition, BR, bugreporter::TrackingKind::Condition, + /*EnableNullFPSuppression=*/false); + return constructDebugPieceForTrackedCondition(Condition, N, BRC); + } + } + } + + return nullptr; +} + //===----------------------------------------------------------------------===// // Implementation of trackExpressionValue. //===----------------------------------------------------------------------===// @@ -1714,7 +1918,7 @@ static const Expr *peelOffOuterExpr(const Expr *Ex, static const ExplodedNode* findNodeForExpression(const ExplodedNode *N, const Expr *Inner) { while (N) { - if (PathDiagnosticLocation::getStmt(N) == Inner) + if (N->getStmtForDiagnostics() == Inner) return N; N = N->getFirstPred(); } @@ -1722,8 +1926,11 @@ static const ExplodedNode* findNodeForExpression(const ExplodedNode *N, } bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, - const Expr *E, BugReport &report, + const Expr *E, + PathSensitiveBugReport &report, + bugreporter::TrackingKind TKind, bool EnableNullFPSuppression) { + if (!E || !InputNode) return false; @@ -1733,17 +1940,28 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, return false; ProgramStateRef LVState = LVNode->getState(); + const StackFrameContext *SFC = LVNode->getStackFrame(); + + // We only track expressions if we believe that they are important. Chances + // are good that control dependencies to the tracking point are also improtant + // because of this, let's explain why we believe control reached this point. + // TODO: Shouldn't we track control dependencies of every bug location, rather + // than only tracked expressions? + if (LVState->getAnalysisManager().getAnalyzerOptions().ShouldTrackConditions) + report.addVisitor(llvm::make_unique( + InputNode)); // The message send could be nil due to the receiver being nil. // At this point in the path, the receiver should be live since we are at the // message send expr. If it is nil, start tracking it. if (const Expr *Receiver = NilReceiverBRVisitor::getNilReceiver(Inner, LVNode)) - trackExpressionValue(LVNode, Receiver, report, EnableNullFPSuppression); + trackExpressionValue( + LVNode, Receiver, report, TKind, EnableNullFPSuppression); // Track the index if this is an array subscript. if (const auto *Arr = dyn_cast(Inner)) trackExpressionValue( - LVNode, Arr->getIdx(), report, /*EnableNullFPSuppression*/ false); + LVNode, Arr->getIdx(), report, TKind, /*EnableNullFPSuppression*/false); // See if the expression we're interested refers to a variable. // If so, we can track both its contents and constraints on its value. @@ -1759,7 +1977,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (RR && !LVIsNull) if (auto KV = LVal.getAs()) report.addVisitor(llvm::make_unique( - *KV, RR, EnableNullFPSuppression)); + *KV, RR, EnableNullFPSuppression, TKind, SFC)); // In case of C++ references, we want to differentiate between a null // reference and reference to null pointer. @@ -1773,17 +1991,18 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, // Mark both the variable region and its contents as interesting. SVal V = LVState->getRawSVal(loc::MemRegionVal(R)); report.addVisitor( - llvm::make_unique(cast(R))); + llvm::make_unique(cast(R), TKind)); MacroNullReturnSuppressionVisitor::addMacroVisitorIfNecessary( LVNode, R, EnableNullFPSuppression, report, V); - report.markInteresting(V); + report.markInteresting(V, TKind); report.addVisitor(llvm::make_unique(R)); - // If the contents are symbolic, find out when they became null. - if (V.getAsLocSymbol(/*IncludeBaseRegions*/ true)) - report.addVisitor(llvm::make_unique( + // If the contents are symbolic and null, find out when they became null. + if (V.getAsLocSymbol(/*IncludeBaseRegions=*/true)) + if (LVState->isNull(V).isConstrainedTrue()) + report.addVisitor(llvm::make_unique( V.castAs(), false)); // Add visitor, which will suppress inline defensive checks. @@ -1796,7 +2015,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, if (auto KV = V.getAs()) report.addVisitor(llvm::make_unique( - *KV, R, EnableNullFPSuppression)); + *KV, R, EnableNullFPSuppression, TKind, SFC)); return true; } } @@ -1806,7 +2025,7 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, SVal V = LVState->getSValAsScalarOrLoc(Inner, LVNode->getLocationContext()); ReturnVisitor::addVisitorIfNecessary( - LVNode, Inner, report, EnableNullFPSuppression); + LVNode, Inner, report, EnableNullFPSuppression, TKind); // Is it a symbolic value? if (auto L = V.getAs()) { @@ -1817,30 +2036,31 @@ bool bugreporter::trackExpressionValue(const ExplodedNode *InputNode, // We should not try to dereference pointers at all when we don't care // what is written inside the pointer. bool CanDereference = true; - if (const auto *SR = dyn_cast(L->getRegion())) + if (const auto *SR = L->getRegionAs()) { if (SR->getSymbol()->getType()->getPointeeType()->isVoidType()) CanDereference = false; + } else if (L->getRegionAs()) + CanDereference = false; // At this point we are dealing with the region's LValue. // However, if the rvalue is a symbolic region, we should track it as well. // Try to use the correct type when looking up the value. SVal RVal; - if (ExplodedGraph::isInterestingLValueExpr(Inner)) { + if (ExplodedGraph::isInterestingLValueExpr(Inner)) RVal = LVState->getRawSVal(L.getValue(), Inner->getType()); - } else if (CanDereference) { + else if (CanDereference) RVal = LVState->getSVal(L->getRegion()); - } if (CanDereference) if (auto KV = RVal.getAs()) report.addVisitor(llvm::make_unique( - *KV, L->getRegion(), EnableNullFPSuppression)); + *KV, L->getRegion(), EnableNullFPSuppression, TKind, SFC)); const MemRegion *RegionRVal = RVal.getAsRegion(); if (RegionRVal && isa(RegionRVal)) { - report.markInteresting(RegionRVal); + report.markInteresting(RegionRVal, TKind); report.addVisitor(llvm::make_unique( - loc::MemRegionVal(RegionRVal), /*assumption=*/false)); + loc::MemRegionVal(RegionRVal), /*assumption=*/false)); } } return true; @@ -1864,9 +2084,9 @@ const Expr *NilReceiverBRVisitor::getNilReceiver(const Stmt *S, return nullptr; } -std::shared_ptr -NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { Optional P = N->getLocationAs(); if (!P) return nullptr; @@ -1892,66 +2112,26 @@ NilReceiverBRVisitor::VisitNode(const ExplodedNode *N, // The receiver was nil, and hence the method was skipped. // Register a BugReporterVisitor to issue a message telling us how // the receiver was null. - bugreporter::trackExpressionValue(N, Receiver, BR, - /*EnableNullFPSuppression*/ false); + bugreporter::trackExpressionValue( + N, Receiver, BR, bugreporter::TrackingKind::Thorough, + /*EnableNullFPSuppression*/ false); // Issue a message saying that the method was skipped. PathDiagnosticLocation L(Receiver, BRC.getSourceManager(), N->getLocationContext()); return std::make_shared(L, OS.str()); } -//===----------------------------------------------------------------------===// -// Implementation of FindLastStoreBRVisitor. -//===----------------------------------------------------------------------===// - -// Registers every VarDecl inside a Stmt with a last store visitor. -void FindLastStoreBRVisitor::registerStatementVarDecls(BugReport &BR, - const Stmt *S, - bool EnableNullFPSuppression) { - const ExplodedNode *N = BR.getErrorNode(); - std::deque WorkList; - WorkList.push_back(S); - - while (!WorkList.empty()) { - const Stmt *Head = WorkList.front(); - WorkList.pop_front(); - - ProgramStateManager &StateMgr = N->getState()->getStateManager(); - - if (const auto *DR = dyn_cast(Head)) { - if (const auto *VD = dyn_cast(DR->getDecl())) { - const VarRegion *R = - StateMgr.getRegionManager().getVarRegion(VD, N->getLocationContext()); - - // What did we load? - SVal V = N->getSVal(S); - - if (V.getAs() || V.getAs()) { - // Register a new visitor with the BugReport. - BR.addVisitor(llvm::make_unique( - V.castAs(), R, EnableNullFPSuppression)); - } - } - } - - for (const Stmt *SubStmt : Head->children()) - WorkList.push_back(SubStmt); - } -} - //===----------------------------------------------------------------------===// // Visitor that tries to report interesting diagnostics from conditions. //===----------------------------------------------------------------------===// /// Return the tag associated with this visitor. This tag will be used /// to make all PathDiagnosticPieces created by this visitor. -const char *ConditionBRVisitor::getTag() { - return "ConditionBRVisitor"; -} +const char *ConditionBRVisitor::getTag() { return "ConditionBRVisitor"; } -std::shared_ptr -ConditionBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +ConditionBRVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { auto piece = VisitNodeImpl(N, BRC, BR); if (piece) { piece->setTag(getTag()); @@ -1961,9 +2141,10 @@ ConditionBRVisitor::VisitNode(const ExplodedNode *N, return piece; } -std::shared_ptr +PathDiagnosticPieceRef ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { + BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramPoint ProgPoint = N->getLocation(); const std::pair &Tags = ExprEngine::geteagerlyAssumeBinOpBifurcationTags(); @@ -1999,9 +2180,10 @@ ConditionBRVisitor::VisitNodeImpl(const ExplodedNode *N, return nullptr; } -std::shared_ptr ConditionBRVisitor::VisitTerminator( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTerminator( const Stmt *Term, const ExplodedNode *N, const CFGBlock *srcBlk, - const CFGBlock *dstBlk, BugReport &R, BugReporterContext &BRC) { + const CFGBlock *dstBlk, PathSensitiveBugReport &R, + BugReporterContext &BRC) { const Expr *Cond = nullptr; // In the code below, Term is a CFG terminator and Cond is a branch condition @@ -2056,10 +2238,10 @@ std::shared_ptr ConditionBRVisitor::VisitTerminator( return VisitTrueTest(Cond, BRC, R, N, TookTrue); } -std::shared_ptr +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest(const Expr *Cond, BugReporterContext &BRC, - BugReport &R, const ExplodedNode *N, - bool TookTrue) { + PathSensitiveBugReport &R, + const ExplodedNode *N, bool TookTrue) { ProgramStateRef CurrentState = N->getState(); ProgramStateRef PrevState = N->getFirstPred()->getState(); const LocationContext *LCtx = N->getLocationContext(); @@ -2129,7 +2311,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, const Expr *ParentEx, raw_ostream &Out, BugReporterContext &BRC, - BugReport &report, + PathSensitiveBugReport &report, const ExplodedNode *N, Optional &prunable, bool IsSameFieldName) { @@ -2144,7 +2326,7 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, SourceLocation BeginLoc = OriginalExpr->getBeginLoc(); SourceLocation EndLoc = OriginalExpr->getEndLoc(); if (BeginLoc.isMacroID() && EndLoc.isMacroID()) { - SourceManager &SM = BRC.getSourceManager(); + const SourceManager &SM = BRC.getSourceManager(); const LangOptions &LO = BRC.getASTContext().getLangOpts(); if (Lexer::isAtStartOfMacroExpansion(BeginLoc, SM, LO) && Lexer::isAtEndOfMacroExpansion(EndLoc, SM, LO)) { @@ -2212,21 +2394,22 @@ bool ConditionBRVisitor::patternMatch(const Expr *Ex, return false; } -std::shared_ptr ConditionBRVisitor::VisitTrueTest( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const BinaryOperator *BExpr, BugReporterContext &BRC, - BugReport &R, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { + PathSensitiveBugReport &R, const ExplodedNode *N, bool TookTrue, + bool IsAssuming) { bool shouldInvert = false; Optional shouldPrune; // Check if the field name of the MemberExprs is ambiguous. Example: // " 'a.d' is equal to 'h.d' " in 'test/Analysis/null-deref-path-notes.cpp'. bool IsSameFieldName = false; - if (const auto *LhsME = - dyn_cast(BExpr->getLHS()->IgnoreParenCasts())) - if (const auto *RhsME = - dyn_cast(BExpr->getRHS()->IgnoreParenCasts())) - IsSameFieldName = LhsME->getMemberDecl()->getName() == - RhsME->getMemberDecl()->getName(); + const auto *LhsME = dyn_cast(BExpr->getLHS()->IgnoreParenCasts()); + const auto *RhsME = dyn_cast(BExpr->getRHS()->IgnoreParenCasts()); + + if (LhsME && RhsME) + IsSameFieldName = + LhsME->getMemberDecl()->getName() == RhsME->getMemberDecl()->getName(); SmallString<128> LhsString, RhsString; { @@ -2296,25 +2479,44 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( Out << (shouldInvert ? LhsString : RhsString); const LocationContext *LCtx = N->getLocationContext(); - PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); + const SourceManager &SM = BRC.getSourceManager(); + + if (isVarAnInterestingCondition(BExpr->getLHS(), N, &R) || + isVarAnInterestingCondition(BExpr->getRHS(), N, &R)) + Out << WillBeUsedForACondition; // Convert 'field ...' to 'Field ...' if it is a MemberExpr. std::string Message = Out.str(); Message[0] = toupper(Message[0]); - // If we know the value create a pop-up note. - if (!IsAssuming) + // If we know the value create a pop-up note to the value part of 'BExpr'. + if (!IsAssuming) { + PathDiagnosticLocation Loc; + if (!shouldInvert) { + if (LhsME && LhsME->getMemberLoc().isValid()) + Loc = PathDiagnosticLocation(LhsME->getMemberLoc(), SM); + else + Loc = PathDiagnosticLocation(BExpr->getLHS(), SM, LCtx); + } else { + if (RhsME && RhsME->getMemberLoc().isValid()) + Loc = PathDiagnosticLocation(RhsME->getMemberLoc(), SM); + else + Loc = PathDiagnosticLocation(BExpr->getRHS(), SM, LCtx); + } + return std::make_shared(Loc, Message); + } + PathDiagnosticLocation Loc(Cond, SM, LCtx); auto event = std::make_shared(Loc, Message); if (shouldPrune.hasValue()) event->setPrunable(shouldPrune.getValue()); return event; } -std::shared_ptr ConditionBRVisitor::VisitConditionVariable( +PathDiagnosticPieceRef ConditionBRVisitor::VisitConditionVariable( StringRef LhsString, const Expr *CondVarExpr, BugReporterContext &BRC, - BugReport &report, const ExplodedNode *N, bool TookTrue) { + PathSensitiveBugReport &report, const ExplodedNode *N, bool TookTrue) { // FIXME: If there's already a constraint tracker for this variable, // we shouldn't emit anything here (c.f. the double note in // test/Analysis/inlining/path-notes.c) @@ -2327,24 +2529,22 @@ std::shared_ptr ConditionBRVisitor::VisitConditionVariable( const LocationContext *LCtx = N->getLocationContext(); PathDiagnosticLocation Loc(CondVarExpr, BRC.getSourceManager(), LCtx); + + if (isVarAnInterestingCondition(CondVarExpr, N, &report)) + Out << WillBeUsedForACondition; + auto event = std::make_shared(Loc, Out.str()); - if (const auto *DR = dyn_cast(CondVarExpr)) { - if (const auto *VD = dyn_cast(DR->getDecl())) { - const ProgramState *state = N->getState().get(); - if (const MemRegion *R = state->getLValue(VD, LCtx).getAsRegion()) { - if (report.isInteresting(R)) - event->setPrunable(false); - } - } - } + if (isInterestingExpr(CondVarExpr, N, &report)) + event->setPrunable(false); return event; } -std::shared_ptr ConditionBRVisitor::VisitTrueTest( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const DeclRefExpr *DRE, BugReporterContext &BRC, - BugReport &report, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { + PathSensitiveBugReport &report, const ExplodedNode *N, bool TookTrue, + bool IsAssuming) { const auto *VD = dyn_cast(DRE->getDecl()); if (!VD) return nullptr; @@ -2358,29 +2558,29 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( return nullptr; const LocationContext *LCtx = N->getLocationContext(); - PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); - // If we know the value create a pop-up note. - if (!IsAssuming) + if (isVarAnInterestingCondition(DRE, N, &report)) + Out << WillBeUsedForACondition; + + // If we know the value create a pop-up note to the 'DRE'. + if (!IsAssuming) { + PathDiagnosticLocation Loc(DRE, BRC.getSourceManager(), LCtx); return std::make_shared(Loc, Out.str()); + } + PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); auto event = std::make_shared(Loc, Out.str()); - const ProgramState *state = N->getState().get(); - if (const MemRegion *R = state->getLValue(VD, LCtx).getAsRegion()) { - if (report.isInteresting(R)) - event->setPrunable(false); - else { - SVal V = state->getSVal(R); - if (report.isInteresting(V)) - event->setPrunable(false); - } - } + + if (isInterestingExpr(DRE, N, &report)) + event->setPrunable(false); + return std::move(event); } -std::shared_ptr ConditionBRVisitor::VisitTrueTest( +PathDiagnosticPieceRef ConditionBRVisitor::VisitTrueTest( const Expr *Cond, const MemberExpr *ME, BugReporterContext &BRC, - BugReport &report, const ExplodedNode *N, bool TookTrue, bool IsAssuming) { + PathSensitiveBugReport &report, const ExplodedNode *N, bool TookTrue, + bool IsAssuming) { SmallString<256> Buf; llvm::raw_svector_ostream Out(Buf); @@ -2391,15 +2591,28 @@ std::shared_ptr ConditionBRVisitor::VisitTrueTest( return nullptr; const LocationContext *LCtx = N->getLocationContext(); - PathDiagnosticLocation Loc(Cond, BRC.getSourceManager(), LCtx); + PathDiagnosticLocation Loc; + + // If we know the value create a pop-up note to the member of the MemberExpr. + if (!IsAssuming && ME->getMemberLoc().isValid()) + Loc = PathDiagnosticLocation(ME->getMemberLoc(), BRC.getSourceManager()); + else + Loc = PathDiagnosticLocation(Cond, BRC.getSourceManager(), LCtx); + if (!Loc.isValid() || !Loc.asLocation().isValid()) return nullptr; + if (isVarAnInterestingCondition(ME, N, &report)) + Out << WillBeUsedForACondition; + // If we know the value create a pop-up note. if (!IsAssuming) return std::make_shared(Loc, Out.str()); - return std::make_shared(Loc, Out.str()); + auto event = std::make_shared(Loc, Out.str()); + if (isInterestingExpr(ME, N, &report)) + event->setPrunable(false); + return event; } bool ConditionBRVisitor::printValue(const Expr *CondVarExpr, raw_ostream &Out, @@ -2439,10 +2652,8 @@ bool ConditionBRVisitor::printValue(const Expr *CondVarExpr, raw_ostream &Out, return true; } -const char *const ConditionBRVisitor::GenericTrueMessage = - "Assuming the condition is true"; -const char *const ConditionBRVisitor::GenericFalseMessage = - "Assuming the condition is false"; +constexpr llvm::StringLiteral ConditionBRVisitor::GenericTrueMessage; +constexpr llvm::StringLiteral ConditionBRVisitor::GenericFalseMessage; bool ConditionBRVisitor::isPieceMessageGeneric( const PathDiagnosticPiece *Piece) { @@ -2455,10 +2666,11 @@ bool ConditionBRVisitor::isPieceMessageGeneric( //===----------------------------------------------------------------------===// void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( - BugReporterContext &BRC, const ExplodedNode *N, BugReport &BR) { + BugReporterContext &BRC, const ExplodedNode *N, + PathSensitiveBugReport &BR) { // Here we suppress false positives coming from system headers. This list is // based on known issues. - AnalyzerOptions &Options = BRC.getAnalyzerOptions(); + const AnalyzerOptions &Options = BRC.getAnalyzerOptions(); const Decl *D = N->getLocationContext()->getDecl(); if (AnalysisDeclContext::isInStdNamespace(D)) { @@ -2525,8 +2737,8 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( // Skip reports within the sys/queue.h macros as we do not have the ability to // reason about data structure shapes. - SourceManager &SM = BRC.getSourceManager(); - FullSourceLoc Loc = BR.getLocation(SM).asLocation(); + const SourceManager &SM = BRC.getSourceManager(); + FullSourceLoc Loc = BR.getLocation().asLocation(); while (Loc.isMacroID()) { Loc = Loc.getSpellingLoc(); if (SM.getFilename(Loc).endswith("sys/queue.h")) { @@ -2540,9 +2752,9 @@ void LikelyFalsePositiveSuppressionBRVisitor::finalizeVisitor( // Implementation of UndefOrNullArgVisitor. //===----------------------------------------------------------------------===// -std::shared_ptr -UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &BRC, BugReport &BR) { +PathDiagnosticPieceRef +UndefOrNullArgVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, + PathSensitiveBugReport &BR) { ProgramStateRef State = N->getState(); ProgramPoint ProgLoc = N->getLocation(); @@ -2598,7 +2810,8 @@ FalsePositiveRefutationBRVisitor::FalsePositiveRefutationBRVisitor() : Constraints(ConstraintRangeTy::Factory().getEmptyMap()) {} void FalsePositiveRefutationBRVisitor::finalizeVisitor( - BugReporterContext &BRC, const ExplodedNode *EndPathNode, BugReport &BR) { + BugReporterContext &BRC, const ExplodedNode *EndPathNode, + PathSensitiveBugReport &BR) { // Collect new constraints VisitNode(EndPathNode, BRC, BR); @@ -2633,10 +2846,8 @@ void FalsePositiveRefutationBRVisitor::finalizeVisitor( BR.markInvalid("Infeasible constraints", EndPathNode->getLocationContext()); } -std::shared_ptr -FalsePositiveRefutationBRVisitor::VisitNode(const ExplodedNode *N, - BugReporterContext &, - BugReport &) { +PathDiagnosticPieceRef FalsePositiveRefutationBRVisitor::VisitNode( + const ExplodedNode *N, BugReporterContext &, PathSensitiveBugReport &) { // Collect new constraints const ConstraintRangeTy &NewCs = N->getState()->get(); ConstraintRangeTy::Factory &CF = @@ -2670,9 +2881,9 @@ void TagVisitor::Profile(llvm::FoldingSetNodeID &ID) const { ID.AddPointer(&Tag); } -std::shared_ptr -TagVisitor::VisitNode(const ExplodedNode *N, BugReporterContext &BRC, - BugReport &R) { +PathDiagnosticPieceRef TagVisitor::VisitNode(const ExplodedNode *N, + BugReporterContext &BRC, + PathSensitiveBugReport &R) { ProgramPoint PP = N->getLocation(); const NoteTag *T = dyn_cast_or_null(PP.getTag()); if (!T) diff --git a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt index 942aedd3889c0..0207470ccf1e1 100644 --- a/clang/lib/StaticAnalyzer/Core/CMakeLists.txt +++ b/clang/lib/StaticAnalyzer/Core/CMakeLists.txt @@ -16,7 +16,7 @@ add_clang_library(clangStaticAnalyzerCore CommonBugCategories.cpp ConstraintManager.cpp CoreEngine.cpp - DynamicTypeMap.cpp + DynamicType.cpp Environment.cpp ExplodedGraph.cpp ExprEngine.cpp @@ -30,7 +30,6 @@ add_clang_library(clangStaticAnalyzerCore LoopUnrolling.cpp LoopWidening.cpp MemRegion.cpp - PathDiagnostic.cpp PlistDiagnostics.cpp ProgramState.cpp RangeConstraintManager.cpp @@ -53,6 +52,7 @@ add_clang_library(clangStaticAnalyzerCore clangAnalysis clangBasic clangCrossTU + clangFrontend clangLex clangRewrite ) diff --git a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp index 11dda7c3acb7c..d95f809bec1aa 100644 --- a/clang/lib/StaticAnalyzer/Core/CallEvent.cpp +++ b/clang/lib/StaticAnalyzer/Core/CallEvent.cpp @@ -27,6 +27,7 @@ #include "clang/Analysis/AnalysisDeclContext.h" #include "clang/Analysis/CFG.h" #include "clang/Analysis/CFGStmtMap.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/CrossTU/CrossTranslationUnit.h" #include "clang/Basic/IdentifierTable.h" @@ -34,10 +35,9 @@ #include "clang/Basic/SourceLocation.h" #include "clang/Basic/SourceManager.h" #include "clang/Basic/Specifiers.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h" #include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeInfo.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState_Fwd.h" @@ -191,7 +191,8 @@ AnalysisDeclContext *CallEvent::getCalleeAnalysisDeclContext() const { return ADC; } -const StackFrameContext *CallEvent::getCalleeStackFrame() const { +const StackFrameContext * +CallEvent::getCalleeStackFrame(unsigned BlockCount) const { AnalysisDeclContext *ADC = getCalleeAnalysisDeclContext(); if (!ADC) return nullptr; @@ -217,11 +218,12 @@ const StackFrameContext *CallEvent::getCalleeStackFrame() const { break; assert(Idx < Sz); - return ADC->getManager()->getStackFrame(ADC, LCtx, E, B, Idx); + return ADC->getManager()->getStackFrame(ADC, LCtx, E, B, BlockCount, Idx); } -const VarRegion *CallEvent::getParameterLocation(unsigned Index) const { - const StackFrameContext *SFC = getCalleeStackFrame(); +const VarRegion *CallEvent::getParameterLocation(unsigned Index, + unsigned BlockCount) const { + const StackFrameContext *SFC = getCalleeStackFrame(BlockCount); // We cannot construct a VarRegion without a stack frame. if (!SFC) return nullptr; @@ -322,7 +324,7 @@ ProgramStateRef CallEvent::invalidateRegions(unsigned BlockCount, if (getKind() != CE_CXXAllocator) if (isArgumentConstructedDirectly(Idx)) if (auto AdjIdx = getAdjustedParameterIndex(Idx)) - if (const VarRegion *VR = getParameterLocation(*AdjIdx)) + if (const VarRegion *VR = getParameterLocation(*AdjIdx, BlockCount)) ValuesToInvalidate.push_back(loc::MemRegionVal(VR)); } @@ -356,20 +358,33 @@ bool CallEvent::isCalled(const CallDescription &CD) const { // FIXME: Add ObjC Message support. if (getKind() == CE_ObjCMessage) return false; + + const IdentifierInfo *II = getCalleeIdentifier(); + if (!II) + return false; + const FunctionDecl *FD = dyn_cast_or_null(getDecl()); + if (!FD) + return false; + + if (CD.Flags & CDF_MaybeBuiltin) { + return CheckerContext::isCLibraryFunction(FD, CD.getFunctionName()) && + (!CD.RequiredArgs || CD.RequiredArgs <= getNumArgs()) && + (!CD.RequiredParams || CD.RequiredParams <= parameters().size()); + } + if (!CD.IsLookupDone) { CD.IsLookupDone = true; CD.II = &getState()->getStateManager().getContext().Idents.get( CD.getFunctionName()); } - const IdentifierInfo *II = getCalleeIdentifier(); - if (!II || II != CD.II) + + if (II != CD.II) return false; - const Decl *D = getDecl(); // If CallDescription provides prefix names, use them to improve matching // accuracy. - if (CD.QualifiedName.size() > 1 && D) { - const DeclContext *Ctx = D->getDeclContext(); + if (CD.QualifiedName.size() > 1 && FD) { + const DeclContext *Ctx = FD->getDeclContext(); // See if we'll be able to match them all. size_t NumUnmatched = CD.QualifiedName.size() - 1; for (; Ctx && isa(Ctx); Ctx = Ctx->getParent()) { @@ -393,8 +408,8 @@ bool CallEvent::isCalled(const CallDescription &CD) const { return false; } - return (CD.RequiredArgs == CallDescription::NoArgRequirement || - CD.RequiredArgs == getNumArgs()); + return (!CD.RequiredArgs || CD.RequiredArgs == getNumArgs()) && + (!CD.RequiredParams || CD.RequiredParams == parameters().size()); } SVal CallEvent::getArgSVal(unsigned Index) const { @@ -504,7 +519,7 @@ static void addParameterValuesToBindings(const StackFrameContext *CalleeCtx, // TODO: Support allocator calls. if (Call.getKind() != CE_CXXAllocator) - if (Call.isArgumentConstructedDirectly(Idx)) + if (Call.isArgumentConstructedDirectly(Call.getASTArgumentIndex(Idx))) continue; // TODO: Allocators should receive the correct size and possibly alignment, @@ -755,8 +770,11 @@ RuntimeDefinition CXXInstanceCall::getRuntimeDefinition() const { // Does the decl that we found have an implementation? const FunctionDecl *Definition; - if (!Result->hasBody(Definition)) + if (!Result->hasBody(Definition)) { + if (!DynType.canBeASubClass()) + return AnyFunctionCall::getRuntimeDefinition(); return {}; + } // We found a definition. If we're not sure that this devirtualization is // actually what will happen at runtime, make sure to provide the region so @@ -1019,7 +1037,7 @@ getSyntacticFromForPseudoObjectExpr(const PseudoObjectExpr *POE) { ObjCMessageKind ObjCMethodCall::getMessageKind() const { if (!Data) { // Find the parent, ignoring implicit casts. - ParentMap &PM = getLocationContext()->getParentMap(); + const ParentMap &PM = getLocationContext()->getParentMap(); const Stmt *S = PM.getParentIgnoreParenCasts(getOriginExpr()); // Check if parent is a PseudoObjectExpr. diff --git a/clang/lib/StaticAnalyzer/Core/Checker.cpp b/clang/lib/StaticAnalyzer/Core/Checker.cpp index f4e6f909d7644..bc1c8964b3ee4 100644 --- a/clang/lib/StaticAnalyzer/Core/Checker.cpp +++ b/clang/lib/StaticAnalyzer/Core/Checker.cpp @@ -19,10 +19,10 @@ using namespace ento; int ImplicitNullDerefEvent::Tag; StringRef CheckerBase::getTagDescription() const { - return getCheckName().getName(); + return getCheckerName().getName(); } -CheckName CheckerBase::getCheckName() const { return Name; } +CheckerNameRef CheckerBase::getCheckerName() const { return Name; } CheckerProgramPointTag::CheckerProgramPointTag(StringRef CheckerName, StringRef Msg) @@ -30,10 +30,10 @@ CheckerProgramPointTag::CheckerProgramPointTag(StringRef CheckerName, CheckerProgramPointTag::CheckerProgramPointTag(const CheckerBase *Checker, StringRef Msg) - : SimpleProgramPointTag(Checker->getCheckName().getName(), Msg) {} + : SimpleProgramPointTag(Checker->getCheckerName().getName(), Msg) {} raw_ostream& clang::ento::operator<<(raw_ostream &Out, const CheckerBase &Checker) { - Out << Checker.getCheckName().getName(); + Out << Checker.getCheckerName().getName(); return Out; } diff --git a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp index 34cdc9db699db..11693132de687 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerHelpers.cpp @@ -91,7 +91,7 @@ parseAssignment(const Stmt *S) { } else if (auto PD = dyn_cast_or_null(S)) { // Initialization assert(PD->isSingleDecl() && "We process decls one by one"); - VD = dyn_cast_or_null(PD->getSingleDecl()); + VD = cast(PD->getSingleDecl()); RHS = VD->getAnyInitializer(); } diff --git a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp index cda9fe9bf5c8f..f676bd8952836 100644 --- a/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp +++ b/clang/lib/StaticAnalyzer/Core/CheckerManager.cpp @@ -651,7 +651,6 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, const ExplodedNodeSet &Src, const CallEvent &Call, ExprEngine &Eng) { - const CallExpr *CE = cast(Call.getOriginExpr()); for (const auto Pred : Src) { bool anyEvaluated = false; @@ -660,16 +659,19 @@ void CheckerManager::runCheckersForEvalCall(ExplodedNodeSet &Dst, // Check if any of the EvalCall callbacks can evaluate the call. for (const auto EvalCallChecker : EvalCallCheckers) { - ProgramPoint::Kind K = ProgramPoint::PostStmtKind; - const ProgramPoint &L = - ProgramPoint::getProgramPoint(CE, K, Pred->getLocationContext(), - EvalCallChecker.Checker); + // TODO: Support the situation when the call doesn't correspond + // to any Expr. + ProgramPoint L = ProgramPoint::getProgramPoint( + cast(Call.getOriginExpr()), + ProgramPoint::PostStmtKind, + Pred->getLocationContext(), + EvalCallChecker.Checker); bool evaluated = false; { // CheckerContext generates transitions(populates checkDest) on // destruction, so introduce the scope to make sure it gets properly // populated. CheckerContext C(B, Eng, Pred, L); - evaluated = EvalCallChecker(CE, C); + evaluated = EvalCallChecker(Call, C); } assert(!(evaluated && anyEvaluated) && "There are more than one checkers evaluating the call"); @@ -746,7 +748,7 @@ void CheckerManager::runCheckersForPrintStateJson(raw_ostream &Out, continue; Indent(Out, Space, IsDot) - << "{ \"checker\": \"" << CT.second->getCheckName().getName() + << "{ \"checker\": \"" << CT.second->getCheckerName().getName() << "\", \"messages\": [" << NL; Indent(Out, InnerSpace, IsDot) << '\"' << TempBuf.str().trim() << '\"' << NL; diff --git a/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp b/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp index 54501314386a9..bdae3e605efff 100644 --- a/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp +++ b/clang/lib/StaticAnalyzer/Core/CommonBugCategories.cpp @@ -17,4 +17,5 @@ const char * const MemoryRefCount = "Memory (Core Foundation/Objective-C/OSObject)"; const char * const MemoryError = "Memory error"; const char * const UnixAPI = "Unix API"; +const char * const CXXObjectLifecycle = "C++ object lifecycle"; }}} diff --git a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp index 431d07dab1e18..94cf74de82931 100644 --- a/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/CoreEngine.cpp @@ -396,6 +396,11 @@ void CoreEngine::HandleBlockExit(const CFGBlock * B, ExplodedNode *Pred) { case Stmt::WhileStmtClass: HandleBranch(cast(Term)->getCond(), Term, B, Pred); return; + + case Stmt::GCCAsmStmtClass: + assert(cast(Term)->isAsmGoto() && "Encountered GCCAsmStmt without labels"); + // TODO: Handle jumping to labels + return; } } diff --git a/clang/lib/StaticAnalyzer/Core/DynamicType.cpp b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp new file mode 100644 index 0000000000000..a78e0e05e903b --- /dev/null +++ b/clang/lib/StaticAnalyzer/Core/DynamicType.cpp @@ -0,0 +1,229 @@ +//===- DynamicType.cpp - Dynamic type related APIs --------------*- 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 +// +//===----------------------------------------------------------------------===// +// +// This file defines APIs that track and query dynamic type information. This +// information can be used to devirtualize calls during the symbolic execution +// or do type checking. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" +#include "clang/Basic/JsonSupport.h" +#include "clang/Basic/LLVM.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" +#include "llvm/Support/Casting.h" +#include "llvm/Support/raw_ostream.h" +#include + +/// The GDM component containing the dynamic type info. This is a map from a +/// symbol to its most likely type. +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicTypeMap, const clang::ento::MemRegion *, + clang::ento::DynamicTypeInfo) + +/// A set factory of dynamic cast informations. +REGISTER_SET_FACTORY_WITH_PROGRAMSTATE(CastSet, clang::ento::DynamicCastInfo) + +/// A map from symbols to cast informations. +REGISTER_MAP_WITH_PROGRAMSTATE(DynamicCastMap, const clang::ento::MemRegion *, + CastSet) + +namespace clang { +namespace ento { + +DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR) { + MR = MR->StripCasts(); + + // Look up the dynamic type in the GDM. + if (const DynamicTypeInfo *DTI = State->get(MR)) + return *DTI; + + // Otherwise, fall back to what we know about the region. + if (const auto *TR = dyn_cast(MR)) + return DynamicTypeInfo(TR->getLocationType(), /*CanBeSub=*/false); + + if (const auto *SR = dyn_cast(MR)) { + SymbolRef Sym = SR->getSymbol(); + return DynamicTypeInfo(Sym->getType()); + } + + return {}; +} + +const DynamicTypeInfo *getRawDynamicTypeInfo(ProgramStateRef State, + const MemRegion *MR) { + return State->get(MR); +} + +const DynamicCastInfo *getDynamicCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy) { + const auto *Lookup = State->get().lookup(MR); + if (!Lookup) + return nullptr; + + for (const DynamicCastInfo &Cast : *Lookup) + if (Cast.equals(CastFromTy, CastToTy)) + return &Cast; + + return nullptr; +} + +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + DynamicTypeInfo NewTy) { + State = State->set(MR->StripCasts(), NewTy); + assert(State); + return State; +} + +ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *MR, + QualType NewTy, bool CanBeSubClassed) { + return setDynamicTypeInfo(State, MR, DynamicTypeInfo(NewTy, CanBeSubClassed)); +} + +ProgramStateRef setDynamicTypeAndCastInfo(ProgramStateRef State, + const MemRegion *MR, + QualType CastFromTy, + QualType CastToTy, + bool CastSucceeds) { + if (!MR) + return State; + + if (CastSucceeds) { + assert((CastToTy->isAnyPointerType() || CastToTy->isReferenceType()) && + "DynamicTypeInfo should always be a pointer."); + State = State->set(MR, CastToTy); + } + + DynamicCastInfo::CastResult ResultKind = + CastSucceeds ? DynamicCastInfo::CastResult::Success + : DynamicCastInfo::CastResult::Failure; + + CastSet::Factory &F = State->get_context(); + + const CastSet *TempSet = State->get(MR); + CastSet Set = TempSet ? *TempSet : F.getEmptySet(); + + Set = F.add(Set, {CastFromTy, CastToTy, ResultKind}); + State = State->set(MR, Set); + + assert(State); + return State; +} + +template +ProgramStateRef removeDead(ProgramStateRef State, const MapTy &Map, + SymbolReaper &SR) { + for (const auto &Elem : Map) + if (!SR.isLiveRegion(Elem.first)) + State = State->remove(Elem.first); + + return State; +} + +ProgramStateRef removeDeadTypes(ProgramStateRef State, SymbolReaper &SR) { + return removeDead(State, State->get(), SR); +} + +ProgramStateRef removeDeadCasts(ProgramStateRef State, SymbolReaper &SR) { + return removeDead(State, State->get(), SR); +} + +static void printDynamicTypesJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + Indent(Out, Space, IsDot) << "\"dynamic_types\": "; + + const DynamicTypeMapTy &Map = State->get(); + if (Map.isEmpty()) { + Out << "null," << NL; + return; + } + + ++Space; + Out << '[' << NL; + for (DynamicTypeMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { + const MemRegion *MR = I->first; + const DynamicTypeInfo &DTI = I->second; + Indent(Out, Space, IsDot) + << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; + if (!DTI.isValid()) { + Out << "null"; + } else { + Out << '\"' << DTI.getType()->getPointeeType().getAsString() + << "\", \"sub_classable\": " + << (DTI.canBeASubClass() ? "true" : "false"); + } + Out << " }"; + + if (std::next(I) != Map.end()) + Out << ','; + Out << NL; + } + + --Space; + Indent(Out, Space, IsDot) << "]," << NL; +} + +static void printDynamicCastsJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, + bool IsDot) { + Indent(Out, Space, IsDot) << "\"dynamic_casts\": "; + + const DynamicCastMapTy &Map = State->get(); + if (Map.isEmpty()) { + Out << "null," << NL; + return; + } + + ++Space; + Out << '[' << NL; + for (DynamicCastMapTy::iterator I = Map.begin(); I != Map.end(); ++I) { + const MemRegion *MR = I->first; + const CastSet &Set = I->second; + + Indent(Out, Space, IsDot) << "{ \"region\": \"" << MR << "\", \"casts\": "; + if (Set.isEmpty()) { + Out << "null "; + } else { + ++Space; + Out << '[' << NL; + for (CastSet::iterator SI = Set.begin(); SI != Set.end(); ++SI) { + Indent(Out, Space, IsDot) + << "{ \"from\": \"" << SI->from().getAsString() << "\", \"to\": \"" + << SI->to().getAsString() << "\", \"kind\": \"" + << (SI->succeeds() ? "success" : "fail") << "\" }"; + + if (std::next(SI) != Set.end()) + Out << ','; + Out << NL; + } + --Space; + Indent(Out, Space, IsDot) << ']'; + } + Out << '}'; + + if (std::next(I) != Map.end()) + Out << ','; + Out << NL; + } + + --Space; + Indent(Out, Space, IsDot) << "]," << NL; +} + +void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, + const char *NL, unsigned int Space, bool IsDot) { + printDynamicTypesJson(Out, State, NL, Space, IsDot); + printDynamicCastsJson(Out, State, NL, Space, IsDot); +} + +} // namespace ento +} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp b/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp deleted file mode 100644 index 22c4cc4a624a7..0000000000000 --- a/clang/lib/StaticAnalyzer/Core/DynamicTypeMap.cpp +++ /dev/null @@ -1,97 +0,0 @@ -//===- DynamicTypeMap.cpp - Dynamic Type Info related APIs ----------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// This file defines APIs that track and query dynamic type information. This -// information can be used to devirtualize calls during the symbolic execution -// or do type checking. -// -//===----------------------------------------------------------------------===// - -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" -#include "clang/Basic/JsonSupport.h" -#include "clang/Basic/LLVM.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/ProgramState.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/SymExpr.h" -#include "llvm/Support/Casting.h" -#include "llvm/Support/raw_ostream.h" -#include - -namespace clang { -namespace ento { - -DynamicTypeInfo getDynamicTypeInfo(ProgramStateRef State, - const MemRegion *Reg) { - Reg = Reg->StripCasts(); - - // Look up the dynamic type in the GDM. - const DynamicTypeInfo *GDMType = State->get(Reg); - if (GDMType) - return *GDMType; - - // Otherwise, fall back to what we know about the region. - if (const auto *TR = dyn_cast(Reg)) - return DynamicTypeInfo(TR->getLocationType(), /*CanBeSubclass=*/false); - - if (const auto *SR = dyn_cast(Reg)) { - SymbolRef Sym = SR->getSymbol(); - return DynamicTypeInfo(Sym->getType()); - } - - return {}; -} - -ProgramStateRef setDynamicTypeInfo(ProgramStateRef State, const MemRegion *Reg, - DynamicTypeInfo NewTy) { - Reg = Reg->StripCasts(); - ProgramStateRef NewState = State->set(Reg, NewTy); - assert(NewState); - return NewState; -} - -void printDynamicTypeInfoJson(raw_ostream &Out, ProgramStateRef State, - const char *NL, unsigned int Space, bool IsDot) { - Indent(Out, Space, IsDot) << "\"dynamic_types\": "; - - const DynamicTypeMapTy &DTM = State->get(); - if (DTM.isEmpty()) { - Out << "null," << NL; - return; - } - - ++Space; - Out << '[' << NL; - for (DynamicTypeMapTy::iterator I = DTM.begin(); I != DTM.end(); ++I) { - const MemRegion *MR = I->first; - const DynamicTypeInfo &DTI = I->second; - Out << "{ \"region\": \"" << MR << "\", \"dyn_type\": "; - if (DTI.isValid()) { - Out << '\"' << DTI.getType()->getPointeeType().getAsString() - << "\" \"sub_classable\": " - << (DTI.canBeASubClass() ? "true" : "false"); - } else { - Out << "null"; // Invalid type info - } - Out << "\" }"; - - if (std::next(I) != DTM.end()) - Out << ','; - Out << NL; - } - - --Space; - Indent(Out, Space, IsDot) << "]," << NL; -} - -void *ProgramStateTrait::GDMIndex() { - static int index = 0; - return &index; -} - -} // namespace ento -} // namespace clang diff --git a/clang/lib/StaticAnalyzer/Core/Environment.cpp b/clang/lib/StaticAnalyzer/Core/Environment.cpp index 94cc4d6dbb2fd..1ccf4c6104a65 100644 --- a/clang/lib/StaticAnalyzer/Core/Environment.cpp +++ b/clang/lib/StaticAnalyzer/Core/Environment.cpp @@ -108,6 +108,7 @@ SVal Environment::getSVal(const EnvironmentEntry &Entry, case Stmt::ObjCStringLiteralClass: case Stmt::StringLiteralClass: case Stmt::TypeTraitExprClass: + case Stmt::SizeOfPackExprClass: // Known constants; defer to SValBuilder. return svalBuilder.getConstantVal(cast(S)).getValue(); @@ -204,13 +205,13 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, const LocationContext *LCtx, const char *NL, unsigned int Space, bool IsDot) const { Indent(Out, Space, IsDot) << "\"environment\": "; - ++Space; if (ExprBindings.isEmpty()) { Out << "null," << NL; return; } + ++Space; if (!LCtx) { // Find the freshest location context. llvm::SmallPtrSet FoundContexts; @@ -227,7 +228,8 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, assert(LCtx); - Out << '[' << NL; // Start of Environment. + Out << "{ \"pointer\": \"" << (const void *)LCtx->getStackFrame() + << "\", \"items\": [" << NL; PrintingPolicy PP = Ctx.getPrintingPolicy(); LCtx->printJson(Out, NL, Space, IsDot, [&](const LocationContext *LC) { @@ -261,8 +263,7 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, const Stmt *S = I->first.getStmt(); Indent(Out, InnerSpace, IsDot) - << "{ \"lctx_id\": " << LC->getID() - << ", \"stmt_id\": " << S->getID(Ctx) << ", \"pretty\": "; + << "{ \"stmt_id\": " << S->getID(Ctx) << ", \"pretty\": "; S->printJson(Out, nullptr, PP, /*AddQuotes=*/true); Out << ", \"value\": "; @@ -281,5 +282,5 @@ void Environment::printJson(raw_ostream &Out, const ASTContext &Ctx, Out << "null "; }); - Indent(Out, --Space, IsDot) << "]," << NL; // End of Environment. + Indent(Out, --Space, IsDot) << "]}," << NL; } diff --git a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp index c86b1436baabb..c4838492271cd 100644 --- a/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExplodedGraph.cpp @@ -16,6 +16,7 @@ #include "clang/AST/ExprObjC.h" #include "clang/AST/ParentMap.h" #include "clang/AST/Stmt.h" +#include "clang/Analysis/CFGStmtMap.h" #include "clang/Analysis/ProgramPoint.h" #include "clang/Analysis/Support/BumpVector.h" #include "clang/Basic/LLVM.h" @@ -134,7 +135,7 @@ bool ExplodedGraph::shouldCollect(const ExplodedNode *node) { // Do not collect nodes for non-consumed Stmt or Expr to ensure precise // diagnostic generation; specifically, so that we could anchor arrows // pointing to the beginning of statements (as written in code). - ParentMap &PM = progPoint.getLocationContext()->getParentMap(); + const ParentMap &PM = progPoint.getLocationContext()->getParentMap(); if (!PM.isConsumedExpr(Ex)) return false; @@ -282,16 +283,115 @@ ExplodedNode * const *ExplodedNode::NodeGroup::end() const { return Storage.getAddrOfPtr1() + 1; } -int64_t ExplodedNode::getID(ExplodedGraph *G) const { - return G->getAllocator().identifyKnownAlignedObject(this); -} - bool ExplodedNode::isTrivial() const { return pred_size() == 1 && succ_size() == 1 && getFirstPred()->getState()->getID() == getState()->getID() && getFirstPred()->succ_size() == 1; } +const CFGBlock *ExplodedNode::getCFGBlock() const { + ProgramPoint P = getLocation(); + if (auto BEP = P.getAs()) + return BEP->getBlock(); + + // Find the node's current statement in the CFG. + // FIXME: getStmtForDiagnostics() does nasty things in order to provide + // a valid statement for body farms, do we need this behavior here? + if (const Stmt *S = getStmtForDiagnostics()) + return getLocationContext() + ->getAnalysisDeclContext() + ->getCFGStmtMap() + ->getBlock(S); + + return nullptr; +} + +static const LocationContext * +findTopAutosynthesizedParentContext(const LocationContext *LC) { + assert(LC->getAnalysisDeclContext()->isBodyAutosynthesized()); + const LocationContext *ParentLC = LC->getParent(); + assert(ParentLC && "We don't start analysis from autosynthesized code"); + while (ParentLC->getAnalysisDeclContext()->isBodyAutosynthesized()) { + LC = ParentLC; + ParentLC = LC->getParent(); + assert(ParentLC && "We don't start analysis from autosynthesized code"); + } + return LC; +} + +const Stmt *ExplodedNode::getStmtForDiagnostics() const { + // We cannot place diagnostics on autosynthesized code. + // Put them onto the call site through which we jumped into autosynthesized + // code for the first time. + const LocationContext *LC = getLocationContext(); + if (LC->getAnalysisDeclContext()->isBodyAutosynthesized()) { + // It must be a stack frame because we only autosynthesize functions. + return cast(findTopAutosynthesizedParentContext(LC)) + ->getCallSite(); + } + // Otherwise, see if the node's program point directly points to a statement. + // FIXME: Refactor into a ProgramPoint method? + ProgramPoint P = getLocation(); + if (auto SP = P.getAs()) + return SP->getStmt(); + if (auto BE = P.getAs()) + return BE->getSrc()->getTerminatorStmt(); + if (auto CE = P.getAs()) + return CE->getCallExpr(); + if (auto CEE = P.getAs()) + return CEE->getCalleeContext()->getCallSite(); + if (auto PIPP = P.getAs()) + return PIPP->getInitializer()->getInit(); + if (auto CEB = P.getAs()) + return CEB->getReturnStmt(); + if (auto FEP = P.getAs()) + return FEP->getStmt(); + + return nullptr; +} + +const Stmt *ExplodedNode::getNextStmtForDiagnostics() const { + for (const ExplodedNode *N = getFirstSucc(); N; N = N->getFirstSucc()) { + if (const Stmt *S = N->getStmtForDiagnostics()) { + // Check if the statement is '?' or '&&'/'||'. These are "merges", + // not actual statement points. + switch (S->getStmtClass()) { + case Stmt::ChooseExprClass: + case Stmt::BinaryConditionalOperatorClass: + case Stmt::ConditionalOperatorClass: + continue; + case Stmt::BinaryOperatorClass: { + BinaryOperatorKind Op = cast(S)->getOpcode(); + if (Op == BO_LAnd || Op == BO_LOr) + continue; + break; + } + default: + break; + } + // We found the statement, so return it. + return S; + } + } + + return nullptr; +} + +const Stmt *ExplodedNode::getPreviousStmtForDiagnostics() const { + for (const ExplodedNode *N = getFirstPred(); N; N = N->getFirstPred()) + if (const Stmt *S = N->getStmtForDiagnostics()) + return S; + + return nullptr; +} + +const Stmt *ExplodedNode::getCurrentOrPreviousStmtForDiagnostics() const { + if (const Stmt *S = getStmtForDiagnostics()) + return S; + + return getPreviousStmtForDiagnostics(); +} + ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, ProgramStateRef State, bool IsSink, @@ -313,14 +413,14 @@ ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, V = (NodeTy*) getAllocator().Allocate(); } - new (V) NodeTy(L, State, IsSink); + ++NumNodes; + new (V) NodeTy(L, State, NumNodes, IsSink); if (ReclaimNodeInterval) ChangedNodes.push_back(V); // Insert the node into the node set and return it. Nodes.InsertNode(V, InsertPos); - ++NumNodes; if (IsNew) *IsNew = true; } @@ -332,9 +432,10 @@ ExplodedNode *ExplodedGraph::getNode(const ProgramPoint &L, ExplodedNode *ExplodedGraph::createUncachedNode(const ProgramPoint &L, ProgramStateRef State, + int64_t Id, bool IsSink) { NodeTy *V = (NodeTy *) getAllocator().Allocate(); - new (V) NodeTy(L, State, IsSink); + new (V) NodeTy(L, State, Id, IsSink); return V; } @@ -394,7 +495,8 @@ ExplodedGraph::trim(ArrayRef Sinks, // Create the corresponding node in the new graph and record the mapping // from the old node to the new node. - ExplodedNode *NewN = G->createUncachedNode(N->getLocation(), N->State, N->isSink()); + ExplodedNode *NewN = G->createUncachedNode(N->getLocation(), N->State, + N->getID(), N->isSink()); Pass2[N] = NewN; // Also record the reverse mapping from the new node to the old node. diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp index b217889390414..8f1b8e3ed65da 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngine.cpp @@ -149,9 +149,6 @@ class ConstructedObjectKey { if (!S) I = getItem().getCXXCtorInitializer(); - // IDs - Out << "\"lctx_id\": " << getLocationContext()->getID() << ", "; - if (S) Out << "\"stmt_id\": " << S->getID(getASTContext()); else @@ -228,10 +225,6 @@ ExprEngine::ExprEngine(cross_tu::CrossTranslationUnitContext &CTU, } } -ExprEngine::~ExprEngine() { - BR.FlushReports(); -} - //===----------------------------------------------------------------------===// // Utility methods. //===----------------------------------------------------------------------===// @@ -984,8 +977,8 @@ void ExprEngine::ProcessAutomaticObjDtor(const CFGAutomaticObjDtor Dtor, Region = makeZeroElementRegion(state, loc::MemRegionVal(Region), varType, CallOpts.IsArrayCtorOrDtor).getAsRegion(); - VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), /*IsBase=*/ false, - Pred, Dst, CallOpts); + VisitCXXDestructor(varType, Region, Dtor.getTriggerStmt(), + /*IsBase=*/false, Pred, Dst, CallOpts); } void ExprEngine::ProcessDeleteDtor(const CFGDeleteDtor Dtor, @@ -1043,8 +1036,9 @@ void ExprEngine::ProcessBaseDtor(const CFGBaseDtor D, SVal BaseVal = getStoreManager().evalDerivedToBase(ThisVal, BaseTy, Base->isVirtual()); - VisitCXXDestructor(BaseTy, BaseVal.castAs().getRegion(), - CurDtor->getBody(), /*IsBase=*/ true, Pred, Dst, {}); + EvalCallOptions CallOpts; + VisitCXXDestructor(BaseTy, BaseVal.getAsRegion(), CurDtor->getBody(), + /*IsBase=*/true, Pred, Dst, CallOpts); } void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, @@ -1055,10 +1049,10 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, const LocationContext *LCtx = Pred->getLocationContext(); const auto *CurDtor = cast(LCtx->getDecl()); - Loc ThisVal = getSValBuilder().getCXXThis(CurDtor, - LCtx->getStackFrame()); - SVal FieldVal = - State->getLValue(Member, State->getSVal(ThisVal).castAs()); + Loc ThisStorageLoc = + getSValBuilder().getCXXThis(CurDtor, LCtx->getStackFrame()); + Loc ThisLoc = State->getSVal(ThisStorageLoc).castAs(); + SVal FieldVal = State->getLValue(Member, ThisLoc); // FIXME: We need to run the same destructor on every element of the array. // This workaround will just run the first destructor (which will still @@ -1067,8 +1061,8 @@ void ExprEngine::ProcessMemberDtor(const CFGMemberDtor D, FieldVal = makeZeroElementRegion(State, FieldVal, T, CallOpts.IsArrayCtorOrDtor); - VisitCXXDestructor(T, FieldVal.castAs().getRegion(), - CurDtor->getBody(), /*IsBase=*/false, Pred, Dst, CallOpts); + VisitCXXDestructor(T, FieldVal.getAsRegion(), CurDtor->getBody(), + /*IsBase=*/false, Pred, Dst, CallOpts); } void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, @@ -1116,8 +1110,6 @@ void ExprEngine::ProcessTemporaryDtor(const CFGTemporaryDtor D, EvalCallOptions CallOpts; CallOpts.IsTemporaryCtorOrDtor = true; if (!MR) { - CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; - // If we have no MR, we still need to unwrap the array to avoid destroying // the whole array at once. Regardless, we'd eventually need to model array // destructors properly, element-by-element. @@ -3011,8 +3003,13 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { llvm::make_range(BR.EQClasses_begin(), BR.EQClasses_end()); for (const auto &EQ : EQClasses) { - for (const BugReport &Report : EQ) { - if (Report.getErrorNode() == N) + for (const auto &I : EQ.getReports()) { + const auto *PR = dyn_cast(I.get()); + if (!PR) + continue; + const ExplodedNode *EN = PR->getErrorNode(); + if (EN->getState() == N->getState() && + EN->getLocation() == N->getLocation()) return true; } } @@ -3028,41 +3025,20 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { llvm::function_ref PreCallback, llvm::function_ref PostCallback, llvm::function_ref Stop) { - const ExplodedNode *FirstHiddenNode = N; - while (FirstHiddenNode->pred_size() == 1 && - isNodeHidden(*FirstHiddenNode->pred_begin())) { - FirstHiddenNode = *FirstHiddenNode->pred_begin(); - } - const ExplodedNode *OtherNode = FirstHiddenNode; while (true) { - PreCallback(OtherNode); - if (Stop(OtherNode)) + PreCallback(N); + if (Stop(N)) return true; - if (OtherNode == N) + if (N->succ_size() != 1 || !isNodeHidden(N->getFirstSucc())) break; - PostCallback(OtherNode); + PostCallback(N); - OtherNode = *OtherNode->succ_begin(); + N = N->getFirstSucc(); } return false; } - static std::string getNodeAttributes(const ExplodedNode *N, - ExplodedGraph *) { - SmallVector Out; - auto Noop = [](const ExplodedNode*){}; - if (traverseHiddenNodes(N, Noop, Noop, &nodeHasBugReport)) { - Out.push_back("style=filled"); - Out.push_back("fillcolor=red"); - } - - if (traverseHiddenNodes(N, Noop, Noop, - [](const ExplodedNode *C) { return C->isSink(); })) - Out.push_back("color=blue"); - return llvm::join(Out, ","); - } - static bool isNodeHidden(const ExplodedNode *N) { return N->isTrivial(); } @@ -3075,9 +3051,7 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { const unsigned int Space = 1; ProgramStateRef State = N->getState(); - Out << "{ \"node_id\": " << N->getID(G) << ", \"pointer\": \"" - << (const void *)N << "\", \"state_id\": " << State->getID() - << ", \"has_report\": " << (nodeHasBugReport(N) ? "true" : "false") + Out << "{ \"state_id\": " << State->getID() << ",\\l"; Indent(Out, Space, IsDot) << "\"program_points\": [\\l"; @@ -3090,9 +3064,12 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { OtherNode->getLocation().printJson(Out, /*NL=*/"\\l"); Out << ", \"tag\": "; if (const ProgramPointTag *Tag = OtherNode->getLocation().getTag()) - Out << '\"' << Tag->getTagDescription() << "\" }"; + Out << '\"' << Tag->getTagDescription() << "\""; else - Out << "null }"; + Out << "null"; + Out << ", \"node_id\": " << OtherNode->getID() << + ", \"is_sink\": " << OtherNode->isSink() << + ", \"has_report\": " << nodeHasBugReport(OtherNode) << " }"; }, // Adds a comma and a new-line between each program point. [&](const ExplodedNode *) { Out << ",\\l"; }, @@ -3101,22 +3078,9 @@ struct DOTGraphTraits : public DefaultDOTGraphTraits { Out << "\\l"; // Adds a new-line to the last program point. Indent(Out, Space, IsDot) << "],\\l"; - bool SameAsAllPredecessors = - std::all_of(N->pred_begin(), N->pred_end(), [&](const ExplodedNode *P) { - return P->getState() == State; - }); - - if (!SameAsAllPredecessors) { - State->printDOT(Out, N->getLocationContext(), Space); - } else { - Indent(Out, Space, IsDot) << "\"program_state\": null"; - } - - Out << "\\l}"; - if (!N->succ_empty()) - Out << ','; - Out << "\\l"; + State->printDOT(Out, N->getLocationContext(), Space); + Out << "\\l}\\l"; return Out.str(); } }; @@ -3149,8 +3113,12 @@ std::string ExprEngine::DumpGraph(bool trim, StringRef Filename) { // Iterate through the reports and get their nodes. for (BugReporter::EQClasses_iterator EI = BR.EQClasses_begin(), EE = BR.EQClasses_end(); EI != EE; ++EI) { - const auto *N = const_cast(EI->begin()->getErrorNode()); - if (N) Src.push_back(N); + const auto *R = + dyn_cast(EI->getReports()[0].get()); + if (!R) + continue; + const auto *N = const_cast(R->getErrorNode()); + Src.push_back(N); } return DumpGraph(Src, Filename); } else { diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp index cc62cf1048c03..ab747230594ac 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp @@ -100,6 +100,10 @@ void ExprEngine::VisitBinaryOperator(const BinaryOperator* B, SVal Result = evalBinOp(state, Op, LeftV, RightV, B->getType()); if (!Result.isUnknown()) { state = state->BindExpr(B, LCtx, Result); + } else { + // If we cannot evaluate the operation escape the operands. + state = escapeValue(state, LeftV, PSK_EscapeOther); + state = escapeValue(state, RightV, PSK_EscapeOther); } Bldr.generateNode(B, *it, state); @@ -845,8 +849,7 @@ VisitOffsetOfExpr(const OffsetOfExpr *OOE, if (OOE->EvaluateAsInt(Result, getContext())) { APSInt IV = Result.Val.getInt(); assert(IV.getBitWidth() == getContext().getTypeSize(OOE->getType())); - assert(OOE->getType()->isBuiltinType()); - assert(OOE->getType()->getAs()->isInteger()); + assert(OOE->getType()->castAs()->isInteger()); assert(IV.isSigned() == OOE->getType()->isSignedIntegerType()); SVal X = svalBuilder.makeIntVal(IV); B.generateNode(OOE, Pred, diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 1cbd09ea57932..058be985540d3 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -323,7 +323,8 @@ std::pair ExprEngine::prepareForObjectConstruction( CallEventManager &CEMgr = getStateManager().getCallEventManager(); SVal V = UnknownVal(); auto getArgLoc = [&](CallEventRef<> Caller) -> Optional { - const LocationContext *FutureSFC = Caller->getCalleeStackFrame(); + const LocationContext *FutureSFC = + Caller->getCalleeStackFrame(currBldrCtx->blockCount()); // Return early if we are unable to reliably foresee // the future stack frame. if (!FutureSFC) @@ -342,7 +343,7 @@ std::pair ExprEngine::prepareForObjectConstruction( // because this-argument is implemented as a normal argument in // operator call expressions but not in operator declarations. const VarRegion *VR = Caller->getParameterLocation( - *Caller->getAdjustedParameterIndex(Idx)); + *Caller->getAdjustedParameterIndex(Idx), currBldrCtx->blockCount()); if (!VR) return None; @@ -603,7 +604,7 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, bool IsBaseDtor, ExplodedNode *Pred, ExplodedNodeSet &Dst, - const EvalCallOptions &CallOpts) { + EvalCallOptions &CallOpts) { assert(S && "A destructor without a trigger!"); const LocationContext *LCtx = Pred->getLocationContext(); ProgramStateRef State = Pred->getState(); @@ -611,7 +612,6 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, const CXXRecordDecl *RecordDecl = ObjectType->getAsCXXRecordDecl(); assert(RecordDecl && "Only CXXRecordDecls should have destructors"); const CXXDestructorDecl *DtorDecl = RecordDecl->getDestructor(); - // FIXME: There should always be a Decl, otherwise the destructor call // shouldn't have been added to the CFG in the first place. if (!DtorDecl) { @@ -625,9 +625,27 @@ void ExprEngine::VisitCXXDestructor(QualType ObjectType, return; } + if (!Dest) { + // We're trying to destroy something that is not a region. This may happen + // for a variety of reasons (unknown target region, concrete integer instead + // of target region, etc.). The current code makes an attempt to recover. + // FIXME: We probably don't really need to recover when we're dealing + // with concrete integers specifically. + CallOpts.IsCtorOrDtorWithImproperlyModeledTargetRegion = true; + if (const Expr *E = dyn_cast_or_null(S)) { + Dest = MRMgr.getCXXTempObjectRegion(E, Pred->getLocationContext()); + } else { + static SimpleProgramPointTag T("ExprEngine", "SkipInvalidDestructor"); + NodeBuilder Bldr(Pred, Dst, *currBldrCtx); + Bldr.generateSink(Pred->getLocation().withTag(&T), + Pred->getState(), Pred); + return; + } + } + CallEventManager &CEMgr = getStateManager().getCallEventManager(); CallEventRef Call = - CEMgr.getCXXDestructorCall(DtorDecl, S, Dest, IsBaseDtor, State, LCtx); + CEMgr.getCXXDestructorCall(DtorDecl, S, Dest, IsBaseDtor, State, LCtx); PrettyStackTraceLoc CrashInfo(getContext().getSourceManager(), Call->getSourceRange().getBegin(), @@ -757,7 +775,8 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, if (!AMgr.getAnalyzerOptions().MayInlineCXXAllocator) { // Invalidate placement args. // FIXME: Once we figure out how we want allocators to work, - // we should be using the usual pre-/(default-)eval-/post-call checks here. + // we should be using the usual pre-/(default-)eval-/post-call checkers + // here. State = Call->invalidateRegions(blockCount); if (!State) return; @@ -785,9 +804,8 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, if (CNE->isArray()) { // FIXME: allocating an array requires simulating the constructors. // For now, just return a symbolicated region. - if (const SubRegion *NewReg = - dyn_cast_or_null(symVal.getAsRegion())) { - QualType ObjTy = CNE->getType()->getAs()->getPointeeType(); + if (const auto *NewReg = cast_or_null(symVal.getAsRegion())) { + QualType ObjTy = CNE->getType()->getPointeeType(); const ElementRegion *EleReg = getStoreManager().GetElementZeroRegion(NewReg, ObjTy); Result = loc::MemRegionVal(EleReg); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp index 3fe06aea63e8e..3bef471751d15 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCallAndReturn.cpp @@ -451,9 +451,8 @@ bool ExprEngine::inlineCall(const CallEvent &Call, const Decl *D, // Construct a new stack frame for the callee. AnalysisDeclContext *CalleeADC = AMgr.getAnalysisDeclContext(D); const StackFrameContext *CalleeSFC = - CalleeADC->getStackFrame(ParentOfCallee, CallE, - currBldrCtx->getBlock(), - currStmtIdx); + CalleeADC->getStackFrame(ParentOfCallee, CallE, currBldrCtx->getBlock(), + currBldrCtx->blockCount(), currStmtIdx); CallEnter Loc(CallE, CalleeSFC, CurLC); @@ -634,12 +633,19 @@ ProgramStateRef ExprEngine::bindReturnValue(const CallEvent &Call, std::tie(State, Target) = prepareForObjectConstruction(Call.getOriginExpr(), State, LCtx, RTC->getConstructionContext(), CallOpts); - assert(Target.getAsRegion()); - // Invalidate the region so that it didn't look uninitialized. Don't notify - // the checkers. - State = State->invalidateRegions(Target.getAsRegion(), E, Count, LCtx, + const MemRegion *TargetR = Target.getAsRegion(); + assert(TargetR); + // Invalidate the region so that it didn't look uninitialized. If this is + // a field or element constructor, we do not want to invalidate + // the whole structure. Pointer escape is meaningless because + // the structure is a product of conservative evaluation + // and therefore contains nothing interesting at this point. + RegionAndSymbolInvalidationTraits ITraits; + ITraits.setTrait(TargetR, + RegionAndSymbolInvalidationTraits::TK_DoNotInvalidateSuperRegion); + State = State->invalidateRegions(TargetR, E, Count, LCtx, /* CausedByPointerEscape=*/false, nullptr, - &Call, nullptr); + &Call, &ITraits); R = State->getSVal(Target.castAs(), E->getType()); } else { diff --git a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp index 64c42699fcf3c..a4918d7179ff5 100644 --- a/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/HTMLDiagnostics.cpp @@ -10,6 +10,7 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclBase.h" #include "clang/AST/Stmt.h" @@ -23,7 +24,6 @@ #include "clang/Rewrite/Core/HTMLRewrite.h" #include "clang/Rewrite/Core/Rewriter.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/IssueHash.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/ArrayRef.h" @@ -134,17 +134,17 @@ class HTMLDiagnostics : public PathDiagnosticConsumer { } // namespace -void ento::createHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string& prefix, - const Preprocessor &PP) { +void ento::createHTMLDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &) { C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, true)); } -void ento::createHTMLSingleFileDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string& prefix, - const Preprocessor &PP) { +void ento::createHTMLSingleFileDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &) { C.push_back(new HTMLDiagnostics(AnalyzerOpts, prefix, PP, false)); } @@ -555,8 +555,9 @@ void HTMLDiagnostics::FinalizeHTML(const PathDiagnostic& D, Rewriter &R, os << "\n\n"; os << "\n\n"; + << GetIssueHash(SMgr, L, D.getCheckerName(), D.getBugType(), + DeclWithIssue, PP.getLangOpts()) + << " -->\n"; os << "\n\n"; o << " issue_hash_content_of_line_in_context"; @@ -634,7 +689,7 @@ void PlistDiagnostics::FlushDiagnosticsImpl( : D->getLocation().asLocation()), SM); const Decl *DeclWithIssue = D->getDeclWithIssue(); - EmitString(o, GetIssueHash(SM, L, D->getCheckName(), D->getBugType(), + EmitString(o, GetIssueHash(SM, L, D->getCheckerName(), D->getBugType(), DeclWithIssue, LangOpts)) << '\n'; @@ -867,17 +922,23 @@ static const MacroInfo *getMacroInfoForLocation(const Preprocessor &PP, // Definitions of helper functions and methods for expanding macros. //===----------------------------------------------------------------------===// -static ExpansionInfo getExpandedMacro(SourceLocation MacroLoc, - const Preprocessor &PP) { +static ExpansionInfo +getExpandedMacro(SourceLocation MacroLoc, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { + + const Preprocessor *PPToUse = &PP; + if (auto LocAndUnit = CTU.getImportedFromSourceLocation(MacroLoc)) { + MacroLoc = LocAndUnit->first; + PPToUse = &LocAndUnit->second->getPreprocessor(); + } llvm::SmallString<200> ExpansionBuf; llvm::raw_svector_ostream OS(ExpansionBuf); - TokenPrinter Printer(OS, PP); + TokenPrinter Printer(OS, *PPToUse); llvm::SmallPtrSet AlreadyProcessedTokens; - std::string MacroName = - getMacroNameAndPrintExpansion(Printer, MacroLoc, PP, MacroArgMap{}, - AlreadyProcessedTokens); + std::string MacroName = getMacroNameAndPrintExpansion( + Printer, MacroLoc, *PPToUse, MacroArgMap{}, AlreadyProcessedTokens); return { MacroName, OS.str() }; } diff --git a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp index a1ca0b1b84bfa..f50d82de3b28d 100644 --- a/clang/lib/StaticAnalyzer/Core/ProgramState.cpp +++ b/clang/lib/StaticAnalyzer/Core/ProgramState.cpp @@ -15,7 +15,7 @@ #include "clang/Basic/JsonSupport.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" #include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" -#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicTypeMap.h" +#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicType.h" #include "clang/StaticAnalyzer/Core/PathSensitive/ProgramStateTrait.h" #include "clang/StaticAnalyzer/Core/PathSensitive/SubEngine.h" #include "llvm/Support/raw_ostream.h" diff --git a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp index 53d0cf54d703f..97b856ef04f15 100644 --- a/clang/lib/StaticAnalyzer/Core/RegionStore.cpp +++ b/clang/lib/StaticAnalyzer/Core/RegionStore.cpp @@ -108,7 +108,7 @@ class BindingKey { Data == X.Data; } - void dump() const; + LLVM_DUMP_METHOD void dump() const; }; } // end anonymous namespace @@ -135,7 +135,9 @@ static inline raw_ostream &operator<<(raw_ostream &Out, BindingKey K) { } // namespace llvm -LLVM_DUMP_METHOD void BindingKey::dump() const { llvm::errs() << *this; } +#if !defined(NDEBUG) || defined(LLVM_ENABLE_DUMP) +void BindingKey::dump() const { llvm::errs() << *this; } +#endif //===----------------------------------------------------------------------===// // Actual Store type. @@ -153,28 +155,42 @@ class RegionBindingsRef : public llvm::ImmutableMapRef { ClusterBindings::Factory *CBFactory; + // This flag indicates whether the current bindings are within the analysis + // that has started from main(). It affects how we perform loads from + // global variables that have initializers: if we have observed the + // program execution from the start and we know that these variables + // have not been overwritten yet, we can be sure that their initializers + // are still relevant. This flag never gets changed when the bindings are + // updated, so it could potentially be moved into RegionStoreManager + // (as if it's the same bindings but a different loading procedure) + // however that would have made the manager needlessly stateful. + bool IsMainAnalysis; + public: typedef llvm::ImmutableMapRef ParentTy; RegionBindingsRef(ClusterBindings::Factory &CBFactory, const RegionBindings::TreeTy *T, - RegionBindings::TreeTy::Factory *F) + RegionBindings::TreeTy::Factory *F, + bool IsMainAnalysis) : llvm::ImmutableMapRef(T, F), - CBFactory(&CBFactory) {} + CBFactory(&CBFactory), IsMainAnalysis(IsMainAnalysis) {} - RegionBindingsRef(const ParentTy &P, ClusterBindings::Factory &CBFactory) + RegionBindingsRef(const ParentTy &P, + ClusterBindings::Factory &CBFactory, + bool IsMainAnalysis) : llvm::ImmutableMapRef(P), - CBFactory(&CBFactory) {} + CBFactory(&CBFactory), IsMainAnalysis(IsMainAnalysis) {} RegionBindingsRef add(key_type_ref K, data_type_ref D) const { return RegionBindingsRef(static_cast(this)->add(K, D), - *CBFactory); + *CBFactory, IsMainAnalysis); } RegionBindingsRef remove(key_type_ref K) const { return RegionBindingsRef(static_cast(this)->remove(K), - *CBFactory); + *CBFactory, IsMainAnalysis); } RegionBindingsRef addBinding(BindingKey K, SVal V) const; @@ -204,20 +220,29 @@ class RegionBindingsRef : public llvm::ImmutableMapRef Ptr = { + asImmutableMap().getRootWithoutRetain(), IsMainAnalysis}; + return reinterpret_cast(Ptr.getOpaqueValue()); + } + + bool isMainAnalysis() const { + return IsMainAnalysis; } void printJson(raw_ostream &Out, const char *NL = "\n", unsigned int Space = 0, bool IsDot = false) const { for (iterator I = begin(); I != end(); ++I) { + // TODO: We might need a .printJson for I.getKey() as well. Indent(Out, Space, IsDot) - << "{ \"cluster\": \"" << I.getKey() << "\", \"items\": [" << NL; + << "{ \"cluster\": \"" << I.getKey() << "\", \"pointer\": \"" + << (const void *)I.getKey() << "\", \"items\": [" << NL; ++Space; const ClusterBindings &CB = I.getData(); for (ClusterBindings::iterator CI = CB.begin(); CI != CB.end(); ++CI) { - Indent(Out, Space, IsDot) << "{ " << CI.getKey() << ", \"value\": \"" - << CI.getData() << "\" }"; + Indent(Out, Space, IsDot) << "{ " << CI.getKey() << ", \"value\": "; + CI.getData().printJson(Out, /*AddQuotes=*/true); + Out << " }"; if (std::next(CI) != CB.end()) Out << ','; Out << NL; @@ -376,8 +401,15 @@ class RegionStoreManager : public StoreManager { /// casts from arrays to pointers. SVal ArrayToPointer(Loc Array, QualType ElementTy) override; + /// Creates the Store that correctly represents memory contents before + /// the beginning of the analysis of the given top-level stack frame. StoreRef getInitialStore(const LocationContext *InitLoc) override { - return StoreRef(RBFactory.getEmptyMap().getRootWithoutRetain(), *this); + bool IsMainAnalysis = false; + if (const auto *FD = dyn_cast(InitLoc->getDecl())) + IsMainAnalysis = FD->isMain() && !Ctx.getLangOpts().CPlusPlus; + return StoreRef(RegionBindingsRef( + RegionBindingsRef::ParentTy(RBFactory.getEmptyMap(), RBFactory), + CBFactory, IsMainAnalysis).asStore(), *this); } //===-------------------------------------------------------------------===// @@ -603,9 +635,13 @@ class RegionStoreManager : public StoreManager { //===------------------------------------------------------------------===// RegionBindingsRef getRegionBindings(Store store) const { - return RegionBindingsRef(CBFactory, - static_cast(store), - RBFactory.getTreeFactory()); + llvm::PointerIntPair Ptr; + Ptr.setFromOpaqueValue(const_cast(store)); + return RegionBindingsRef( + CBFactory, + static_cast(Ptr.getPointer()), + RBFactory.getTreeFactory(), + Ptr.getInt()); } void printJson(raw_ostream &Out, Store S, const char *NL = "\n", @@ -1666,10 +1702,12 @@ SVal RegionStoreManager::getBindingForElement(RegionBindingsConstRef B, return svalBuilder.makeIntVal(c, T); } } else if (const VarRegion *VR = dyn_cast(superR)) { - // Check if the containing array is const and has an initialized value. + // Check if the containing array has an initialized value that we can trust. + // We can trust a const value or a value of a global initializer in main(). const VarDecl *VD = VR->getDecl(); - // Either the array or the array element has to be const. - if (VD->getType().isConstQualified() || R->getElementType().isConstQualified()) { + if (VD->getType().isConstQualified() || + R->getElementType().isConstQualified() || + (B.isMainAnalysis() && VD->hasGlobalStorage())) { if (const Expr *Init = VD->getAnyInitializer()) { if (const auto *InitList = dyn_cast(Init)) { // The array index has to be known. @@ -1758,8 +1796,11 @@ SVal RegionStoreManager::getBindingForField(RegionBindingsConstRef B, const VarDecl *VD = VR->getDecl(); QualType RecordVarTy = VD->getType(); unsigned Index = FD->getFieldIndex(); - // Either the record variable or the field has to be const qualified. - if (RecordVarTy.isConstQualified() || Ty.isConstQualified()) + // Either the record variable or the field has an initializer that we can + // trust. We trust initializers of constants and, additionally, respect + // initializers of globals when analyzing main(). + if (RecordVarTy.isConstQualified() || Ty.isConstQualified() || + (B.isMainAnalysis() && VD->hasGlobalStorage())) if (const Expr *Init = VD->getAnyInitializer()) if (const auto *InitList = dyn_cast(Init)) { if (Index < InitList->getNumInits()) { @@ -1977,6 +2018,12 @@ SVal RegionStoreManager::getBindingForVar(RegionBindingsConstRef B, if (isa(MS)) { QualType T = VD->getType(); + // If we're in main(), then global initializers have not become stale yet. + if (B.isMainAnalysis()) + if (const Expr *Init = VD->getAnyInitializer()) + if (Optional V = svalBuilder.getConstantVal(Init)) + return *V; + // Function-scoped static variables are default-initialized to 0; if they // have an initializer, it would have been processed by now. // FIXME: This is only true when we're starting analysis from main(). @@ -2244,8 +2291,7 @@ RegionBindingsRef RegionStoreManager::bindVector(RegionBindingsConstRef B, const TypedValueRegion* R, SVal V) { QualType T = R->getValueType(); - assert(T->isVectorType()); - const VectorType *VT = T->getAs(); // Use getAs for typedefs. + const VectorType *VT = T->castAs(); // Use castAs for typedefs. // Handle lazy compound values and symbolic values. if (V.getAs() || V.getAs()) @@ -2330,7 +2376,7 @@ RegionBindingsRef RegionStoreManager::bindStruct(RegionBindingsConstRef B, QualType T = R->getValueType(); assert(T->isStructureOrClassType()); - const RecordType* RT = T->getAs(); + const RecordType* RT = T->castAs(); const RecordDecl *RD = RT->getDecl(); if (!RD->isCompleteDefinition()) @@ -2637,7 +2683,7 @@ void RegionStoreManager::printJson(raw_ostream &Out, Store S, const char *NL, return; } - Out << '[' << NL; - Bindings.printJson(Out, NL, ++Space, IsDot); - Indent(Out, --Space, IsDot) << "]," << NL; + Out << "{ \"pointer\": \"" << Bindings.asStore() << "\", \"items\": [" << NL; + Bindings.printJson(Out, NL, Space + 1, IsDot); + Indent(Out, Space, IsDot) << "]}," << NL; } diff --git a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp index 4233f25edb349..1e60966e7bbd0 100644 --- a/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp +++ b/clang/lib/StaticAnalyzer/Core/SarifDiagnostics.cpp @@ -10,10 +10,10 @@ // //===----------------------------------------------------------------------===// +#include "clang/Analysis/PathDiagnostic.h" #include "clang/Basic/Version.h" #include "clang/Lex/Preprocessor.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "llvm/ADT/STLExtras.h" #include "llvm/ADT/StringMap.h" @@ -43,10 +43,10 @@ class SarifDiagnostics : public PathDiagnosticConsumer { }; } // end anonymous namespace -void ento::createSarifDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &Output, - const Preprocessor &) { +void ento::createSarifDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Output, const Preprocessor &, + const cross_tu::CrossTranslationUnitContext &) { C.push_back(new SarifDiagnostics(AnalyzerOpts, Output)); } @@ -240,7 +240,7 @@ static json::Object createResult(const PathDiagnostic &Diag, json::Array &Files, const PathPieces &Path = Diag.path.flatten(false); const SourceManager &SMgr = Path.front()->getLocation().getManager(); - auto Iter = RuleMapping.find(Diag.getCheckName()); + auto Iter = RuleMapping.find(Diag.getCheckerName()); assert(Iter != RuleMapping.end() && "Rule ID is not in the array index map?"); return json::Object{ @@ -251,7 +251,7 @@ static json::Object createResult(const PathDiagnostic &Diag, json::Array &Files, Diag.getLocation().asRange(), *Diag.getLocation().asLocation().getFileEntry(), SMgr, Files))}}, {"ruleIndex", Iter->getValue()}, - {"ruleId", Diag.getCheckName()}}; + {"ruleId", Diag.getCheckerName()}}; } static StringRef getRuleDescription(StringRef CheckName) { @@ -277,7 +277,7 @@ static StringRef getRuleHelpURIStr(StringRef CheckName) { } static json::Object createRule(const PathDiagnostic &Diag) { - StringRef CheckName = Diag.getCheckName(); + StringRef CheckName = Diag.getCheckerName(); json::Object Ret{ {"fullDescription", createMessage(getRuleDescription(CheckName))}, {"name", createMessage(CheckName)}, @@ -296,7 +296,7 @@ static json::Array createRules(std::vector &Diags, llvm::StringSet<> Seen; llvm::for_each(Diags, [&](const PathDiagnostic *D) { - StringRef RuleID = D->getCheckName(); + StringRef RuleID = D->getCheckerName(); std::pair::iterator, bool> P = Seen.insert(RuleID); if (P.second) { RuleMapping[RuleID] = Rules.size(); // Maps RuleID to an Array Index. diff --git a/clang/lib/StaticAnalyzer/Core/Store.cpp b/clang/lib/StaticAnalyzer/Core/Store.cpp index 3cf616161c669..b4ab6877726ce 100644 --- a/clang/lib/StaticAnalyzer/Core/Store.cpp +++ b/clang/lib/StaticAnalyzer/Core/Store.cpp @@ -52,7 +52,7 @@ StoreRef StoreManager::enterStackFrame(Store OldStore, Call.getInitialStackFrameContents(LCtx, InitialBindings); for (const auto &I : InitialBindings) - Store = Bind(Store.getStore(), I.first, I.second); + Store = Bind(Store.getStore(), I.first.castAs(), I.second); return Store; } diff --git a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp index a75ff2563cf51..99f92bb81773e 100644 --- a/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/AnalysisConsumer.cpp @@ -12,6 +12,7 @@ #include "clang/StaticAnalyzer/Frontend/AnalysisConsumer.h" #include "ModelInjector.h" +#include "clang/Analysis/PathDiagnostic.h" #include "clang/AST/Decl.h" #include "clang/AST/DeclCXX.h" #include "clang/AST/DeclObjC.h" @@ -27,7 +28,6 @@ #include "clang/StaticAnalyzer/Checkers/LocalCheckers.h" #include "clang/StaticAnalyzer/Core/AnalyzerOptions.h" #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" -#include "clang/StaticAnalyzer/Core/BugReporter/PathDiagnostic.h" #include "clang/StaticAnalyzer/Core/CheckerManager.h" #include "clang/StaticAnalyzer/Core/PathDiagnosticConsumers.h" #include "clang/StaticAnalyzer/Core/PathSensitive/AnalysisManager.h" @@ -64,30 +64,30 @@ STATISTIC(MaxCFGSize, "The maximum number of basic blocks in a function."); // Special PathDiagnosticConsumers. //===----------------------------------------------------------------------===// -void ento::createPlistHTMLDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &prefix, - const Preprocessor &PP) { +void ento::createPlistHTMLDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &prefix, const Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { createHTMLDiagnosticConsumer(AnalyzerOpts, C, - llvm::sys::path::parent_path(prefix), PP); - createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP); + llvm::sys::path::parent_path(prefix), PP, CTU); + createPlistMultiFileDiagnosticConsumer(AnalyzerOpts, C, prefix, PP, CTU); } -void ento::createTextPathDiagnosticConsumer(AnalyzerOptions &AnalyzerOpts, - PathDiagnosticConsumers &C, - const std::string &Prefix, - const clang::Preprocessor &PP) { +void ento::createTextPathDiagnosticConsumer( + AnalyzerOptions &AnalyzerOpts, PathDiagnosticConsumers &C, + const std::string &Prefix, const clang::Preprocessor &PP, + const cross_tu::CrossTranslationUnitContext &CTU) { llvm_unreachable("'text' consumer should be enabled on ClangDiags"); } namespace { class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { DiagnosticsEngine &Diag; - bool IncludePath, ShouldEmitAsError; + bool IncludePath = false, ShouldEmitAsError = false, FixitsAsRemarks = false; public: ClangDiagPathDiagConsumer(DiagnosticsEngine &Diag) - : Diag(Diag), IncludePath(false), ShouldEmitAsError(false) {} + : Diag(Diag) {} ~ClangDiagPathDiagConsumer() override {} StringRef getName() const override { return "ClangDiags"; } @@ -98,11 +98,9 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { return IncludePath ? Minimal : None; } - void enablePaths() { - IncludePath = true; - } - + void enablePaths() { IncludePath = true; } void enableWerror() { ShouldEmitAsError = true; } + void enableFixitsAsRemarks() { FixitsAsRemarks = true; } void FlushDiagnosticsImpl(std::vector &Diags, FilesMade *filesMade) override { @@ -111,22 +109,46 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { ? Diag.getCustomDiagID(DiagnosticsEngine::Error, "%0") : Diag.getCustomDiagID(DiagnosticsEngine::Warning, "%0"); unsigned NoteID = Diag.getCustomDiagID(DiagnosticsEngine::Note, "%0"); - - for (std::vector::iterator I = Diags.begin(), - E = Diags.end(); I != E; ++I) { + unsigned RemarkID = Diag.getCustomDiagID(DiagnosticsEngine::Remark, "%0"); + + auto reportPiece = + [&](unsigned ID, SourceLocation Loc, StringRef String, + ArrayRef Ranges, ArrayRef Fixits) { + if (!FixitsAsRemarks) { + Diag.Report(Loc, ID) << String << Ranges << Fixits; + } else { + Diag.Report(Loc, ID) << String << Ranges; + for (const FixItHint &Hint : Fixits) { + SourceManager &SM = Diag.getSourceManager(); + llvm::SmallString<128> Str; + llvm::raw_svector_ostream OS(Str); + // FIXME: Add support for InsertFromRange and + // BeforePreviousInsertion. + assert(!Hint.InsertFromRange.isValid() && "Not implemented yet!"); + assert(!Hint.BeforePreviousInsertions && "Not implemented yet!"); + OS << SM.getSpellingColumnNumber(Hint.RemoveRange.getBegin()) + << "-" << SM.getSpellingColumnNumber(Hint.RemoveRange.getEnd()) + << ": '" << Hint.CodeToInsert << "'"; + Diag.Report(Loc, RemarkID) << OS.str(); + } + } + }; + + for (std::vector::iterator I = Diags.begin(), + E = Diags.end(); + I != E; ++I) { const PathDiagnostic *PD = *I; - SourceLocation WarnLoc = PD->getLocation().asLocation(); - Diag.Report(WarnLoc, WarnID) << PD->getShortDescription() - << PD->path.back()->getRanges(); + reportPiece(WarnID, PD->getLocation().asLocation(), + PD->getShortDescription(), PD->path.back()->getRanges(), + PD->path.back()->getFixits()); // First, add extra notes, even if paths should not be included. for (const auto &Piece : PD->path) { if (!isa(Piece.get())) continue; - SourceLocation NoteLoc = Piece->getLocation().asLocation(); - Diag.Report(NoteLoc, NoteID) << Piece->getString() - << Piece->getRanges(); + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), Piece->getFixits()); } if (!IncludePath) @@ -138,9 +160,8 @@ class ClangDiagPathDiagConsumer : public PathDiagnosticConsumer { if (isa(Piece.get())) continue; - SourceLocation NoteLoc = Piece->getLocation().asLocation(); - Diag.Report(NoteLoc, NoteID) << Piece->getString() - << Piece->getRanges(); + reportPiece(NoteID, Piece->getLocation().asLocation(), + Piece->getString(), Piece->getRanges(), Piece->getFixits()); } } } @@ -196,7 +217,9 @@ class AnalysisConsumer : public AnalysisASTConsumer, /// Time the analyzes time of each translation unit. std::unique_ptr AnalyzerTimers; - std::unique_ptr TUTotalTimer; + std::unique_ptr SyntaxCheckTimer; + std::unique_ptr ExprEngineTimer; + std::unique_ptr BugReporterTimer; /// The information about analyzed functions shared throughout the /// translation unit. @@ -212,8 +235,13 @@ class AnalysisConsumer : public AnalysisASTConsumer, if (Opts->PrintStats || Opts->ShouldSerializeStats) { AnalyzerTimers = llvm::make_unique( "analyzer", "Analyzer timers"); - TUTotalTimer = llvm::make_unique( - "time", "Analyzer total time", *AnalyzerTimers); + SyntaxCheckTimer = llvm::make_unique( + "syntaxchecks", "Syntax-based analysis time", *AnalyzerTimers); + ExprEngineTimer = llvm::make_unique( + "exprengine", "Path exploration time", *AnalyzerTimers); + BugReporterTimer = llvm::make_unique( + "bugreporter", "Path-sensitive report post-processing time", + *AnalyzerTimers); llvm::EnableStatistics(/* PrintOnExit= */ false); } } @@ -234,6 +262,9 @@ class AnalysisConsumer : public AnalysisASTConsumer, if (Opts->AnalyzerWerror) clangDiags->enableWerror(); + if (Opts->ShouldEmitFixItHintsAsRemarks) + clangDiags->enableFixitsAsRemarks(); + if (Opts->AnalysisDiagOpt == PD_TEXT) { clangDiags->enablePaths(); @@ -242,7 +273,7 @@ class AnalysisConsumer : public AnalysisASTConsumer, default: #define ANALYSIS_DIAGNOSTICS(NAME, CMDFLAG, DESC, CREATEFN) \ case PD_##NAME: \ - CREATEFN(*Opts.get(), PathConsumers, OutDir, PP); \ + CREATEFN(*Opts.get(), PathConsumers, OutDir, PP, CTU); \ break; #include "clang/StaticAnalyzer/Core/Analyses.def" } @@ -305,8 +336,8 @@ class AnalysisConsumer : public AnalysisASTConsumer, *Ctx, *Opts, Plugins, CheckerRegistrationFns, PP.getDiagnostics()); Mgr = llvm::make_unique( - *Ctx, PP.getDiagnostics(), PathConsumers, CreateStoreMgr, - CreateConstraintMgr, checkerMgr.get(), *Opts, Injector); + *Ctx, PathConsumers, CreateStoreMgr, CreateConstraintMgr, + checkerMgr.get(), *Opts, Injector); } /// Store the top level decls in the set to be processed later on. @@ -346,8 +377,13 @@ class AnalysisConsumer : public AnalysisASTConsumer, /// Handle callbacks for arbitrary Decls. bool VisitDecl(Decl *D) { AnalysisMode Mode = getModeForDecl(D, RecVisitorMode); - if (Mode & AM_Syntax) + if (Mode & AM_Syntax) { + if (SyntaxCheckTimer) + SyntaxCheckTimer->startTimer(); checkerMgr->runCheckersOnASTDecl(D, *Mgr, *RecVisitorBR); + if (SyntaxCheckTimer) + SyntaxCheckTimer->stopTimer(); + } return true; } @@ -566,7 +602,11 @@ static bool isBisonFile(ASTContext &C) { void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { BugReporter BR(*Mgr); TranslationUnitDecl *TU = C.getTranslationUnitDecl(); + if (SyntaxCheckTimer) + SyntaxCheckTimer->startTimer(); checkerMgr->runCheckersOnASTDecl(TU, *Mgr, BR); + if (SyntaxCheckTimer) + SyntaxCheckTimer->stopTimer(); // Run the AST-only checks using the order in which functions are defined. // If inlining is not turned on, use the simplest function order for path @@ -593,6 +633,7 @@ void AnalysisConsumer::runAnalysisOnTranslationUnit(ASTContext &C) { // After all decls handled, run checkers on the entire TranslationUnit. checkerMgr->runCheckersOnEndOfTranslationUnit(TU, *Mgr, BR); + BR.FlushReports(); RecVisitorBR = nullptr; } @@ -608,11 +649,9 @@ void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { if (Diags.hasErrorOccurred() || Diags.hasFatalErrorOccurred()) return; - if (TUTotalTimer) TUTotalTimer->startTimer(); - if (isBisonFile(C)) { reportAnalyzerProgress("Skipping bison-generated file\n"); - } else if (Opts->DisableAllChecks) { + } else if (Opts->DisableAllCheckers) { // Don't analyze if the user explicitly asked for no checks to be performed // on this file. @@ -622,8 +661,6 @@ void AnalysisConsumer::HandleTranslationUnit(ASTContext &C) { runAnalysisOnTranslationUnit(C); } - if (TUTotalTimer) TUTotalTimer->stopTimer(); - // Count how many basic blocks we have not covered. NumBlocksInAnalyzedFunctions = FunctionSummaries.getTotalNumBasicBlocks(); NumVisitedBlocksInAnalyzedFunctions = @@ -747,8 +784,16 @@ void AnalysisConsumer::HandleCode(Decl *D, AnalysisMode Mode, BugReporter BR(*Mgr); - if (Mode & AM_Syntax) + if (Mode & AM_Syntax) { + if (SyntaxCheckTimer) + SyntaxCheckTimer->startTimer(); checkerMgr->runCheckersOnASTBody(D, *Mgr, BR); + if (SyntaxCheckTimer) + SyntaxCheckTimer->stopTimer(); + } + + BR.FlushReports(); + if ((Mode & AM_Path) && checkerMgr->hasPathSensitiveCheckers()) { RunPathSensitiveChecks(D, IMode, VisitedCallees); if (IMode != ExprEngine::Inline_Minimal) @@ -775,8 +820,12 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, ExprEngine Eng(CTU, *Mgr, VisitedCallees, &FunctionSummaries, IMode); // Execute the worklist algorithm. + if (ExprEngineTimer) + ExprEngineTimer->startTimer(); Eng.ExecuteWorkList(Mgr->getAnalysisDeclContextManager().getStackFrame(D), Mgr->options.MaxNodesPerTopLevelFunction); + if (ExprEngineTimer) + ExprEngineTimer->stopTimer(); if (!Mgr->options.DumpExplodedGraphTo.empty()) Eng.DumpGraph(Mgr->options.TrimGraph, Mgr->options.DumpExplodedGraphTo); @@ -786,7 +835,11 @@ void AnalysisConsumer::RunPathSensitiveChecks(Decl *D, Eng.ViewGraph(Mgr->options.TrimGraph); // Display warnings. + if (BugReporterTimer) + BugReporterTimer->startTimer(); Eng.getBugReporter().FlushReports(); + if (BugReporterTimer) + BugReporterTimer->stopTimer(); } //===----------------------------------------------------------------------===// diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp index 1e45ee96145ab..27555537c89e4 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistration.cpp @@ -74,11 +74,22 @@ void ento::printCheckerConfigList(raw_ostream &OS, } void ento::printAnalyzerConfigList(raw_ostream &out) { - out << "OVERVIEW: Clang Static Analyzer -analyzer-config Option List\n\n"; - out << "USAGE: -analyzer-config \n\n"; - out << " -analyzer-config OPTION1=VALUE, -analyzer-config " - "OPTION2=VALUE, ...\n\n"; - out << "OPTIONS:\n\n"; + // FIXME: This message sounds scary, should be scary, but incorrectly states + // that all configs are super dangerous. In reality, many of them should be + // accessible to the user. We should create a user-facing subset of config + // options under a different frontend flag. + out << R"( +OVERVIEW: Clang Static Analyzer -analyzer-config Option List + +The following list of configurations are meant for development purposes only, as +some of the variables they define are set to result in the most optimal +analysis. Setting them to other values may drastically change how the analyzer +behaves, and may even result in instabilities, crashes! + +USAGE: -analyzer-config + -analyzer-config OPTION1=VALUE, -analyzer-config OPTION2=VALUE, ... +OPTIONS: +)"; using OptionAndDescriptionTy = std::pair; OptionAndDescriptionTy PrintableOptions[] = { diff --git a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp index 3fd4c36947cbb..e00fd976f6b80 100644 --- a/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp +++ b/clang/lib/StaticAnalyzer/Frontend/CheckerRegistry.cpp @@ -200,12 +200,12 @@ CheckerRegistry::CheckerRegistry( // Parse '-analyzer-checker' and '-analyzer-disable-checker' options from the // command line. - for (const std::pair &Opt : AnOpts.CheckersControlList) { + for (const std::pair &Opt : AnOpts.CheckersAndPackages) { CheckerInfoListRange CheckerForCmdLineArg = getMutableCheckersForCmdLineArg(Opt.first); if (CheckerForCmdLineArg.begin() == CheckerForCmdLineArg.end()) { - Diags.Report(diag::err_unknown_analyzer_checker) << Opt.first; + Diags.Report(diag::err_unknown_analyzer_checker_or_package) << Opt.first; Diags.Report(diag::note_suggest_disabling_all_checkers); } @@ -437,7 +437,7 @@ void CheckerRegistry::initializeManager(CheckerManager &CheckerMgr) const { // Initialize the CheckerManager with all enabled checkers. for (const auto *Checker : enabledCheckers) { - CheckerMgr.setCurrentCheckName(CheckName(Checker->FullName)); + CheckerMgr.setCurrentCheckerName(CheckerNameRef(Checker->FullName)); Checker->Initialize(CheckerMgr); } } @@ -468,9 +468,10 @@ isOptionContainedIn(const CheckerRegistry::CmdLineOptionList &OptionList, void CheckerRegistry::validateCheckerOptions() const { for (const auto &Config : AnOpts.Config) { - StringRef SuppliedChecker; + StringRef SuppliedCheckerOrPackage; StringRef SuppliedOption; - std::tie(SuppliedChecker, SuppliedOption) = Config.getKey().split(':'); + std::tie(SuppliedCheckerOrPackage, SuppliedOption) = + Config.getKey().split(':'); if (SuppliedOption.empty()) continue; @@ -483,21 +484,24 @@ void CheckerRegistry::validateCheckerOptions() const { // Since lower_bound would look for the first element *not less* than "cor", // it would return with an iterator to the first checker in the core, so we // we really have to use find here, which uses operator==. - auto CheckerIt = llvm::find(Checkers, CheckerInfo(SuppliedChecker)); + auto CheckerIt = + llvm::find(Checkers, CheckerInfo(SuppliedCheckerOrPackage)); if (CheckerIt != Checkers.end()) { - isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedChecker, + isOptionContainedIn(CheckerIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; } - auto PackageIt = llvm::find(Packages, PackageInfo(SuppliedChecker)); + auto PackageIt = + llvm::find(Packages, PackageInfo(SuppliedCheckerOrPackage)); if (PackageIt != Packages.end()) { - isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedChecker, + isOptionContainedIn(PackageIt->CmdLineOptions, SuppliedCheckerOrPackage, SuppliedOption, AnOpts, Diags); continue; } - Diags.Report(diag::err_unknown_analyzer_checker) << SuppliedChecker; + Diags.Report(diag::err_unknown_analyzer_checker_or_package) + << SuppliedCheckerOrPackage; } } diff --git a/clang/test/Analysis/Inputs/ctu-other.cpp b/clang/test/Analysis/Inputs/ctu-other.cpp index de7d064135f4d..a9ff6b5a93a57 100644 --- a/clang/test/Analysis/Inputs/ctu-other.cpp +++ b/clang/test/Analysis/Inputs/ctu-other.cpp @@ -38,6 +38,7 @@ int embed_cls::fecl(int x) { class mycls { public: int fcl(int x); + virtual int fvcl(int x); static int fscl(int x); class embed_cls2 { @@ -49,6 +50,9 @@ class mycls { int mycls::fcl(int x) { return x + 5; } +int mycls::fvcl(int x) { + return x + 7; +} int mycls::fscl(int x) { return x + 6; } @@ -56,6 +60,15 @@ int mycls::embed_cls2::fecl2(int x) { return x - 11; } +class derived : public mycls { +public: + virtual int fvcl(int x) override; +}; + +int derived::fvcl(int x) { + return x + 8; +} + namespace chns { int chf2(int x); diff --git a/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt b/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt index 57f4194831afa..3df181b29d51e 100644 --- a/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt +++ b/clang/test/Analysis/Inputs/ctu-other.cpp.externalDefMap.txt @@ -3,8 +3,10 @@ c:@N@myns@N@embed_ns@F@fens#I# ctu-other.cpp.ast c:@F@g#I# ctu-other.cpp.ast c:@S@mycls@F@fscl#I#S ctu-other.cpp.ast c:@S@mycls@F@fcl#I# ctu-other.cpp.ast +c:@S@mycls@F@fvcl#I# ctu-other.cpp.ast c:@N@myns@S@embed_cls@F@fecl#I# ctu-other.cpp.ast c:@S@mycls@S@embed_cls2@F@fecl2#I# ctu-other.cpp.ast +c:@S@derived@F@fvcl#I# ctu-other.cpp.ast c:@F@f#I# ctu-other.cpp.ast c:@N@myns@F@fns#I# ctu-other.cpp.ast c:@F@h#I# ctu-other.cpp.ast diff --git a/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist b/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist index e7f6dba2fa43f..edcaaf2cd44b8 100644 --- a/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist +++ b/clang/test/Analysis/Inputs/expected-plists/cxx-for-range.cpp.plist @@ -191,7 +191,7 @@ line11 - col14 + col9 file0 @@ -515,7 +515,7 @@ line11 - col14 + col9 file0 diff --git a/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist b/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist index b4c79018c6665..b949e20ebbe86 100644 --- a/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist +++ b/clang/test/Analysis/Inputs/expected-plists/edges-new.mm.plist @@ -2727,7 +2727,7 @@ line146 - col13 + col8 file0 @@ -2949,7 +2949,7 @@ line146 - col13 + col8 file0 @@ -3929,7 +3929,7 @@ line178 - col14 + col9 file0 @@ -4185,7 +4185,7 @@ line178 - col14 + col9 file0 @@ -4281,7 +4281,7 @@ line181 - col14 + col9 file0 @@ -8087,7 +8087,7 @@ location line267 - col18 + col19 file0 ranges @@ -8095,7 +8095,7 @@ line267 - col18 + col19 file0 @@ -8119,12 +8119,12 @@ line267 - col18 + col19 file0 line267 - col18 + col22 file0 @@ -10853,12 +10853,12 @@ depth0 extended_message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' - descriptionNull pointer passed as an argument to a 'nonnull' parameter + descriptionNull pointer passed to 1st parameter expecting 'nonnull' categoryAPI typeArgument with 'nonnull' attribute passed null check_namecore.NonNullParamChecker @@ -11983,12 +11983,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12000,7 +12000,7 @@ location line457 - col9 + col10 file0 ranges @@ -12008,7 +12008,7 @@ line457 - col9 + col10 file0 @@ -12032,12 +12032,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12244,12 +12244,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12261,7 +12261,7 @@ location line457 - col9 + col10 file0 ranges @@ -12269,7 +12269,7 @@ line457 - col9 + col10 file0 @@ -12293,12 +12293,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12571,12 +12571,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -12588,7 +12588,7 @@ location line457 - col9 + col10 file0 ranges @@ -12596,7 +12596,7 @@ line457 - col9 + col10 file0 @@ -12620,12 +12620,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13128,12 +13128,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13145,7 +13145,7 @@ location line457 - col9 + col10 file0 ranges @@ -13153,7 +13153,7 @@ line457 - col9 + col10 file0 @@ -13177,12 +13177,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13752,12 +13752,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -13769,7 +13769,7 @@ location line457 - col9 + col10 file0 ranges @@ -13777,7 +13777,7 @@ line457 - col9 + col10 file0 @@ -13801,12 +13801,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -15295,12 +15295,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -15312,7 +15312,7 @@ location line457 - col9 + col10 file0 ranges @@ -15320,7 +15320,7 @@ line457 - col9 + col10 file0 @@ -15344,12 +15344,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -16965,12 +16965,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -16982,7 +16982,7 @@ location line457 - col9 + col10 file0 ranges @@ -16990,7 +16990,7 @@ line457 - col9 + col10 file0 @@ -17014,12 +17014,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -18860,12 +18860,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -18877,7 +18877,7 @@ location line457 - col9 + col10 file0 ranges @@ -18885,7 +18885,7 @@ line457 - col9 + col10 file0 @@ -18909,12 +18909,12 @@ line457 - col9 + col10 file0 line457 - col9 + col14 file0 @@ -22243,6 +22243,40 @@ + + kindcontrol + edges + + + start + + + line587 + col11 + file0 + + + line587 + col11 + file0 + + + end + + + line587 + col11 + file0 + + + line587 + col11 + file0 + + + + + kindpop-up location @@ -22251,21 +22285,6 @@ col11 file0 - ranges - - - - line587 - col11 - file0 - - - line587 - col16 - file0 - - - extended_message Field 'b' is equal to 2 message diff --git a/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist b/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist index db6b5af0d5684..1ee17de4b5fa9 100644 --- a/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist +++ b/clang/test/Analysis/Inputs/expected-plists/inline-plist.c.plist @@ -548,7 +548,7 @@ line45 - col12 + col7 file0 diff --git a/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist b/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist index 926f827426499..3c87e3909bec5 100644 --- a/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist +++ b/clang/test/Analysis/Inputs/expected-plists/objc-radar17039661.m.plist @@ -836,7 +836,7 @@ line38 - col37 + col20 file0 diff --git a/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist b/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist index 5b1de9121f35c..9203e48c46835 100644 --- a/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist +++ b/clang/test/Analysis/Inputs/expected-plists/plist-output.m.plist @@ -2513,7 +2513,7 @@ line96 - col13 + col8 file0 @@ -2735,7 +2735,7 @@ line96 - col13 + col8 file0 @@ -3554,7 +3554,7 @@ line127 - col14 + col9 file0 @@ -3776,7 +3776,7 @@ line127 - col14 + col9 file0 @@ -6141,12 +6141,12 @@ depth0 extended_message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' message - Null pointer passed as an argument to a 'nonnull' parameter + Null pointer passed to 1st parameter expecting 'nonnull' - descriptionNull pointer passed as an argument to a 'nonnull' parameter + descriptionNull pointer passed to 1st parameter expecting 'nonnull' categoryAPI typeArgument with 'nonnull' attribute passed null check_namecore.NonNullParamChecker diff --git a/clang/test/Analysis/Inputs/llvm.h b/clang/test/Analysis/Inputs/llvm.h new file mode 100644 index 0000000000000..c9d66ba2374d3 --- /dev/null +++ b/clang/test/Analysis/Inputs/llvm.h @@ -0,0 +1,30 @@ +#pragma clang system_header + +#include "system-header-simulator-cxx.h" + +namespace llvm { +template +const X *cast(Y Value); + +template +const X *dyn_cast(Y *Value); +template +const X &dyn_cast(Y &Value); + +template +const X *cast_or_null(Y Value); + +template +const X *dyn_cast_or_null(Y *Value); +template +const X *dyn_cast_or_null(Y &Value); + +template +bool isa(Y Value); + +template +bool isa_and_nonnull(Y Value); + +template +std::unique_ptr cast(std::unique_ptr &&Value); +} // namespace llvm diff --git a/clang/test/Analysis/Inputs/plist-macros-ctu.c b/clang/test/Analysis/Inputs/plist-macros-ctu.c new file mode 100644 index 0000000000000..f3e374caae58b --- /dev/null +++ b/clang/test/Analysis/Inputs/plist-macros-ctu.c @@ -0,0 +1,21 @@ + +#include "plist-macros-ctu.h" + +#define M *X = (int *)0 + +void F1(int **X) { + M; +} + +#undef M +#define M *Y = (int *)0 + +void F2(int **Y) { + M; +} + +#define M1 *Z = (int *)0 + +void F3(int **Z) { + M1; +} diff --git a/clang/test/Analysis/Inputs/plist-macros-ctu.h b/clang/test/Analysis/Inputs/plist-macros-ctu.h new file mode 100644 index 0000000000000..0b1b9d7a4f533 --- /dev/null +++ b/clang/test/Analysis/Inputs/plist-macros-ctu.h @@ -0,0 +1,4 @@ +#define M_H *A = (int *)0 +void F_H(int **A) { + M_H; +} diff --git a/clang/test/Analysis/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt b/clang/test/Analysis/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt new file mode 100644 index 0000000000000..52214a41891ef --- /dev/null +++ b/clang/test/Analysis/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt @@ -0,0 +1,4 @@ +c:@F@F1 plist-macros-ctu.c.ast +c:@F@F2 plist-macros-ctu.c.ast +c:@F@F3 plist-macros-ctu.c.ast +c:@F@F_H plist-macros-ctu.c.ast diff --git a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h index 5b37e96f60277..26248a4d1f51a 100644 --- a/clang/test/Analysis/Inputs/system-header-simulator-cxx.h +++ b/clang/test/Analysis/Inputs/system-header-simulator-cxx.h @@ -70,6 +70,9 @@ template struct __vector_iterator { return ptr -= n; } + template + difference_type operator-(const __vector_iterator &rhs); + Ref operator*() const { return *ptr; } Ptr operator->() const { return *ptr; } @@ -147,7 +150,7 @@ template struct __list_iterator { typedef std::bidirectional_iterator_tag iterator_category; __list_iterator(T* it = 0) : item(it) {} - __list_iterator(const iterator &rhs): item(rhs.base()) {} + __list_iterator(const iterator &rhs): item(rhs.item) {} __list_iterator operator++() { item = item->next; return *this; } __list_iterator operator++(int) { auto tmp = *this; @@ -172,6 +175,9 @@ template struct __list_iterator { const T* &base() const { return item; } + template + friend struct __list_iterator; + private: T* item; }; @@ -187,7 +193,7 @@ template struct __fwdl_iterator { typedef std::forward_iterator_tag iterator_category; __fwdl_iterator(T* it = 0) : item(it) {} - __fwdl_iterator(const iterator &rhs): item(rhs.base()) {} + __fwdl_iterator(const iterator &rhs): item(rhs.item) {} __fwdl_iterator operator++() { item = item->next; return *this; } __fwdl_iterator operator++(int) { auto tmp = *this; @@ -205,6 +211,9 @@ template struct __fwdl_iterator { const T* &base() const { return item; } + template + friend struct __fwdl_iterator; + private: T* item; }; diff --git a/clang/test/Analysis/Inputs/taint-generic-config-ill-formed.yaml b/clang/test/Analysis/Inputs/taint-generic-config-ill-formed.yaml new file mode 100755 index 0000000000000..9b79b32027942 --- /dev/null +++ b/clang/test/Analysis/Inputs/taint-generic-config-ill-formed.yaml @@ -0,0 +1,4 @@ +Propagations: + - Name: mySource1 + DstArgs: [-1] + NotExist: 1 diff --git a/clang/test/Analysis/Inputs/taint-generic-config-invalid-arg.yaml b/clang/test/Analysis/Inputs/taint-generic-config-invalid-arg.yaml new file mode 100755 index 0000000000000..c06aef83de840 --- /dev/null +++ b/clang/test/Analysis/Inputs/taint-generic-config-invalid-arg.yaml @@ -0,0 +1,3 @@ +Propagations: + - Name: mySource1 + DstArgs: [-2] diff --git a/clang/test/Analysis/Inputs/taint-generic-config.yaml b/clang/test/Analysis/Inputs/taint-generic-config.yaml new file mode 100755 index 0000000000000..0a822d7750398 --- /dev/null +++ b/clang/test/Analysis/Inputs/taint-generic-config.yaml @@ -0,0 +1,50 @@ +# A list of source/propagation function +Propagations: + # int x = mySource1(); // x is tainted + - Name: mySource1 + DstArgs: [-1] # Index for return value + + # int x; + # mySource2(&x); // x is tainted + - Name: mySource2 + DstArgs: [0] + + # int x, y; + # myScanf("%d %d", &x, &y); // x and y are tainted + - Name: myScanf + VariadicType: Dst + VariadicIndex: 1 + + # int x; // x is tainted + # int y; + # myPropagator(x, &y); // y is tainted + - Name: myPropagator + SrcArgs: [0] + DstArgs: [1] + + # constexpr unsigned size = 100; + # char buf[size]; + # int x, y; + # int n = mySprintf(buf, size, "%d %d", x, y); // If size, x or y is tainted + # // the return value and the buf will be tainted + - Name: mySnprintf + SrcArgs: [1] + DstArgs: [0, -1] + VariadicType: Src + VariadicIndex: 3 + +# A list of filter functions +Filters: + # int x; // x is tainted + # myFilter(&x); // x is not tainted anymore + - Name: myFilter + Args: [0] + +# A list of sink functions +Sinks: + # int x, y; // x and y are tainted + # mySink(x, 0, 1); // It will warn + # mySink(0, 1, y); // It will warn + # mySink(0, x, 1); // It won't warn + - Name: mySink + Args: [0, 2] diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c index a9c2eaa353b34..6c6883d88d69a 100644 --- a/clang/test/Analysis/analyzer-config.c +++ b/clang/test/Analysis/analyzer-config.c @@ -9,6 +9,7 @@ // CHECK-NEXT: alpha.clone.CloneChecker:ReportNormalClones = true // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtExec = 0x04 // CHECK-NEXT: alpha.security.MmapWriteExec:MmapProtRead = 0x01 +// CHECK-NEXT: alpha.security.taint.TaintPropagation:Config = "" // CHECK-NEXT: avoid-suppressing-null-argument-paths = false // CHECK-NEXT: c++-allocator-inlining = true // CHECK-NEXT: c++-container-inlining = false @@ -27,7 +28,10 @@ // CHECK-NEXT: cplusplus.Move:WarnOn = KnownsAndLocals // CHECK-NEXT: crosscheck-with-z3 = false // CHECK-NEXT: ctu-dir = "" +// CHECK-NEXT: ctu-import-threshold = 100 // CHECK-NEXT: ctu-index-name = externalDefMap.txt +// CHECK-NEXT: deadcode.DeadStores:ShowFixIts = false +// CHECK-NEXT: deadcode.DeadStores:WarnForDeadNestedAssignments = true // CHECK-NEXT: debug.AnalysisOrder:* = false // CHECK-NEXT: debug.AnalysisOrder:Bind = false // CHECK-NEXT: debug.AnalysisOrder:EndFunction = false @@ -51,6 +55,7 @@ // CHECK-NEXT: experimental-enable-naive-ctu-analysis = false // CHECK-NEXT: exploration_strategy = unexplored_first_queue // CHECK-NEXT: faux-bodies = true +// CHECK-NEXT: fixits-as-remarks = false // CHECK-NEXT: graph-trim-interval = 1000 // CHECK-NEXT: inline-lambdas = true // CHECK-NEXT: ipa = dynamic-bifurcate @@ -71,6 +76,7 @@ // CHECK-NEXT: optin.cplusplus.UninitializedObject:NotesAsWarnings = false // CHECK-NEXT: optin.cplusplus.UninitializedObject:Pedantic = false // CHECK-NEXT: optin.cplusplus.VirtualCall:PureOnly = false +// CHECK-NEXT: optin.cplusplus.VirtualCall:ShowFixIts = false // CHECK-NEXT: optin.osx.cocoa.localizability.NonLocalizedStringChecker:AggressiveReport = false // CHECK-NEXT: optin.performance.Padding:AllowedPad = 24 // CHECK-NEXT: osx.NumberObjectConversion:Pedantic = false @@ -80,12 +86,15 @@ // CHECK-NEXT: region-store-small-struct-limit = 2 // CHECK-NEXT: report-in-main-source-file = false // CHECK-NEXT: serialize-stats = false +// CHECK-NEXT: silence-checkers = "" // CHECK-NEXT: stable-report-filename = false // CHECK-NEXT: suppress-c++-stdlib = true // CHECK-NEXT: suppress-inlined-defensive-checks = true // CHECK-NEXT: suppress-null-return-paths = true +// CHECK-NEXT: track-conditions = true +// CHECK-NEXT: track-conditions-debug = false // CHECK-NEXT: unix.DynamicMemoryModeling:Optimistic = false // CHECK-NEXT: unroll-loops = false // CHECK-NEXT: widen-loops = false // CHECK-NEXT: [stats] -// CHECK-NEXT: num-entries = 85 +// CHECK-NEXT: num-entries = 94 diff --git a/clang/test/Analysis/analyzer-enabled-checkers.c b/clang/test/Analysis/analyzer-enabled-checkers.c index 0ea01a010af63..ba850ec6da48f 100644 --- a/clang/test/Analysis/analyzer-enabled-checkers.c +++ b/clang/test/Analysis/analyzer-enabled-checkers.c @@ -1,20 +1,54 @@ -// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 %s -o /dev/null -analyzer-checker=core -analyzer-list-enabled-checkers > %t 2>&1 -// RUN: FileCheck --input-file=%t %s +// RUN: %clang --analyze %s --target=x86_64-pc-linux-gnu \ +// RUN: -Xclang -analyzer-list-enabled-checkers \ +// RUN: -Xclang -analyzer-display-progress \ +// RUN: 2>&1 | FileCheck %s --implicit-check-not=ANALYZE \ +// RUN: --implicit-check-not=\. -// CHECK: OVERVIEW: Clang Static Analyzer Enabled Checkers List -// CHECK: core.CallAndMessage -// CHECK: core.DivideZero -// CHECK: core.DynamicTypePropagation -// CHECK: core.NonNullParamChecker -// CHECK: core.NullDereference -// CHECK: core.StackAddressEscape -// CHECK: core.UndefinedBinaryOperatorResult -// CHECK: core.VLASize -// CHECK: core.builtin.BuiltinFunctions -// CHECK: core.builtin.NoReturnFunctions -// CHECK: core.uninitialized.ArraySubscript -// CHECK: core.uninitialized.Assign -// CHECK: core.uninitialized.Branch -// CHECK: core.uninitialized.CapturedBlockVariable -// CHECK: core.uninitialized.UndefReturn +// CHECK: OVERVIEW: Clang Static Analyzer Enabled Checkers List +// CHECK-EMPTY: +// CHECK-NEXT: apiModeling.StdCLibraryFunctions +// CHECK-NEXT: apiModeling.TrustNonnull +// CHECK-NEXT: apiModeling.llvm.CastValue +// CHECK-NEXT: apiModeling.llvm.ReturnValue +// CHECK-NEXT: core.CallAndMessage +// CHECK-NEXT: core.DivideZero +// CHECK-NEXT: core.DynamicTypePropagation +// CHECK-NEXT: core.NonNullParamChecker +// CHECK-NEXT: core.NonnilStringConstants +// CHECK-NEXT: core.NullDereference +// CHECK-NEXT: core.StackAddrEscapeBase +// CHECK-NEXT: core.StackAddressEscape +// CHECK-NEXT: core.UndefinedBinaryOperatorResult +// CHECK-NEXT: core.VLASize +// CHECK-NEXT: core.builtin.BuiltinFunctions +// CHECK-NEXT: core.builtin.NoReturnFunctions +// CHECK-NEXT: core.uninitialized.ArraySubscript +// CHECK-NEXT: core.uninitialized.Assign +// CHECK-NEXT: core.uninitialized.Branch +// CHECK-NEXT: core.uninitialized.CapturedBlockVariable +// CHECK-NEXT: core.uninitialized.UndefReturn +// CHECK-NEXT: deadcode.DeadStores +// CHECK-NEXT: nullability.NullabilityBase +// CHECK-NEXT: nullability.NullPassedToNonnull +// CHECK-NEXT: nullability.NullReturnedFromNonnull +// CHECK-NEXT: security.insecureAPI.SecuritySyntaxChecker +// CHECK-NEXT: security.insecureAPI.UncheckedReturn +// CHECK-NEXT: security.insecureAPI.getpw +// CHECK-NEXT: security.insecureAPI.gets +// CHECK-NEXT: security.insecureAPI.mkstemp +// CHECK-NEXT: security.insecureAPI.mktemp +// CHECK-NEXT: security.insecureAPI.vfork +// CHECK-NEXT: unix.API +// CHECK-NEXT: unix.cstring.CStringModeling +// CHECK-NEXT: unix.DynamicMemoryModeling +// CHECK-NEXT: unix.Malloc +// CHECK-NEXT: unix.MallocSizeof +// CHECK-NEXT: unix.MismatchedDeallocator +// CHECK-NEXT: unix.Vfork +// CHECK-NEXT: unix.cstring.BadSizeArg +// CHECK-NEXT: unix.cstring.NullArg +int main() { + int i; + (void)(10 / i); +} diff --git a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp index 7e678a1ec78d4..716eaf3c0fb44 100644 --- a/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp +++ b/clang/test/Analysis/auto-obj-dtors-cfg-output.cpp @@ -334,7 +334,7 @@ void test_aggregate_array_lifetime_extension() { // CHECK-NEXT: 61: ~A() (Temporary object destructor) // CHECK-NEXT: 62: ~A() (Temporary object destructor) // CHECK-NEXT: 63: ~A() (Temporary object destructor) -// CHECK-NEXT: 64: [B1.57].~D() (Implicit destructor) +// CHECK-NEXT: 64: [B1.57].~D [2]() (Implicit destructor) // CHECK-NEXT: 65: [B1.18].~D() (Implicit destructor) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 @@ -363,7 +363,7 @@ void test_aggregate_with_nontrivial_own_destructor() { // WARNINGS-NEXT: 3: (CXXConstructExpr, class A [0]) // ANALYZER-NEXT: 3: (CXXConstructExpr, [B1.4], class A [0]) // CHECK-NEXT: 4: A b[0]; -// CHECK-NEXT: 5: [B1.2].~A() (Implicit destructor) +// CHECK-NEXT: 5: [B1.2].~A [2]() (Implicit destructor) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 // CHECK: [B0 (EXIT)] diff --git a/clang/test/Analysis/cast-value-logic.cpp b/clang/test/Analysis/cast-value-logic.cpp new file mode 100644 index 0000000000000..1411ede92e366 --- /dev/null +++ b/clang/test/Analysis/cast-value-logic.cpp @@ -0,0 +1,162 @@ +// RUN: %clang_analyze_cc1 -std=c++14 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -verify %s + +#include "Inputs/llvm.h" + +void clang_analyzer_numTimesReached(); +void clang_analyzer_warnIfReached(); +void clang_analyzer_eval(bool); + +namespace clang { +struct Shape { + template + const T *castAs() const; + + template + const T *getAs() const; + + virtual double area(); +}; +class Triangle : public Shape {}; +class Circle : public Shape { +public: + ~Circle(); +}; +class SuspiciouslySpecificCircle : public Circle {}; +} // namespace clang + +using namespace llvm; +using namespace clang; + +void test_regions_dyn_cast(const Shape *A, const Shape *B) { + if (dyn_cast(A) && !dyn_cast(B)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +} + +void test_regions_isa(const Shape *A, const Shape *B) { + if (isa(A) && !isa(B)) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} +} + +namespace test_cast { +void evalLogic(const Shape *S) { + const Circle *C = cast(S); + clang_analyzer_numTimesReached(); // expected-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast + +namespace test_dyn_cast { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast(S); + clang_analyzer_numTimesReached(); // expected-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_dyn_cast + +namespace test_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = cast_or_null(S); + clang_analyzer_numTimesReached(); // expected-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_eval(!C); // expected-warning {{TRUE}} +} +} // namespace test_cast_or_null + +namespace test_dyn_cast_or_null { +void evalLogic(const Shape *S) { + const Circle *C = dyn_cast_or_null(S); + clang_analyzer_numTimesReached(); // expected-warning {{3}} + + if (S && C) + clang_analyzer_eval(C == S); // expected-warning {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + + if (!S) + clang_analyzer_eval(!C); // expected-warning {{TRUE}} +} +} // namespace test_dyn_cast_or_null + +namespace test_cast_as { +void evalLogic(const Shape *S) { + const Circle *C = S->castAs(); + clang_analyzer_numTimesReached(); // expected-warning {{1}} + + if (S && C) + clang_analyzer_eval(C == S); + // expected-warning@-1 {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // no-warning + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_cast_as + +namespace test_get_as { +void evalLogic(const Shape *S) { + const Circle *C = S->getAs(); + clang_analyzer_numTimesReached(); // expected-warning {{2}} + + if (S && C) + clang_analyzer_eval(C == S); + // expected-warning@-1 {{TRUE}} + + if (S && !C) + clang_analyzer_warnIfReached(); // expected-warning {{REACHABLE}} + + if (!S) + clang_analyzer_warnIfReached(); // no-warning +} +} // namespace test_get_as + +namespace crashes { +void test_non_reference_null_region_crash(Shape s) { + cast(s); // no-crash +} + +void test_non_reference_temporary_crash() { + extern std::unique_ptr foo(); + auto P = foo(); + auto Q = cast(std::move(P)); // no-crash +} + +double test_virtual_method_after_call(Shape *S) { + if (isa(S)) + return S->area(); + return S->area() / 2; +} + +void test_delete_crash() { + extern Circle *makeCircle(); + Shape *S = makeCircle(); + delete cast(S); +} +} // namespace crashes diff --git a/clang/test/Analysis/cast-value-notes.cpp b/clang/test/Analysis/cast-value-notes.cpp new file mode 100644 index 0000000000000..eb5d1b3d3fe27 --- /dev/null +++ b/clang/test/Analysis/cast-value-notes.cpp @@ -0,0 +1,155 @@ +// RUN: %clang_analyze_cc1 -std=c++14 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -analyzer-output=text -verify %s + +#include "Inputs/llvm.h" + +namespace clang { +struct Shape { + template + const T *castAs() const; + + template + const T *getAs() const; +}; +class Triangle : public Shape {}; +class Circle : public Shape {}; +} // namespace clang + +using namespace llvm; +using namespace clang; + +void evalReferences(const Shape &S) { + const auto &C = dyn_cast(S); + // expected-note@-1 {{Assuming 'S' is not a 'Circle'}} + // expected-note@-2 {{Dereference of null pointer}} + // expected-warning@-3 {{Dereference of null pointer}} +} + +void evalNonNullParamNonNullReturnReference(const Shape &S) { + // Unmodeled cast from reference to pointer. + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{'C' initialized here}} + + if (!dyn_cast_or_null(C)) { + // expected-note@-1 {{'C' is a 'Circle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (dyn_cast_or_null(C)) { + // expected-note@-1 {{Assuming 'C' is not a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (isa(C)) { + // expected-note@-1 {{'C' is not a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (isa(C)) { + // expected-note@-1 {{'C' is a 'Circle'}} + // expected-note@-2 {{Taking true branch}} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + } +} + +void evalNonNullParamNonNullReturn(const Shape *S) { + const auto *C = cast(S); + // expected-note@-1 {{'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + if (!isa(C)) { + // expected-note@-1 {{Assuming 'C' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (!isa(C)) { + // expected-note@-1 {{'C' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + +void evalNonNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming 'S' is not a 'Circle'}} + + if (const auto *T = dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming 'S' is a 'Triangle'}} + // expected-note@-2 {{'T' initialized here}} + // expected-note@-3 {{'T' is non-null}} + // expected-note@-4 {{Taking true branch}} + + (void)(1 / !T); + // expected-note@-1 {{'T' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} + } +} + +void evalNullParamNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming null pointer is passed into cast}} + // expected-note@-2 {{'C' initialized to a null pointer value}} + + (void)(1 / (bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} +} + +void evalZeroParamNonNullReturnPointer(const Shape *S) { + const auto *C = S->castAs(); + // expected-note@-1 {{'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + +void evalZeroParamNonNullReturn(const Shape &S) { + const auto *C = S.castAs(); + // expected-note@-1 {{'C' initialized here}} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + +void evalZeroParamNullReturn(const Shape *S) { + const auto &C = S->getAs(); + // expected-note@-1 {{Assuming 'S' is not a 'Circle'}} + // expected-note@-2 {{Storing null pointer value}} + // expected-note@-3 {{'C' initialized here}} + + if (!dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming 'S' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + if (!dyn_cast_or_null(S)) { + // expected-note@-1 {{'S' is a 'Triangle'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + (void)(1 / (bool)C); + // expected-note@-1 {{Division by zero}} + // expected-warning@-2 {{Division by zero}} +} diff --git a/clang/test/Analysis/cast-value-state-dump.cpp b/clang/test/Analysis/cast-value-state-dump.cpp new file mode 100644 index 0000000000000..9abdaae0d4592 --- /dev/null +++ b/clang/test/Analysis/cast-value-state-dump.cpp @@ -0,0 +1,47 @@ +// RUN: %clang_analyze_cc1 -std=c++14 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.CastValue,debug.ExprInspection\ +// RUN: -analyzer-output=text -verify %s 2>&1 | FileCheck %s + +#include "Inputs/llvm.h" + +void clang_analyzer_printState(); + +namespace clang { +struct Shape {}; +class Triangle : public Shape {}; +class Circle : public Shape {}; +class Square : public Shape {}; +} // namespace clang + +using namespace llvm; +using namespace clang; + +void evalNonNullParamNonNullReturn(const Shape *S) { + const auto *C = dyn_cast_or_null(S); + // expected-note@-1 {{Assuming 'S' is a 'Circle'}} + // expected-note@-2 {{'C' initialized here}} + + // FIXME: We assumed that 'S' is a 'Circle' therefore it is not a 'Square'. + if (dyn_cast_or_null(S)) { + // expected-note@-1 {{Assuming 'S' is not a 'Square'}} + // expected-note@-2 {{Taking false branch}} + return; + } + + clang_analyzer_printState(); + + // CHECK: "dynamic_types": [ + // CHECK-NEXT: { "region": "SymRegion{reg_$0}", "dyn_type": "const class clang::Circle", "sub_classable": true } + // CHECK-NEXT: ], + // CHECK-NEXT: "dynamic_casts": [ + // CHECK: { "region": "SymRegion{reg_$0}", "casts": [ + // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Circle *", "kind": "success" }, + // CHECK-NEXT: { "from": "const struct clang::Shape *", "to": "const class clang::Square *", "kind": "fail" } + // CHECK-NEXT: ]} + + (void)(1 / !C); + // expected-note@-1 {{'C' is non-null}} + // expected-note@-2 {{Division by zero}} + // expected-warning@-3 {{Division by zero}} +} + diff --git a/clang/test/Analysis/cast-value-weird.cpp b/clang/test/Analysis/cast-value-weird.cpp new file mode 100644 index 0000000000000..f15cc19c4477f --- /dev/null +++ b/clang/test/Analysis/cast-value-weird.cpp @@ -0,0 +1,9 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,apiModeling -verify %s + +// expected-no-diagnostics + +namespace llvm { +template +void cast(...); +void a() { cast(int()); } // no-crash +} // namespace llvm diff --git a/clang/test/Analysis/cfg-rich-constructors.cpp b/clang/test/Analysis/cfg-rich-constructors.cpp index 0125c9bf52325..e33e57597d9e4 100644 --- a/clang/test/Analysis/cfg-rich-constructors.cpp +++ b/clang/test/Analysis/cfg-rich-constructors.cpp @@ -412,7 +412,6 @@ class D { ~D(); }; -// FIXME: There should be no temporary destructor in C++17. // CHECK: return_stmt_with_dtor::D returnTemporary() // CXX11-ELIDE: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.2], [B1.4], [B1.5], class return_stmt_with_dtor::D) // CXX11-NOELIDE: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.2], [B1.4], class return_stmt_with_dtor::D) @@ -422,15 +421,13 @@ class D { // CXX11-NEXT: 5: [B1.4] (CXXConstructExpr, [B1.7], class return_stmt_with_dtor::D) // CXX11-NEXT: 6: ~return_stmt_with_dtor::D() (Temporary object destructor) // CXX11-NEXT: 7: return [B1.5]; -// CXX17: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.4], [B1.2], class return_stmt_w +// CXX17: 1: return_stmt_with_dtor::D() (CXXConstructExpr, [B1.3], [B1.2], class return_stmt_w // CXX17-NEXT: 2: [B1.1] (BindTemporary) -// CXX17-NEXT: 3: ~return_stmt_with_dtor::D() (Temporary object destructor) -// CXX17-NEXT: 4: return [B1.2]; +// CXX17-NEXT: 3: return [B1.2]; D returnTemporary() { return D(); } -// FIXME: There should be no temporary destructor in C++17. // CHECK: void returnByValueIntoVariable() // CHECK: 1: returnTemporary // CHECK-NEXT: 2: [B1.1] (ImplicitCastExpr, FunctionToPointerDecay, class return_stmt_with_dtor::D (*)(void)) @@ -442,12 +439,10 @@ D returnTemporary() { // CXX11-NEXT: 7: [B1.6] (CXXConstructExpr, [B1.8], class return_stmt_with_dtor::D) // CXX11-NEXT: 8: return_stmt_with_dtor::D d = returnTemporary(); // CXX11-NEXT: 9: ~return_stmt_with_dtor::D() (Temporary object destructor) -// CXX11-NEXT: 10: [B1.8].~D() (Implicit destructor) +// CXX11-NEXT: 10: [B1.8].~return_stmt_with_dtor::D() (Implicit destructor) // CXX17-NEXT: 3: [B1.2]() (CXXRecordTypedCall, [B1.5], [B1.4]) // CXX17-NEXT: 4: [B1.3] (BindTemporary) // CXX17-NEXT: 5: return_stmt_with_dtor::D d = returnTemporary(); -// CXX17-NEXT: 6: ~return_stmt_with_dtor::D() (Temporary object destructor) -// CXX17-NEXT: 7: [B1.5].~D() (Implicit destructor) void returnByValueIntoVariable() { D d = returnTemporary(); } @@ -459,7 +454,7 @@ namespace temporary_object_expr_without_dtors { // TODO: Should provide construction context for the constructor, // even if there is no specific trigger statement here. // CHECK: void simpleTemporary() -// CHECK 1: C() (CXXConstructExpr, class C) +// CHECK: 1: C() (CXXConstructExpr, class C) void simpleTemporary() { C(); } @@ -602,7 +597,7 @@ void temporaryInCondition() { // CHECK-NEXT: 3: [B1.2] (BindTemporary) // CHECK-NEXT: 4: [B1.3] // CHECK-NEXT: 5: const temporary_object_expr_with_dtors::D &d(0); -// CHECK-NEXT: 6: [B1.5].~D() (Implicit destructor) +// CHECK-NEXT: 6: [B1.5].~temporary_object_expr_with_dtors::D() (Implicit destructor) void referenceVariableWithConstructor() { const D &d(0); } @@ -613,14 +608,14 @@ void referenceVariableWithConstructor() { // CHECK-NEXT: 3: [B1.2] (ImplicitCastExpr, NoOp, const class temporary_object_expr_with_dtors::D) // CHECK-NEXT: 4: [B1.3] // CHECK-NEXT: 5: const temporary_object_expr_with_dtors::D &d = temporary_object_expr_with_dtors::D(); -// CHECK-NEXT: 6: [B1.5].~D() (Implicit destructor) +// CHECK-NEXT: 6: [B1.5].~temporary_object_expr_with_dtors::D() (Implicit destructor) void referenceVariableWithInitializer() { const D &d = D(); } // CHECK: void referenceVariableWithTernaryOperator(bool coin) // CXX11: [B1] -// CXX11-NEXT: 1: [B4.4].~D() (Implicit destructor) +// CXX11-NEXT: 1: [B4.4].~temporary_object_expr_with_dtors::D() (Implicit destructor) // CXX11: [B2] // CXX11-NEXT: 1: ~temporary_object_expr_with_dtors::D() (Temporary object destructor) // CXX11: [B3] @@ -660,7 +655,7 @@ void referenceVariableWithInitializer() { // CXX17-NEXT: 2: [B1.1] (ImplicitCastExpr, NoOp, const class temporary_object_expr_with_dtors::D) // CXX17-NEXT: 3: [B1.2] // CXX17-NEXT: 4: const temporary_object_expr_with_dtors::D &d = coin ? D::get() : temporary_object_expr_with_dtors::D(0); -// CXX17-NEXT: 5: [B1.4].~D() (Implicit destructor) +// CXX17-NEXT: 5: [B1.4].~temporary_object_expr_with_dtors::D() (Implicit destructor) // CXX17: [B2] // CXX17-NEXT: 1: D::get // CXX17-NEXT: 2: [B2.1] (ImplicitCastExpr, FunctionToPointerDecay, class temporary_object_expr_with_dtors::D (*)(void)) @@ -686,7 +681,7 @@ void referenceVariableWithTernaryOperator(bool coin) { // CHECK-NEXT: 4: temporary_object_expr_with_dtors::D([B1.3]) (CXXFunctionalCastExpr, ConstructorCon // CHECK-NEXT: 5: [B1.4] // CHECK-NEXT: 6: temporary_object_expr_with_dtors::D &&d = temporary_object_expr_with_dtors::D(1); -// CHECK-NEXT: 7: [B1.6].~D() (Implicit destructor) +// CHECK-NEXT: 7: [B1.6].~temporary_object_expr_with_dtors::D() (Implicit destructor) void referenceWithFunctionalCast() { D &&d = D(1); } @@ -743,13 +738,13 @@ class B { // CXX11-NEXT: 9: [B1.8] (CXXConstructExpr, [B1.10], class implicit_constructor_conversion::B) // CXX11-NEXT: 10: implicit_constructor_conversion::B b = implicit_constructor_conversion::A(); // CXX11-NEXT: 11: ~implicit_constructor_conversion::B() (Temporary object destructor) -// CXX11-NEXT: 12: [B1.10].~B() (Implicit destructor) +// CXX11-NEXT: 12: [B1.10].~implicit_constructor_conversion::B() (Implicit destructor) // CXX17-NEXT: 2: [B1.1] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::A) // CXX17-NEXT: 3: [B1.2] // CXX17-NEXT: 4: [B1.3] (CXXConstructExpr, [B1.6], class implicit_constructor_conversion::B) // CXX17-NEXT: 5: [B1.4] (ImplicitCastExpr, ConstructorConversion, class implicit_constructor_conversion::B) // CXX17-NEXT: 6: implicit_constructor_conversion::B b = implicit_constructor_conversion::A(); -// CXX17-NEXT: 7: [B1.6].~B() (Implicit destructor) +// CXX17-NEXT: 7: [B1.6].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromTemporary() { B b = A(); } @@ -769,11 +764,11 @@ void implicitConstructionConversionFromTemporary() { // CXX11-NEXT: 11: [B1.10] (CXXConstructExpr, [B1.12], class implicit_constructor_conversion::B) // CXX11-NEXT: 12: implicit_constructor_conversion::B b = get(); // CXX11-NEXT: 13: ~implicit_constructor_conversion::B() (Temporary object destructor) -// CXX11-NEXT: 14: [B1.12].~B() (Implicit destructor) +// CXX11-NEXT: 14: [B1.12].~implicit_constructor_conversion::B() (Implicit destructor) // CXX17-NEXT: 6: [B1.5] (CXXConstructExpr, [B1.8], class implicit_constructor_conversion::B) // CXX17-NEXT: 7: [B1.6] (ImplicitCastExpr, ConstructorConversion, class implicit_constructor_conversion::B) // CXX17-NEXT: 8: implicit_constructor_conversion::B b = get(); -// CXX17-NEXT: 9: [B1.8].~B() (Implicit destructor) +// CXX17-NEXT: 9: [B1.8].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromFunctionValue() { B b = get(); } @@ -787,7 +782,7 @@ void implicitConstructionConversionFromFunctionValue() { // CHECK-NEXT: 6: [B1.5] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::B) // CHECK-NEXT: 7: [B1.6] // CHECK-NEXT: 8: const implicit_constructor_conversion::B &b = implicit_constructor_conversion::A(); -// CHECK-NEXT: 9: [B1.8].~B() (Implicit destructor) +// CHECK-NEXT: 9: [B1.8].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromTemporaryWithLifetimeExtension() { const B &b = A(); } @@ -803,7 +798,7 @@ void implicitConstructionConversionFromTemporaryWithLifetimeExtension() { // CHECK-NEXT: 8: [B1.7] (ImplicitCastExpr, NoOp, const class implicit_constructor_conversion::B) // CHECK-NEXT: 9: [B1.8] // CHECK-NEXT: 10: const implicit_constructor_conversion::B &b = get(); -// CHECK-NEXT: 11: [B1.10].~B() (Implicit destructor) +// CHECK-NEXT: 11: [B1.10].~implicit_constructor_conversion::B() (Implicit destructor) void implicitConstructionConversionFromFunctionValueWithLifetimeExtension() { const B &b = get(); // no-crash } diff --git a/clang/test/Analysis/cfg-rich-constructors.mm b/clang/test/Analysis/cfg-rich-constructors.mm index 289094293eb5c..e55928d4c51fa 100644 --- a/clang/test/Analysis/cfg-rich-constructors.mm +++ b/clang/test/Analysis/cfg-rich-constructors.mm @@ -59,8 +59,7 @@ void passArgumentIntoMessage(E *e) { // CXX17-NEXT: 3: {{\[}}[B1.2] bar] (CXXRecordTypedCall, [B1.5], [B1.4]) // CXX17-NEXT: 4: [B1.3] (BindTemporary) // CXX17-NEXT: 5: D d = [e bar]; -// CXX17-NEXT: 6: ~D() (Temporary object destructor) -// CXX17-NEXT: 7: [B1.5].~D() (Implicit destructor) +// CXX17-NEXT: 6: [B1.5].~D() (Implicit destructor) void returnObjectFromMessage(E *e) { D d = [e bar]; } diff --git a/clang/test/Analysis/cfg.cpp b/clang/test/Analysis/cfg.cpp index ea028e06f3471..9b0203e99efe9 100644 --- a/clang/test/Analysis/cfg.cpp +++ b/clang/test/Analysis/cfg.cpp @@ -203,7 +203,7 @@ namespace NoReturnSingleSuccessor { // CHECK-LABEL: int test1(int *x) // CHECK: 1: 1 // CHECK-NEXT: 2: return -// CHECK-NEXT: ~B() (Implicit destructor) +// CHECK-NEXT: ~NoReturnSingleSuccessor::B() (Implicit destructor) // CHECK-NEXT: Preds (1) // CHECK-NEXT: Succs (1): B0 int test1(int *x) { @@ -477,7 +477,7 @@ void test_lifetime_extended_temporaries() { // WARNINGS-NEXT: 1: (CXXConstructExpr, struct pr37688_deleted_union_destructor::A) // ANALYZER-NEXT: 1: (CXXConstructExpr, [B1.2], struct pr37688_deleted_union_destructor::A) // CHECK-NEXT: 2: pr37688_deleted_union_destructor::A a; -// CHECK-NEXT: 3: [B1.2].~A() (Implicit destructor) +// CHECK-NEXT: 3: [B1.2].~pr37688_deleted_union_destructor::A() (Implicit destructor) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 // CHECK: [B0 (EXIT)] @@ -499,6 +499,54 @@ void foo() { } // end namespace pr37688_deleted_union_destructor +namespace return_statement_expression { +int unknown(); + +// CHECK-LABEL: int foo() +// CHECK: [B6 (ENTRY)] +// CHECK-NEXT: Succs (1): B5 +// CHECK: [B1] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: return [B1.1]; +// CHECK-NEXT: Preds (1): B5 +// CHECK-NEXT: Succs (1): B0 +// CHECK: [B2] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: ({ ... ; [B2.1] }) +// CHECK-NEXT: 3: return [B2.2]; +// CHECK-NEXT: Preds (1): B4 +// CHECK-NEXT: Succs (1): B0 +// FIXME: Why do we have [B3] at all? +// CHECK: [B3] +// CHECK-NEXT: Succs (1): B4 +// CHECK: [B4] +// CHECK-NEXT: 1: 0 +// CHECK-NEXT: 2: [B4.1] (ImplicitCastExpr, IntegralToBoolean, _Bool) +// CHECK-NEXT: T: while [B4.2] +// CHECK-NEXT: Preds (2): B3 B5 +// CHECK-NEXT: Succs (2): NULL B2 +// CHECK: [B5] +// CHECK-NEXT: 1: unknown +// CHECK-NEXT: 2: [B5.1] (ImplicitCastExpr, FunctionToPointerDecay, int (*)(void)) +// CHECK-NEXT: 3: [B5.2]() +// CHECK-NEXT: 4: [B5.3] (ImplicitCastExpr, IntegralToBoolean, _Bool) +// CHECK-NEXT: T: if [B5.4] +// CHECK-NEXT: Preds (1): B6 +// CHECK-NEXT: Succs (2): B4 B1 +// CHECK: [B0 (EXIT)] +// CHECK-NEXT: Preds (2): B1 B2 +int foo() { + if (unknown()) + return ({ + while (0) + ; + 0; + }); + else + return 0; +} +} // namespace statement_expression_in_return + // CHECK-LABEL: template<> int *PR18472() // CHECK: [B2 (ENTRY)] // CHECK-NEXT: Succs (1): B1 @@ -522,4 +570,3 @@ template T *PR18472() { void PR18472_helper() { PR18472(); } - diff --git a/clang/test/Analysis/checker-plugins.c b/clang/test/Analysis/checker-plugins.c index b5444fa6cbf7f..fbc9c9bd1c22b 100644 --- a/clang/test/Analysis/checker-plugins.c +++ b/clang/test/Analysis/checker-plugins.c @@ -1,5 +1,9 @@ // REQUIRES: plugins +// FIXME: This test fails on clang-stage2-cmake-RgSan, +// see also https://reviews.llvm.org/D62445#1613268 +// UNSUPPORTED: darwin + // RUN: %clang_analyze_cc1 -verify %s \ // RUN: -load %llvmshlibdir/SampleAnalyzerPlugin%pluginext \ // RUN: -analyzer-checker='example.MainCallChecker' diff --git a/clang/test/Analysis/cstring-syntax-weird.c b/clang/test/Analysis/cstring-syntax-weird.c new file mode 100644 index 0000000000000..9a58f1609f7ae --- /dev/null +++ b/clang/test/Analysis/cstring-syntax-weird.c @@ -0,0 +1,19 @@ +// RUN: %clang_analyze_cc1 -w -analyzer-checker=unix.cstring.BadSizeArg \ +// RUN: -verify %s + +// expected-no-diagnostics + +typedef __SIZE_TYPE__ size_t; +// The last parameter is normally size_t but the test is about the abnormal +// situation when it's not a size_t. +size_t strlcpy(char *, const char *, int); + +enum WeirdDecl { + AStrangeWayToSpecifyStringLengthCorrectly = 10UL, + AStrangeWayToSpecifyStringLengthIncorrectly = 5UL +}; +void testWeirdDecls(const char *src) { + char dst[10]; + strlcpy(dst, src, AStrangeWayToSpecifyStringLengthCorrectly); // no-crash + strlcpy(dst, src, AStrangeWayToSpecifyStringLengthIncorrectly); // no-crash // no-warning +} diff --git a/clang/test/Analysis/cstring-syntax-weird2.c b/clang/test/Analysis/cstring-syntax-weird2.c new file mode 100644 index 0000000000000..a0f28536d4a38 --- /dev/null +++ b/clang/test/Analysis/cstring-syntax-weird2.c @@ -0,0 +1,17 @@ +// RUN: %clang_analyze_cc1 -w -analyzer-checker=unix.cstring.BadSizeArg \ +// RUN: -verify %s + +// expected-no-diagnostics + +typedef __SIZE_TYPE__ size_t; +// The last parameter is normally size_t but the test is about the abnormal +// situation when it's not a size_t. +size_t strlcpy(char *, const char *, void (*)()); + +void foo(); + +void testWeirdDecls(const char *src) { + char dst[10]; + strlcpy(dst, src, foo); // no-crash + strlcpy(dst, src, &foo); // no-crash +} diff --git a/clang/test/Analysis/cstring-syntax.c b/clang/test/Analysis/cstring-syntax.c index f01de36c1afb8..8ac971fe24127 100644 --- a/clang/test/Analysis/cstring-syntax.c +++ b/clang/test/Analysis/cstring-syntax.c @@ -1,7 +1,18 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s -// RUN: %clang_analyze_cc1 -triple armv7-a15-linux -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s -// RUN: %clang_analyze_cc1 -triple aarch64_be-none-linux-gnu -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s -// RUN: %clang_analyze_cc1 -triple i386-apple-darwin10 -analyzer-checker=unix.cstring.BadSizeArg -analyzer-store=region -Wno-strncat-size -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument -Wno-sizeof-pointer-memaccess -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument\ +// RUN: -triple armv7-a15-linux +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument\ +// RUN: -triple aarch64_be-none-linux-gnu +// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.BadSizeArg -verify %s\ +// RUN: -Wno-strncat-size -Wno-sizeof-pointer-memaccess \ +// RUN: -Wno-strlcpy-strlcat-size -Wno-sizeof-array-argument\ +// RUN: -triple i386-apple-darwin10 typedef __SIZE_TYPE__ size_t; char *strncat(char *, const char *, size_t); diff --git a/clang/test/Analysis/ctu-different-triples.cpp b/clang/test/Analysis/ctu-different-triples.cpp index dbfa82fb483d9..20acc318e2e72 100644 --- a/clang/test/Analysis/ctu-different-triples.cpp +++ b/clang/test/Analysis/ctu-different-triples.cpp @@ -1,9 +1,9 @@ // RUN: rm -rf %t && mkdir %t // RUN: mkdir -p %t/ctudir -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp // RUN: cp %S/Inputs/ctu-other.cpp.externalDefMap.txt %t/ctudir/externalDefMap.txt -// RUN: %clang_analyze_cc1 -triple powerpc64-montavista-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple powerpc64-montavista-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ diff --git a/clang/test/Analysis/ctu-import-threshold.c b/clang/test/Analysis/ctu-import-threshold.c new file mode 100644 index 0000000000000..82711b873d106 --- /dev/null +++ b/clang/test/Analysis/ctu-import-threshold.c @@ -0,0 +1,5 @@ +// Ensure analyzer option 'ctu-import-threshold' is a recognized option. +// +// RUN: %clang_cc1 -analyze -analyzer-config ctu-import-threshold=30 -verify %s +// +// expected-no-diagnostics diff --git a/clang/test/Analysis/ctu-main.cpp b/clang/test/Analysis/ctu-main.cpp index a5de18bb3ec62..0bddeee81e677 100644 --- a/clang/test/Analysis/ctu-main.cpp +++ b/clang/test/Analysis/ctu-main.cpp @@ -1,16 +1,16 @@ // RUN: rm -rf %t && mkdir %t // RUN: mkdir -p %t/ctudir -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-chain.cpp.ast %S/Inputs/ctu-chain.cpp // RUN: cp %S/Inputs/ctu-other.cpp.externalDefMap.txt %t/ctudir/externalDefMap.txt -// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ // RUN: -verify %s -// RUN: %clang_analyze_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ @@ -45,6 +45,7 @@ class embed_cls { class mycls { public: int fcl(int x); + virtual int fvcl(int x); static int fscl(int x); class embed_cls2 { @@ -53,6 +54,11 @@ class mycls { }; }; +class derived : public mycls { +public: + virtual int fvcl(int x) override; +}; + namespace chns { int chf1(int x); } @@ -98,6 +104,14 @@ union U { }; extern U extU; +void test_virtual_functions(mycls* obj) { + // The dynamic type is known. + clang_analyzer_eval(mycls().fvcl(1) == 8); // expected-warning{{TRUE}} + clang_analyzer_eval(derived().fvcl(1) == 9); // expected-warning{{TRUE}} + // We cannot decide about the dynamic type. + clang_analyzer_eval(obj->fvcl(1) == 8); // expected-warning{{FALSE}} expected-warning{{TRUE}} +} + int main() { clang_analyzer_eval(f(3) == 2); // expected-warning{{TRUE}} clang_analyzer_eval(f(4) == 3); // expected-warning{{TRUE}} @@ -116,7 +130,7 @@ int main() { clang_analyzer_eval(fun_using_anon_struct(8) == 8); // expected-warning{{TRUE}} clang_analyzer_eval(other_macro_diag(1) == 1); // expected-warning{{TRUE}} - // expected-warning@Inputs/ctu-other.cpp:80{{REACHABLE}} + // expected-warning@Inputs/ctu-other.cpp:93{{REACHABLE}} MACRODIAG(); // expected-warning{{REACHABLE}} clang_analyzer_eval(extInt == 2); // expected-warning{{TRUE}} diff --git a/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp b/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp index 5e643c164dd7d..6bcbd709b5ef7 100644 --- a/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp +++ b/clang/test/Analysis/ctu-unknown-parts-in-triples.cpp @@ -3,10 +3,10 @@ // RUN: rm -rf %t && mkdir %t // RUN: mkdir -p %t/ctudir -// RUN: %clang_cc1 -triple x86_64-pc-linux-gnu \ +// RUN: %clang_cc1 -std=c++14 -triple x86_64-pc-linux-gnu \ // RUN: -emit-pch -o %t/ctudir/ctu-other.cpp.ast %S/Inputs/ctu-other.cpp // RUN: cp %S/Inputs/ctu-other.cpp.externalDefMap.txt %t/ctudir/externalDefMap.txt -// RUN: %clang_analyze_cc1 -triple x86_64-unknown-linux-gnu \ +// RUN: %clang_analyze_cc1 -std=c++14 -triple x86_64-unknown-linux-gnu \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ // RUN: -analyzer-config ctu-dir=%t/ctudir \ diff --git a/clang/test/Analysis/cxx-callgraph.cpp b/clang/test/Analysis/cxx-callgraph.cpp new file mode 100644 index 0000000000000..4a48e422ce08a --- /dev/null +++ b/clang/test/Analysis/cxx-callgraph.cpp @@ -0,0 +1,29 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCallGraph %s 2>&1 | FileCheck %s + +static int aaa() { + return 0; +} + +static int bbb(int param=aaa()) { + return 1; +} + +int ddd(); + +struct c { + c(int param=2) : val(bbb(param)) {} + int val; + int val2 = ddd(); +}; + +int ddd() { + c c; + return bbb(); +} + +// CHECK:--- Call graph Dump --- +// CHECK-NEXT: {{Function: < root > calls: aaa bbb c::c ddd}} +// CHECK-NEXT: {{Function: c::c calls: bbb ddd $}} +// CHECK-NEXT: {{Function: ddd calls: c::c bbb aaa $}} +// CHECK-NEXT: {{Function: bbb calls: $}} +// CHECK-NEXT: {{Function: aaa calls: $}} diff --git a/clang/test/Analysis/dead-stores.c b/clang/test/Analysis/dead-stores.c index 84217a286a551..cbfdabdcec632 100644 --- a/clang/test/Analysis/dead-stores.c +++ b/clang/test/Analysis/dead-stores.c @@ -1,102 +1,112 @@ -// RUN: %clang_analyze_cc1 -Wunused-variable -analyzer-checker=core,deadcode.DeadStores -fblocks -verify -Wno-unreachable-code -analyzer-opt-analyze-nested-blocks %s -// RUN: %clang_analyze_cc1 -Wunused-variable -analyzer-checker=core,deadcode.DeadStores -analyzer-store=region -fblocks -verify -Wno-unreachable-code -analyzer-opt-analyze-nested-blocks %s +// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ +// RUN: -analyzer-checker=core,deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:ShowFixIts=true \ +// RUN: -analyzer-config fixits-as-remarks=true \ +// RUN: -analyzer-config \ +// RUN: deadcode.DeadStores:WarnForDeadNestedAssignments=false \ +// RUN: -verify=non-nested %s + +// RUN: %clang_analyze_cc1 -Wunused-variable -fblocks -Wno-unreachable-code \ +// RUN: -analyzer-checker=core,deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:ShowFixIts=true \ +// RUN: -analyzer-config fixits-as-remarks=true \ +// RUN: -verify=non-nested,nested %s void f1() { - int k, y; // expected-warning{{unused variable 'k'}} expected-warning{{unused variable 'y'}} - int abc=1; - long idx=abc+3*5; // expected-warning {{never read}} expected-warning{{unused variable 'idx'}} + int k, y; // non-nested-warning {{unused variable 'k'}} + // non-nested-warning@-1 {{unused variable 'y'}} + int abc = 1; + long idx = abc + 3 * 5; // non-nested-warning {{never read}} + // non-nested-warning@-1 {{unused variable 'idx'}} + // non-nested-remark@-2 {{11-24: ''}} } void f2(void *b) { - char *c = (char*)b; // no-warning - char *d = b+1; // expected-warning {{never read}} expected-warning{{unused variable 'd'}} - printf("%s", c); // expected-warning{{implicitly declaring library function 'printf' with type 'int (const char *, ...)'}} \ - // expected-note{{include the header or explicitly provide a declaration for 'printf'}} + char *c = (char *)b; // no-warning + char *d = b + 1; // non-nested-warning {{never read}} + // non-nested-warning@-1 {{unused variable 'd'}} + // non-nested-remark@-2 {{10-17: ''}} + printf("%s", c); + // non-nested-warning@-1 {{implicitly declaring library function 'printf' with type 'int (const char *, ...)'}} + // non-nested-note@-2 {{include the header or explicitly provide a declaration for 'printf'}} } int f(); - void f3() { int r; if ((r = f()) != 0) { // no-warning - int y = r; // no-warning + int y = r; // no-warning printf("the error is: %d\n", y); } } void f4(int k) { - k = 1; - if (k) f1(); - - k = 2; // expected-warning {{never read}} + k = 2; // non-nested-warning {{never read}} } - -void f5() { - - int x = 4; // no-warning - int *p = &x; // expected-warning{{never read}} expected-warning{{unused variable 'p'}} +void f5() { + int x = 4; // no-warning + int *p = &x; // non-nested-warning {{never read}} + // non-nested-warning@-1 {{unused variable 'p'}} + // non-nested-remark@-2 {{9-13: ''}} } -// int f6() { - int x = 4; ++x; // no-warning return 1; } -int f7(int *p) { +int f7(int *p) { // This is allowed for defensive programming. - p = 0; // no-warning + p = 0; // no-warning return 1; } -int f7b(int *p) { +int f7b(int *p) { // This is allowed for defensive programming. - p = (0); // no-warning + p = (0); // no-warning return 1; } -int f7c(int *p) { +int f7c(int *p) { // This is allowed for defensive programming. - p = (void*) 0; // no-warning + p = (void *)0; // no-warning return 1; } -int f7d(int *p) { +int f7d(int *p) { // This is allowed for defensive programming. - p = (void*) (0); // no-warning + p = (void *)(0); // no-warning return 1; } -// Don't warn for dead stores in nested expressions. We have yet -// to see a real bug in this scenario. +// Warn for dead stores in nested expressions. int f8(int *p) { extern int *baz(); - if ((p = baz())) // no-warning + if ((p = baz())) // nested-warning {{Although the value stored}} return 1; return 0; } int f9() { int x = 4; - x = x + 10; // expected-warning{{never read}} + x = x + 10; // non-nested-warning {{never read}} return 1; } int f10() { int x = 4; - x = 10 + x; // expected-warning{{never read}} + x = 10 + x; // non-nested-warning {{never read}} return 1; } int f11() { int x = 4; - return x++; // expected-warning{{never read}} + return x++; // non-nested-warning {{never read}} } int f11b() { @@ -105,38 +115,38 @@ int f11b() { } int f12a(int y) { - int x = y; // expected-warning{{unused variable 'x'}} + int x = y; // non-nested-warning {{unused variable 'x'}} return 1; } + int f12b(int y) { - int x __attribute__((unused)) = y; // no-warning + int x __attribute__((unused)) = y; // no-warning return 1; } + int f12c(int y) { // Allow initialiation of scalar variables by parameters as a form of // defensive programming. - int x = y; // no-warning + int x = y; // no-warning x = 1; return x; } // Filed with PR 2630. This code should produce no warnings. -int f13(void) -{ +int f13(void) { int a = 1; int b, c = b = a + a; if (b > 0) return (0); - return (a + b + c); } // Filed with PR 2763. int f14(int count) { int index, nextLineIndex; - for (index = 0; index < count; index = nextLineIndex+1) { - nextLineIndex = index+1; // no-warning + for (index = 0; index < count; index = nextLineIndex + 1) { + nextLineIndex = index + 1; // no-warning continue; } return index; @@ -144,16 +154,15 @@ int f14(int count) { // Test case for void f15(unsigned x, unsigned y) { - int count = x * y; // no-warning - int z[count]; // expected-warning{{unused variable 'z'}} + int count = x * y; // no-warning + int z[count]; // non-nested-warning {{unused variable 'z'}} } -// Don't warn for dead stores in nested expressions. We have yet -// to see a real bug in this scenario. +// Warn for dead stores in nested expressions. int f16(int x) { x = x * 2; - x = sizeof(int [x = (x || x + 1) * 2]) - ? 5 : 8; + x = sizeof(int[x = (x || x + 1) * 2]) ? 5 : 8; + // nested-warning@-1 {{Although the value stored}} return x; } @@ -168,39 +177,39 @@ void f17() { // what that value is actually used. In other words, don't say "Although the // value stored to 'x' is used...". int f18() { - int x = 0; // no-warning - if (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} - while (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} - // unreachable. - do - x = 10; // no-warning - while (1); - return (x = 10); // no-warning + int x = 0; // no-warning + if (1) + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} + while (1) + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} + // unreachable. + do + x = 10; // no-warning + while (1); + return (x = 10); // no-warning } int f18_a() { - int x = 0; // no-warning - return (x = 10); // no-warning + int x = 0; // no-warning + return (x = 10); // nested-warning {{Although the value stored}} } void f18_b() { - int x = 0; // no-warning - if (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} + int x = 0; // no-warning + if (1) + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} } void f18_c() { int x = 0; while (1) - x = 10; // expected-warning{{Value stored to 'x' is never read}} + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} } void f18_d() { int x = 0; // no-warning do - x = 10; // expected-warning{{Value stored to 'x' is never read}} + x = 10; // non-nested-warning {{Value stored to 'x' is never read}} while (1); } @@ -208,7 +217,7 @@ void f18_d() { // http://llvm.org/bugs/show_bug.cgi?id=3514 extern const int MyConstant; int f19(void) { - int x = MyConstant; // no-warning + int x = MyConstant; // no-warning x = 1; return x; } @@ -217,7 +226,7 @@ int f19b(void) { // This case is the same as f19. const int MyConstant = 0; int x = MyConstant; // no-warning x = 1; - return x; + return x; } void f20(void) { @@ -228,8 +237,7 @@ void f20(void) { void halt() __attribute__((noreturn)); int f21() { int x = 4; - - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} if (1) { halt(); (void)x; @@ -261,7 +269,7 @@ void f22() { int y19 = 4; int y20 = 4; - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} ++y1; ++y2; ++y3; @@ -309,13 +317,13 @@ void f22() { } else (void)x; (void)x; - break; + break; case 4: - 0 ? : ((void)y4, ({ return; })); + 0 ?: ((void)y4, ({ return; })); (void)x; break; case 5: - 1 ? : (void)x; + 1 ?: (void)x; 0 ? (void)x : ((void)y5, ({ return; })); (void)x; break; @@ -326,11 +334,13 @@ void f22() { case 7: (void)(0 && x); (void)y7; - (void)(0 || (y8, ({ return; }), 1)); // expected-warning {{expression result unused}} + (void)(0 || (y8, ({ return; }), 1)); + // non-nested-warning@-1 {{expression result unused}} (void)x; break; case 8: - (void)(1 && (y9, ({ return; }), 1)); // expected-warning {{expression result unused}} + (void)(1 && (y9, ({ return; }), 1)); + // non-nested-warning@-1 {{expression result unused}} (void)x; break; case 9: @@ -365,16 +375,16 @@ void f22() { for (;;) { (void)y16; } - (void)x; + (void)x; break; case 15: - for (;1;) { + for (; 1;) { (void)y17; } (void)x; break; case 16: - for (;0;) { + for (; 0;) { (void)x; } (void)y18; @@ -390,28 +400,36 @@ void f22() { } } -void f23_aux(const char* s); +void f23_aux(const char *s); void f23(int argc, char **argv) { int shouldLog = (argc > 1); // no-warning - ^{ - if (shouldLog) f23_aux("I did too use it!\n"); - else f23_aux("I shouldn't log. Wait.. d'oh!\n"); + ^{ + if (shouldLog) + f23_aux("I did too use it!\n"); + else + f23_aux("I shouldn't log. Wait.. d'oh!\n"); }(); } void f23_pos(int argc, char **argv) { - int shouldLog = (argc > 1); // expected-warning{{Value stored to 'shouldLog' during its initialization is never read}} expected-warning{{unused variable 'shouldLog'}} - ^{ - f23_aux("I did too use it!\n"); - }(); + int shouldLog = (argc > 1); + // non-nested-warning@-1 {{Value stored to 'shouldLog' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'shouldLog'}} + // non-nested-remark@-3 {{16-28: ''}} + ^{ + f23_aux("I did too use it!\n"); + }(); } void f24_A(int y) { // FIXME: One day this should be reported as dead since 'z = x + y' is dead. int x = (y > 2); // no-warning - ^ { - int z = x + y; // expected-warning{{Value stored to 'z' during its initialization is never read}} expected-warning{{unused variable 'z'}} - }(); + ^{ + int z = x + y; + // non-nested-warning@-1 {{Value stored to 'z' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'z'}} + // non-nested-remark@-3 {{10-17: ''}} + }(); } void f24_B(int y) { @@ -426,7 +444,7 @@ void f24_B(int y) { int f24_C(int y) { // FIXME: One day this should be reported as dead since 'x' is just overwritten. __block int x = (y > 2); // no-warning - ^{ + ^{ x = 5; // no-warning }(); return x; @@ -434,32 +452,35 @@ int f24_C(int y) { int f24_D(int y) { __block int x = (y > 2); // no-warning - ^{ + ^{ if (y > 4) x = 5; // no-warning }(); return x; } -// This example shows that writing to a variable captured by a block means that it might -// not be dead. +// This example shows that writing to a variable captured by a block means that +// it might not be dead. int f25(int y) { __block int x = (y > 2); __block int z = 0; - void (^foo)() = ^{ z = x + y; }; + void (^foo)() = ^{ + z = x + y; + }; x = 4; // no-warning foo(); - return z; + return z; } -// This test is mostly the same as 'f25', but shows that the heuristic of pruning out dead -// stores for variables that are just marked '__block' is overly conservative. +// This test is mostly the same as 'f25', but shows that the heuristic of +// pruning out dead stores for variables that are just marked '__block' is +// overly conservative. int f25_b(int y) { // FIXME: we should eventually report a dead store here. __block int x = (y > 2); __block int z = 0; x = 4; // no-warning - return z; + return z; } int f26_nestedblocks() { @@ -468,10 +489,10 @@ int f26_nestedblocks() { __block int y = 0; ^{ int k; - k = 1; // expected-warning{{Value stored to 'k' is never read}} + k = 1; // non-nested-warning {{Value stored to 'k' is never read}} ^{ - y = z + 1; - }(); + y = z + 1; + }(); }(); return y; } @@ -480,11 +501,13 @@ int f26_nestedblocks() { // placed within the increment code of for loops. void rdar8014335() { for (int i = 0 ; i != 10 ; ({ break; })) { - for ( ; ; ({ ++i; break; })) ; // expected-warning {{'break' is bound to current loop, GCC binds it to the enclosing loop}} + for (;; ({ ++i; break; })) + ; + // non-nested-warning@-2 {{'break' is bound to current loop, GCC binds it to the enclosing loop}} // Note that the next value stored to 'i' is never executed // because the next statement to be executed is the 'break' // in the increment code of the first loop. - i = i * 3; // expected-warning{{Value stored to 'i' is never read}} + i = i * 3; // non-nested-warning {{Value stored to 'i' is never read}} } } @@ -517,10 +540,8 @@ void rdar8405222_aux(int i); void rdar8405222() { const int show = 0; int i = 0; - if (show) - i = 5; // no-warning - + i = 5; // no-warning if (show) rdar8405222_aux(i); } @@ -529,13 +550,13 @@ void rdar8405222() { // silencing heuristics. int radar11185138_foo() { int x, y; - x = y = 0; // expected-warning {{never read}} + x = y = 0; // non-nested-warning {{never read}} return y; } int rdar11185138_bar() { int y; - int x = y = 0; // no-warning + int x = y = 0; // nested-warning {{Although the value stored}} x = 2; y = 2; return x + y; @@ -550,26 +571,58 @@ int *radar11185138_baz() { int getInt(); int *getPtr(); void testBOComma() { - int x0 = (getInt(), 0); // expected-warning{{unused variable 'x0'}} - int x1 = (getInt(), getInt()); // expected-warning {{Value stored to 'x1' during its initialization is never read}} // expected-warning{{unused variable 'x1'}} - int x2 = (getInt(), getInt(), getInt()); //expected-warning{{Value stored to 'x2' during its initialization is never read}} // expected-warning{{unused variable 'x2'}} + int x0 = (getInt(), 0); // non-nested-warning {{unused variable 'x0'}} + int x1 = (getInt(), getInt()); + // non-nested-warning@-1 {{Value stored to 'x1' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x1'}} + + int x2 = (getInt(), getInt(), getInt()); + // non-nested-warning@-1 {{Value stored to 'x2' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x2'}} + int x3; - x3 = (getInt(), getInt(), 0); // expected-warning{{Value stored to 'x3' is never read}} - int x4 = (getInt(), (getInt(), 0)); // expected-warning{{unused variable 'x4'}} + x3 = (getInt(), getInt(), 0); + // non-nested-warning@-1 {{Value stored to 'x3' is never read}} + + int x4 = (getInt(), (getInt(), 0)); + // non-nested-warning@-1 {{unused variable 'x4'}} + int y; - int x5 = (getInt(), (y = 0)); // expected-warning{{unused variable 'x5'}} - int x6 = (getInt(), (y = getInt())); //expected-warning {{Value stored to 'x6' during its initialization is never read}} // expected-warning{{unused variable 'x6'}} - int x7 = 0, x8 = getInt(); //expected-warning {{Value stored to 'x8' during its initialization is never read}} // expected-warning{{unused variable 'x8'}} // expected-warning{{unused variable 'x7'}} - int x9 = getInt(), x10 = 0; //expected-warning {{Value stored to 'x9' during its initialization is never read}} // expected-warning{{unused variable 'x9'}} // expected-warning{{unused variable 'x10'}} - int m = getInt(), mm, mmm; //expected-warning {{Value stored to 'm' during its initialization is never read}} // expected-warning{{unused variable 'm'}} // expected-warning{{unused variable 'mm'}} // expected-warning{{unused variable 'mmm'}} - int n, nn = getInt(); //expected-warning {{Value stored to 'nn' during its initialization is never read}} // expected-warning{{unused variable 'n'}} // expected-warning{{unused variable 'nn'}} + int x5 = (getInt(), (y = 0)); + // non-nested-warning@-1 {{unused variable 'x5'}} + // nested-warning@-2 {{Although the value stored}} + + int x6 = (getInt(), (y = getInt())); + // non-nested-warning@-1 {{Value stored to 'x6' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x6'}} + // nested-warning@-3 {{Although the value stored}} + + int x7 = 0, x8 = getInt(); + // non-nested-warning@-1 {{Value stored to 'x8' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x8'}} + // non-nested-warning@-3 {{unused variable 'x7'}} + + int x9 = getInt(), x10 = 0; + // non-nested-warning@-1 {{Value stored to 'x9' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'x9'}} + // non-nested-warning@-3 {{unused variable 'x10'}} + + int m = getInt(), mm, mmm; + // non-nested-warning@-1 {{Value stored to 'm' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'm'}} + // non-nested-warning@-3 {{unused variable 'mm'}} + // non-nested-warning@-4 {{unused variable 'mmm'}} + + int n, nn = getInt(); + // non-nested-warning@-1 {{Value stored to 'nn' during its initialization is never read}} + // non-nested-warning@-2 {{unused variable 'n'}} + // non-nested-warning@-3 {{unused variable 'nn'}} int *p; p = (getPtr(), (int *)0); // no warning - } void testVolatile() { - volatile int v; - v = 0; // no warning + volatile int v; + v = 0; // no warning } diff --git a/clang/test/Analysis/dead-stores.cpp b/clang/test/Analysis/dead-stores.cpp index d926ccf5ecf69..94865b36a9084 100644 --- a/clang/test/Analysis/dead-stores.cpp +++ b/clang/test/Analysis/dead-stores.cpp @@ -1,15 +1,26 @@ -// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 -analyzer-checker=deadcode.DeadStores -verify -Wno-unreachable-code %s -// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 -analyzer-store=region -analyzer-checker=deadcode.DeadStores -verify -Wno-unreachable-code %s +// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 \ +// RUN: -analyzer-checker=deadcode.DeadStores -Wno-unreachable-code \ +// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ +// RUN: -verify=non-nested %s +// +// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 \ +// RUN: -analyzer-store=region -analyzer-checker=deadcode.DeadStores \ +// RUN: -analyzer-config deadcode.DeadStores:WarnForDeadNestedAssignments=false\ +// RUN: -Wno-unreachable-code -verify=non-nested %s +// +// RUN: %clang_analyze_cc1 -fcxx-exceptions -fexceptions -fblocks -std=c++11 \ +// RUN: -analyzer-checker=deadcode.DeadStores -Wno-unreachable-code \ +// RUN: -verify=non-nested,nested %s //===----------------------------------------------------------------------===// // Basic dead store checking (but in C++ mode). //===----------------------------------------------------------------------===// int j; +int make_int(); void test1() { int x = 4; - - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} switch (j) { case 1: @@ -17,6 +28,11 @@ void test1() { (void)x; break; } + + int y; + (void)y; + if ((y = make_int())) // nested-warning {{Although the value stored}} + return; } //===----------------------------------------------------------------------===// @@ -25,6 +41,7 @@ void test1() { class Test2 { int &x; + public: Test2(int &y) : x(y) {} ~Test2() { ++x; } @@ -66,17 +83,17 @@ void test2_b() { //===----------------------------------------------------------------------===// void test3_a(int x) { - x = x + 1; // expected-warning{{never read}} + x = x + 1; // non-nested-warning {{never read}} } void test3_b(int &x) { - x = x + 1; // no-warninge + x = x + 1; // no-warning } void test3_c(int x) { int &y = x; - // Shows the limitation of dead stores tracking. The write is really - // dead since the value cannot escape the function. + // Shows the limitation of dead stores tracking. The write is really dead + // since the value cannot escape the function. ++y; // no-warning } @@ -94,7 +111,7 @@ void test3_e(int &x) { //===----------------------------------------------------------------------===// static void test_new(unsigned n) { - char **p = new char* [n]; // expected-warning{{never read}} + char **p = new char *[n]; // non-nested-warning {{never read}} } //===----------------------------------------------------------------------===// @@ -102,11 +119,11 @@ static void test_new(unsigned n) { //===----------------------------------------------------------------------===// namespace foo { - int test_4(int x) { - x = 2; // expected-warning{{Value stored to 'x' is never read}} - x = 2; - return x; - } +int test_4(int x) { + x = 2; // non-nested-warning {{Value stored to 'x' is never read}} + x = 2; + return x; +} } //===----------------------------------------------------------------------===// @@ -119,42 +136,39 @@ int test_5() { try { x = 2; // no-warning test_5_Aux(); - } - catch (int z) { + } catch (int z) { return x + z; } return 1; } - int test_6_aux(unsigned x); - void test_6() { - unsigned currDestLen = 0; // no-warning + unsigned currDestLen = 0; // no-warning try { while (test_6_aux(currDestLen)) { currDestLen += 2; // no-warning - } + } + } catch (void *) { } - catch (void *) {} } void test_6b() { - unsigned currDestLen = 0; // no-warning + unsigned currDestLen = 0; // no-warning try { while (test_6_aux(currDestLen)) { - currDestLen += 2; // expected-warning {{Value stored to 'currDestLen' is never read}} + currDestLen += 2; + // non-nested-warning@-1 {{Value stored to 'currDestLen' is never read}} break; - } + } + } catch (void *) { } - catch (void *) {} } - void testCXX11Using() { using Int = int; Int value; - value = 1; // expected-warning {{never read}} + value = 1; // non-nested-warning {{never read}} } //===----------------------------------------------------------------------===// @@ -177,13 +191,14 @@ int radar_13213575() { template void test_block_in_dependent_context(typename T::some_t someArray) { ^{ - int i = someArray[0]; // no-warning + int i = someArray[0]; // no-warning }(); } void test_block_in_non_dependent_context(int *someArray) { ^{ - int i = someArray[0]; // expected-warning {{Value stored to 'i' during its initialization is never read}} + int i = someArray[0]; + // non-nested-warning@-1 {{Value stored to 'i' during its initialization is never read}} }(); } diff --git a/clang/test/Analysis/dead-stores.m b/clang/test/Analysis/dead-stores.m index 9f91f393a1164..27543ab38139b 100644 --- a/clang/test/Analysis/dead-stores.m +++ b/clang/test/Analysis/dead-stores.m @@ -1,5 +1,4 @@ // RUN: %clang_analyze_cc1 -analyzer-checker=core,alpha.core -analyzer-checker=deadcode.DeadStores,osx.cocoa.RetainCount -fblocks -verify -Wno-objc-root-class %s -// expected-no-diagnostics typedef signed char BOOL; typedef unsigned int NSUInteger; @@ -72,7 +71,8 @@ - (id) init; @implementation Rdar7947686_B - (id) init { - id x = (self = [super init]); // no-warning + id x = (self = [super init]); + // expected-warning@-1 {{Although the value stored to 'self'}} return x; } @end diff --git a/clang/test/Analysis/deadstores-driverkit.cpp b/clang/test/Analysis/deadstores-driverkit.cpp new file mode 100644 index 0000000000000..9c423fc6ff218 --- /dev/null +++ b/clang/test/Analysis/deadstores-driverkit.cpp @@ -0,0 +1,24 @@ +/* iig(DriverKit-60) generated from SomethingSomething.iig */ + +// The comment above is the whole point of the test. +// That's how the suppression works. +// It needs to be on the top. +// Run-lines can wait. + +// RUN: %clang_analyze_cc1 -std=c++17 -w -triple x86_64-apple-driverkit19.0 \ +// RUN: -analyzer-checker=deadcode -verify %s + +// expected-no-diagnostics + +#include "os_object_base.h" + +class OSSomething { + kern_return_t Invoke(const IORPC); + void foo(OSDispatchMethod supermethod) { + kern_return_t ret; + IORPC rpc; + // Test the DriverKit specific suppression in the dead stores checker. + if (supermethod) ret = supermethod((OSObject *)this, rpc); // no-warning + else ret = ((OSObject *)this)->Invoke(rpc); // no-warning + } +}; diff --git a/clang/test/Analysis/diagnostics/dtors.cpp b/clang/test/Analysis/diagnostics/dtors.cpp index 18bedc61f98e8..6a8349da9d78c 100644 --- a/clang/test/Analysis/diagnostics/dtors.cpp +++ b/clang/test/Analysis/diagnostics/dtors.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,cplusplus -analyzer-output=text -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -w -analyzer-checker=core,cplusplus -analyzer-output=text -verify %s namespace no_crash_on_delete_dtor { // We were crashing when producing diagnostics for this code, but not for the diff --git a/clang/test/Analysis/diagnostics/explicit-suppression.cpp b/clang/test/Analysis/diagnostics/explicit-suppression.cpp index 2bb969059ffbe..c10aaa528e0d8 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:677 {{Called C++ object pointer is null}} + // expected-warning@../Inputs/system-header-simulator-cxx.h:686 {{Called C++ object pointer is null}} #endif } diff --git a/clang/test/Analysis/diagnostics/find_last_store.c b/clang/test/Analysis/diagnostics/find_last_store.c index 9bf601ea30e2a..486e4ec64d169 100644 --- a/clang/test/Analysis/diagnostics/find_last_store.c +++ b/clang/test/Analysis/diagnostics/find_last_store.c @@ -2,13 +2,11 @@ typedef struct { float b; } c; void *a(); void *d() { - return a(); // expected-note{{Returning pointer}} + return a(); } void no_find_last_store() { - c *e = d(); // expected-note{{Calling 'd'}} - // expected-note@-1{{Returning from 'd'}} - // expected-note@-2{{'e' initialized here}} + c *e = d(); // expected-note{{'e' initialized here}} (void)(e || e->b); // expected-note{{Assuming 'e' is null}} // expected-note@-1{{Left side of '||' is false}} diff --git a/clang/test/Analysis/diagnostics/no-store-func-path-notes.m b/clang/test/Analysis/diagnostics/no-store-func-path-notes.m index 0136389efe617..6ef162e4ecd50 100644 --- a/clang/test/Analysis/diagnostics/no-store-func-path-notes.m +++ b/clang/test/Analysis/diagnostics/no-store-func-path-notes.m @@ -16,6 +16,7 @@ - (int)initVar:(int *)var param:(int)param { return 0; } return 1; // expected-note{{Returning without writing to '*var'}} + // expected-note@-1{{Returning the value 1, which participates in a condition later}} } @end diff --git a/clang/test/Analysis/domtest.c b/clang/test/Analysis/domtest.c index e957c8d70e02b..b642bd35319cf 100644 --- a/clang/test/Analysis/domtest.c +++ b/clang/test/Analysis/domtest.c @@ -1,6 +1,8 @@ -// RUN: rm -f %t -// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpDominators %s > %t 2>&1 -// RUN: FileCheck --input-file=%t %s +// RUN: %clang_analyze_cc1 %s \ +// RUN: -analyzer-checker=debug.DumpDominators \ +// RUN: -analyzer-checker=debug.DumpPostDominators \ +// RUN: -analyzer-checker=debug.DumpControlDependencies \ +// RUN: 2>&1 | FileCheck %s // Test the DominatorsTree implementation with various control flows int test1() @@ -24,17 +26,44 @@ int test1() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,7) -// CHECK: (2,3) -// CHECK: (3,6) -// CHECK: (4,6) -// CHECK: (5,6) -// CHECK: (6,7) -// CHECK: (7,8) -// CHECK: (8,9) -// CHECK: (9,9) +// [B9 (ENTRY)] -> [B8] -> [B7] -> [B6] -> [B5] -> [B3] -> [B2] +// |\ \ / / +// | \ ---> [B4] ---> / +// | <--------------------------- +// V +// [B1] -> [B0 (EXIT)] + +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,7) +// CHECK-NEXT: (3,7) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (4,7) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (5,7) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,7) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,7) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (3,6) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (9,9) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,7) +// CHECK-NEXT: (3,2) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,3) +// CHECK-NEXT: (6,3) +// CHECK-NEXT: (7,1) +// CHECK-NEXT: (8,7) +// CHECK-NEXT: (9,8) int test2() { @@ -54,15 +83,39 @@ int test2() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,6) -// CHECK: (2,3) -// CHECK: (3,4) -// CHECK: (4,6) -// CHECK: (5,6) -// CHECK: (6,7) -// CHECK: (7,7) +// <------------- +// / \ +// -----------> [B4] -> [B3] -> [B2] +// / | +// / V +// [B7 (ENTRY)] -> [B6] -> [B5] -> [B1] -> [B0 (EXIT)] + +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,4) +// CHECK-NEXT: (2,6) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (3,6) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (4,4) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,6) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,7) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,4) +// CHECK-NEXT: (3,2) +// CHECK-NEXT: (4,1) +// CHECK-NEXT: (5,1) +// CHECK-NEXT: (6,1) +// CHECK-NEXT: (7,6) int test3() { @@ -82,16 +135,48 @@ int test3() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,7) -// CHECK: (2,5) -// CHECK: (3,4) -// CHECK: (4,5) -// CHECK: (5,6) -// CHECK: (6,7) -// CHECK: (7,8) -// CHECK: (8,8) +// <- [B2] <- +// / \ +// [B8 (ENTRY)] -> [B7] -> [B6] ---> [B5] -> [B4] -> [B3] +// \ | \ / +// \ | <------------- +// \ \ +// --------> [B1] -> [B0 (EXIT)] + +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,6) +// CHECK-NEXT: (2,7) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (3,6) +// CHECK-NEXT: (3,7) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (4,6) +// CHECK-NEXT: (4,7) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (5,5) +// CHECK-NEXT: (5,7) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (6,6) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,7) +// CHECK-NEXT: (2,5) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (5,6) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,8) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,6) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,2) +// CHECK-NEXT: (6,1) +// CHECK-NEXT: (7,1) +// CHECK-NEXT: (8,7) int test4() { @@ -108,20 +193,69 @@ int test4() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,10) -// CHECK: (2,9) -// CHECK: (3,4) -// CHECK: (4,5) -// CHECK: (5,9) -// CHECK: (6,7) -// CHECK: (7,8) -// CHECK: (8,9) -// CHECK: (9,10) -// CHECK: (10,11) -// CHECK: (11,12) -// CHECK: (12,12) +// <---------------------------------- +// / <----------------- \ +// / / \ \ +// [B12 (ENTRY)] -> [B11] -> [B10]-> [B9] -> [B8] ---> [B7] -> [B6] | +// | \ \ / +// | \ -----> [B2] --------/ +// | \ / +// | -> [B5] -> [B4] -> [B3] +// | \ / +// | <------------ +// \ +// -> [B1] -> [B0 (EXIT)] + +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (3,9) +// CHECK-NEXT: (3,10) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (4,9) +// CHECK-NEXT: (4,10) +// CHECK-NEXT: (5,9) +// CHECK-NEXT: (5,5) +// CHECK-NEXT: (5,10) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (6,9) +// CHECK-NEXT: (6,10) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (7,9) +// CHECK-NEXT: (7,10) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (8,8) +// CHECK-NEXT: (8,10) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: (10,10) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,10) +// CHECK-NEXT: (2,9) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (5,9) +// CHECK-NEXT: (6,7) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: (10,11) +// CHECK-NEXT: (11,12) +// CHECK-NEXT: (12,12) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,5) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,2) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (7,6) +// CHECK-NEXT: (8,2) +// CHECK-NEXT: (9,2) +// CHECK-NEXT: (10,1) +// CHECK-NEXT: (11,10) +// CHECK-NEXT: (12,11) int test5() { @@ -151,18 +285,54 @@ int test5() return 0; } -// CHECK: Immediate dominance tree (Node#,IDom#): -// CHECK: (0,1) -// CHECK: (1,10) -// CHECK: (2,10) -// CHECK: (3,9) -// CHECK: (4,9) -// CHECK: (5,8) -// CHECK: (6,8) -// CHECK: (7,8) -// CHECK: (8,9) -// CHECK: (9,10) -// CHECK: (10,11) -// CHECK: (11,11) - +// [B0 (EXIT)] <-- +// \ +// [B11 (ENTY)] -> [B10] -> [B9] -> [B8] -> [B7] -> [B5] -> [B3] -> [B1] +// | | | / / / +// | | V / / / +// | V [B6] --> / / +// V [B4] -----------------> / +// [B2]---------------------------------> +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,10) +// CHECK-NEXT: (4,9) +// CHECK-NEXT: (4,10) +// CHECK-NEXT: (5,9) +// CHECK-NEXT: (5,10) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (6,9) +// CHECK-NEXT: (6,10) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (7,9) +// CHECK-NEXT: (7,10) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (8,10) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,1) +// CHECK-NEXT: (1,10) +// CHECK-NEXT: (2,10) +// CHECK-NEXT: (3,9) +// CHECK-NEXT: (4,9) +// CHECK-NEXT: (5,8) +// CHECK-NEXT: (6,8) +// CHECK-NEXT: (7,8) +// CHECK-NEXT: (8,9) +// CHECK-NEXT: (9,10) +// CHECK-NEXT: (10,11) +// CHECK-NEXT: (11,11) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,1) +// CHECK-NEXT: (3,1) +// CHECK-NEXT: (4,3) +// CHECK-NEXT: (5,3) +// CHECK-NEXT: (6,5) +// CHECK-NEXT: (7,5) +// CHECK-NEXT: (8,5) +// CHECK-NEXT: (9,3) +// CHECK-NEXT: (10,1) +// CHECK-NEXT: (11,10) diff --git a/clang/test/Analysis/domtest.cpp b/clang/test/Analysis/domtest.cpp new file mode 100644 index 0000000000000..2a2caed130d79 --- /dev/null +++ b/clang/test/Analysis/domtest.cpp @@ -0,0 +1,71 @@ +// RUN: %clang_analyze_cc1 -std=c++14 %s \ +// RUN: -analyzer-checker=debug.DumpDominators \ +// RUN: -analyzer-checker=debug.DumpPostDominators \ +// RUN: -analyzer-checker=debug.DumpControlDependencies \ +// RUN: 2>&1 | FileCheck %s + +bool coin(); + +namespace pr42041_unreachable_cfg_successor { +enum Kind { + A +}; + +void f() { + switch(Kind{}) { + case A: + break; + } +} +} // end of namespace pr42041_unreachable_cfg_successor + +// [B3 (ENTRY)] -> [B1] -> [B2] -> [B0 (EXIT)] + +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,2) +// CHECK-NEXT: (1,3) +// CHECK-NEXT: (2,1) +// CHECK-NEXT: (3,3) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,2) +// CHECK-NEXT: (2,0) +// CHECK-NEXT: (3,1) + +void funcWithBranch() { + int x = 0; + if (coin()) { + if (coin()) { + x = 5; + } + int j = 10 / x; + (void)j; + } +} + +// 1st if 2nd if +// [B5 (ENTRY)] -> [B4] -> [B3] -> [B2] -> [B1] -> [B0 (EXIT)] +// \ \ / / +// \ -------------> / +// ------------------------------> + +// CHECK: Control dependencies (Node#,Dependency#): +// CHECK-NEXT: (1,4) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (2,4) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: Immediate dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,4) +// CHECK-NEXT: (1,3) +// CHECK-NEXT: (2,3) +// CHECK-NEXT: (3,4) +// CHECK-NEXT: (4,5) +// CHECK-NEXT: (5,5) +// CHECK-NEXT: Immediate post dominance tree (Node#,IDom#): +// CHECK-NEXT: (0,0) +// CHECK-NEXT: (1,0) +// CHECK-NEXT: (2,1) +// CHECK-NEXT: (3,1) +// CHECK-NEXT: (4,0) +// CHECK-NEXT: (5,4) diff --git a/clang/test/Analysis/dtor.cpp b/clang/test/Analysis/dtor.cpp index d843f03aada7e..1c6251781545c 100644 --- a/clang/test/Analysis/dtor.cpp +++ b/clang/test/Analysis/dtor.cpp @@ -540,3 +540,33 @@ void f() { clang_analyzer_eval(__alignof(NonTrivial) > 0); // expected-warning{{TRUE}} } } + +namespace dtor_over_loc_concrete_int { +struct A { + ~A() {} +}; + +struct B { + A a; + ~B() {} +}; + +struct C : A { + ~C() {} +}; + +void testB() { + B *b = (B *)-1; + b->~B(); // no-crash +} + +void testC() { + C *c = (C *)-1; + c->~C(); // no-crash +} + +void testAutoDtor() { + const A &a = *(A *)-1; + // no-crash +} +} // namespace dtor_over_loc_concrete_int diff --git a/clang/test/Analysis/dump_egraph.c b/clang/test/Analysis/dump_egraph.c index f1ac03b10cc94..c5dd65de5fadf 100644 --- a/clang/test/Analysis/dump_egraph.c +++ b/clang/test/Analysis/dump_egraph.c @@ -1,6 +1,12 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-dump-egraph=%t.dot %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s // RUN: cat %t.dot | FileCheck %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core -analyzer-dump-egraph=%t.dot -trim-egraph %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot \ +// RUN: -trim-egraph %s +// RUN: cat %t.dot | FileCheck %s + // REQUIRES: asserts int getJ(); @@ -10,11 +16,33 @@ int foo() { return *x + *y; } -// CHECK: digraph "Exploded Graph" { - -// CHECK: \"program_points\": [\l    \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, \"terminator\": null, \"term_kind\": null, \"tag\": null \}\l  ],\l  \"program_state\": null +// CHECK: \"program_points\": [\l +// CHECK-SAME: \{ \"kind\": \"Edge\", \"src_id\": 2, \"dst_id\": 1, +// CHECK-SAME: \"terminator\": null, \"term_kind\": null, \"tag\": null, +// CHECK-SAME: \"node_id\": 1, \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \}, +// CHECK-SAME: \{ \"kind\": \"BlockEntrance\", \"block_id\": 1, \"tag\": null, +// CHECK-SAME: \"node_id\": 2, \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \}, +// CHECK-SAME: \{ \"kind\": \"Statement\", \"stmt_kind\": \"IntegerLiteral\", +// CHECK-SAME: \"stmt_id\": {{[0-9]*}}, \"pointer\": \"0x{{[0-9a-f]*}}\", +// CHECK-SAME: \"pretty\": \"0\", \"location\": \{ +// CHECK-SAME: \"line\": 15, \"column\": 12, \"file\": +// CHECK-SAME: \}, \"stmt_point_kind\": \"PreStmtPurgeDeadSymbols\", +// CHECK-SAME: \"tag\": \"ExprEngine : Clean Node\", \"node_id\": 3, +// CHECK-SAME: \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \}, +// CHECK-SAME: \{ \"kind\": \"Statement\", \"stmt_kind\": \"IntegerLiteral\", +// CHECK-SAME: \"stmt_id\": {{[0-9]*}}, \"pointer\": \"0x{{[0-9a-f]*}}\", +// CHECK-SAME: \"pretty\": \"0\", \"location\": \{ +// CHECK-SAME: \"line\": 15, \"column\": 12, \"file\": +// CHECK-SAME: \}, \"stmt_point_kind\": \"PostStmt\", \"tag\": null, +// CHECK-SAME: \"node_id\": 4, \"is_sink\": 0, \"has_report\": 0 +// CHECK-SAME: \} +// CHECK-SAME: ] -// CHECK: \"program_points\": [\l    \{ \"kind\": \"BlockEntrance\", \"block_id\": 1 +// CHECK: \"pretty\": \"*x\", \"location\": \{ \"line\": 16, \"column\": 10, \"file\": \"{{(.+)}}dump_egraph.c\" \} -// CHECK: \"has_report\": true +// CHECK: \"has_report\": 1 +// CHECK-NOT: \"program_state\": null diff --git a/clang/test/Analysis/dump_egraph.cpp b/clang/test/Analysis/dump_egraph.cpp index c62e4bfd4c106..9b87d1a90fcaa 100644 --- a/clang/test/Analysis/dump_egraph.cpp +++ b/clang/test/Analysis/dump_egraph.cpp @@ -14,11 +14,15 @@ struct T { void foo() { // Test that dumping symbols conjured on null statements doesn't crash. T t; + + new S; } -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"call_line\": null, \"items\": [\l        \{ \"lctx_id\": 1, \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"foo\", \"location\": null, \"items\": [\l        \{ \"stmt_id\": {{[0-9]+}}, \"kind\": \"construct into local variable\", \"argument_index\": null, \"pretty\": \"T t;\", \"value\": \"&t\" + +// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"location\": \{ \"line\": 16, \"column\": 5, \"file\": \"{{.*}}dump_egraph.cpp\" \}, \"items\": [\l        \{ \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t.s\" -// CHECK: \"location_context\": \"#0 Call\", \"calling\": \"T::T\", \"call_line\": \"16\", \"items\": [\l        \{ \"lctx_id\": 2, \"init_id\": {{[0-9]+}}, \"kind\": \"construct into member variable\", \"argument_index\": null, \"pretty\": \"s\", \"value\": \"&t-\>s\" +// CHECK: \"cluster\": \"t\", \"pointer\": \"{{0x[0-9a-f]+}}\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$2\{int, LC5, no stmt, #1\}\" -// CHECK: \"cluster\": \"t\", \"items\": [\l        \{ \"kind\": \"Default\", \"offset\": 0, \"value\": \"conj_$3\{int, LC3, no stmt, #1\}\" +// CHECK: \"dynamic_types\": [\l      \{ \"region\": \"HeapSymRegion\{conj_$1\{struct S *, LC1, S{{[0-9]+}}, #1\}\}\", \"dyn_type\": \"struct S\", \"sub_classable\": false \}\l diff --git a/clang/test/Analysis/edges-new.mm b/clang/test/Analysis/edges-new.mm index dda1bfb31e745..6bddbef58f1db 100644 --- a/clang/test/Analysis/edges-new.mm +++ b/clang/test/Analysis/edges-new.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,deadcode.DeadStores,osx.cocoa.RetainCount,unix.Malloc,unix.MismatchedDeallocator -analyzer-output=plist -o %t -w %s +// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,deadcode.DeadStores,osx.cocoa.RetainCount,unix.Malloc,unix.MismatchedDeallocator -analyzer-output=plist -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t -w %s // RUN: %normalize_plist <%t | diff -ub %S/Inputs/expected-plists/edges-new.mm.plist - //===----------------------------------------------------------------------===// diff --git a/clang/test/Analysis/egraph-asm-goto-no-crash.cpp b/clang/test/Analysis/egraph-asm-goto-no-crash.cpp new file mode 100644 index 0000000000000..37f8fc533abe3 --- /dev/null +++ b/clang/test/Analysis/egraph-asm-goto-no-crash.cpp @@ -0,0 +1,26 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +// expected-no-diagnostics + +void clang_analyzer_warnIfReached(); + +void testAsmGoto() { + asm goto("xor %0, %0\n je %l[label1]\n jl %l[label2]" + : /* no outputs */ + : /* inputs */ + : /* clobbers */ + : label1, label2 /* any labels used */); + + // FIXME: Should be reachable. + clang_analyzer_warnIfReached(); + + label1: + // FIXME: Should be reachable. + clang_analyzer_warnIfReached(); + return; + + label2: + // FIXME: Should be reachable. + clang_analyzer_warnIfReached(); + return; +} diff --git a/clang/test/Analysis/enum-cast-out-of-range.c b/clang/test/Analysis/enum-cast-out-of-range.c new file mode 100644 index 0000000000000..03e1100c38f49 --- /dev/null +++ b/clang/test/Analysis/enum-cast-out-of-range.c @@ -0,0 +1,34 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,alpha.cplusplus.EnumCastOutOfRange \ +// RUN: -verify %s + +enum En_t { + En_0 = -4, + En_1, + En_2 = 1, + En_3, + En_4 = 4 +}; + +void unscopedUnspecifiedCStyle() { + enum En_t Below = (enum En_t)(-5); // expected-warning {{not in the valid range}} + enum En_t NegVal1 = (enum En_t)(-4); // OK. + enum En_t NegVal2 = (enum En_t)(-3); // OK. + enum En_t InRange1 = (enum En_t)(-2); // expected-warning {{not in the valid range}} + enum En_t InRange2 = (enum En_t)(-1); // expected-warning {{not in the valid range}} + enum En_t InRange3 = (enum En_t)(0); // expected-warning {{not in the valid range}} + enum En_t PosVal1 = (enum En_t)(1); // OK. + enum En_t PosVal2 = (enum En_t)(2); // OK. + enum En_t InRange4 = (enum En_t)(3); // expected-warning {{not in the valid range}} + enum En_t PosVal3 = (enum En_t)(4); // OK. + enum En_t Above = (enum En_t)(5); // expected-warning {{not in the valid range}} +} + +enum En_t unused; +void unusedExpr() { + // Following line is not something that EnumCastOutOfRangeChecker should + // evaluate. Checker should either ignore this line or process it without + // producing any warnings. However, compilation will (and should) still + // generate a warning having nothing to do with this checker. + unused; // expected-warning {{expression result unused}} +} diff --git a/clang/test/Analysis/enum-cast-out-of-range.cpp b/clang/test/Analysis/enum-cast-out-of-range.cpp index e77339b551e0b..b600367f8c14a 100644 --- a/clang/test/Analysis/enum-cast-out-of-range.cpp +++ b/clang/test/Analysis/enum-cast-out-of-range.cpp @@ -150,7 +150,15 @@ void scopedSpecifiedCStyle() { scoped_specified_t InvalidAfterRangeEnd = (scoped_specified_t)(5); // expected-warning {{The value provided to the cast expression is not in the valid range of values for the enum}} } -void rangeContstrained1(int input) { +unscoped_unspecified_t unused; +void unusedExpr() { + // following line is not something that EnumCastOutOfRangeChecker should evaluate. checker should either ignore this line + // or process it without producing any warnings. However, compilation will (and should) still generate a warning having + // nothing to do with this checker. + unused; // expected-warning {{expression result unused}} +} + +void rangeConstrained1(int input) { if (input > -5 && input < 5) auto value = static_cast(input); // OK. Being conservative, this is a possibly good value. } diff --git a/clang/test/Analysis/explain-svals.cpp b/clang/test/Analysis/explain-svals.cpp index c1b5200eb8e92..9c37642758bb9 100644 --- a/clang/test/Analysis/explain-svals.cpp +++ b/clang/test/Analysis/explain-svals.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -triple i386-apple-darwin10 -analyzer-checker=core.builtin,debug.ExprInspection,unix.cstring -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -triple i386-apple-darwin10 -analyzer-checker=core.builtin,debug.ExprInspection,unix.cstring -verify %s typedef unsigned long size_t; diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot new file mode 100644 index 0000000000000..2d054a8d48bdb --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages.dot @@ -0,0 +1,39 @@ +// RUN: %exploded_graph_rewriter %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: Checker State: +// CHECK-SAME: alpha.core.FooChecker: +// CHECK-SAME: Foo stuff: +// CHECK-SAME: Foo: Bar +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "has_report": false, + "is_sink": false, + "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "environment": null, + "checker_messages": [ + { "checker": "alpha.core.FooChecker", "messages": [ + "Foo stuff:", + "Foo: Bar" + ]} + ] + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot new file mode 100644 index 0000000000000..898f79600b8ae --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/checker_messages_diff.dot @@ -0,0 +1,109 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{ + { "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "environment": null, + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "FooChecker", "messages": [ + "Foo: Bar" + ]}, + { "checker": "BarChecker", "messages": [ + "Bar: Foo" + ]} + ] + } + } +\l}"]; + +Node0x1 -> Node0x4; + + +// CHECK: Node0x4 [ +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: BarChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: Bar: Foo +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: FooChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: Bar: Foo +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: DunnoWhateverSomeOtherChecker: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: Dunno, some other message. +// CHECK-SAME: +Node0x4 [shape=record,label= + "{ + { + "state_id": 5, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "environment": null, + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "FooChecker", "messages": [ + "Foo: Bar", + "Bar: Foo" + ]}, + { "checker": "DunnoWhateverSomeOtherChecker", "messages": [ + "Dunno, some other message." + ]} + ] + } + } +\l}"]; + +Node0x4 -> Node0x6; + +Node0x6 [shape=record,label= + "{ + { "state_id": 7, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": null + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot new file mode 100644 index 0000000000000..f5ebcf1a606e2 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints.dot @@ -0,0 +1,36 @@ +// RUN: %exploded_graph_rewriter %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: Ranges: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME:
reg_$0\{ [0, 0] \}
+Node0x1 [shape=record,label= + "{ + { + "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "environment": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "constraints": [ + { "symbol": "reg_$0", "range": "{ [0, 0] }" } + ] + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot new file mode 100644 index 0000000000000..53a87aa66755b --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/constraints_diff.dot @@ -0,0 +1,92 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{ + { + "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "environment": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "constraints": [ + { "symbol": "reg_$0", "range": "{ [0, 10] }" } + ] + } + } +\l}"]; + +Node0x1 -> Node0x3; + +// CHECK: Node0x3 [ +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: reg_$0 +// CHECK-SAME: \{ [0, 10] \} +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: reg_$0 +// CHECK-SAME: \{ [0, 5] \} +// CHECK-SAME: +Node0x3 [shape=record,label= + "{ + { + "state_id": 4, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "environment": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "constraints": [ + { "symbol": "reg_$0", "range": "{ [0, 5] }" } + ] + } + } +\l}"]; + +Node0x3 -> Node0x5; + +Node0x5 [shape=record,label= + "{ + { + "state_id": 6, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "environment": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/edge.dot b/clang/test/Analysis/exploded-graph-rewriter/edge.dot index fa4b017e8a971..43d6e3b8faf10 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/edge.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/edge.dot @@ -1,15 +1,29 @@ -// RUN: %exploded_graph_rewriter %s | FileCheck %s +// RUN: %exploded_graph_rewriter %s | FileCheck %s -check-prefix=LIGHT +// RUN: %exploded_graph_rewriter --dark %s | FileCheck %s -check-prefixes=DARK // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows Node0x1 [shape=record,label= - "{{ "node_id": 1, "pointer": "0x1", - "program_state": null, "program_points": []}\l}"]; + "{{ "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; -// CHECK: Node0x1 -> Node0x2; +// LIGHT: Node0x1 -> Node0x2; +// DARK: Node0x1 -> Node0x2 [color="white"]; Node0x1 -> Node0x2; Node0x2 [shape=record,label= - "{{ "node_id": 2, "pointer": "0x2", - "program_state": null, "program_points": []}\l}"]; + "{{ "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/empty.dot b/clang/test/Analysis/exploded-graph-rewriter/empty.dot index 3e0733c5173e7..7f67e9cfed8a7 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/empty.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/empty.dot @@ -1,4 +1,6 @@ // RUN: %exploded_graph_rewriter %s | FileCheck %s +// RUN: %exploded_graph_rewriter --dark %s | FileCheck %s \ +// RUN: -check-prefixes=CHECK,DARK // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows @@ -8,5 +10,6 @@ digraph "Exploded Graph" { } // CHECK: digraph "ExplodedGraph" { +// DARK-NEXT: bgcolor="gray10"; // CHECK-NEXT: label=""; // CHECK-NEXT: } diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment.dot b/clang/test/Analysis/exploded-graph-rewriter/environment.dot index 7271684642a28..7b8fc05d3bb4a 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/environment.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/environment.dot @@ -3,14 +3,18 @@ // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows -// CHECK: Environment: +// CHECK: Expressions: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: -// CHECK-SAME: // CHECK-SAME: // CHECK-SAME: @@ -29,25 +33,50 @@ Node0x1 [shape=record,label= "{ { "node_id": 1, "pointer": "0x1", + "has_report": false, + "is_sink": false, "state_id": 2, - "program_points": [], + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "store": null, - "environment": [ - { - "location_context": "#0 Call", - "lctx_id": 3, - "calling": "foo", - "call_line": 4, - "items": [ - { - "stmt_id": 5, - "pretty": "bar()", - "value": "Unknown" - } - ] - } - ] + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "location": { + "file": "environment.cpp", + "line": 4, + "column": 6, + "spelling": { + "file": "environment.h", + "line": 7, + "column": 8 + } + }, + "items": [ + { + "stmt_id": 5, + "pretty": "bar()", + "value": "Unknown" + } + ] + } + ] + } } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot new file mode 100644 index 0000000000000..05e8d4eef50fd --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/environment_diff.dot @@ -0,0 +1,149 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// No diffs on the first node, nothing to check. +Node0x1 [shape=record,label= + "{ + { + "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "location": null, + "items": [ + { + "stmt_id": 5, + "pretty": "bar()", + "value": "Unknown" + } + ] + } + ] + } + } + } +\l}"]; + +Node0x1 -> Node0x6; + +// CHECK: Node0x6 [ +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +Node0x6 [shape=record,label= + "{ + { + "state_id": 7, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "location": null, + "items": [ + { + "stmt_id": 9, + "pretty": "baz()", + "value": "Undefined" + } + ] + } + ] + } + } + } +\l}"]; + +Node0x6 -> Node0x9; +// Make sure that the last node in the path is not diffed. +// CHECK: Node0x9 [ +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +Node0x9 [shape=record,label= + "{ + { + "state_id": 7, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "store": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "environment": { + "pointer": "0x2", + "items": [ + { + "location_context": "#0 Call", + "lctx_id": 3, + "calling": "foo", + "location": null, + "items": [ + { + "stmt_id": 9, + "pretty": "baz()", + "value": "Undefined" + } + ] + } + ] + } + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/escapes.c b/clang/test/Analysis/exploded-graph-rewriter/escapes.c new file mode 100644 index 0000000000000..aebb74a7ae284 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/escapes.c @@ -0,0 +1,23 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +void escapes() { + // CHECK: + // CHECK-SAME: + // CHECK-SAME: + // CHECK: + // CHECK-SAME: + // CHECK-SAME: + const char *const foo = "foo"; + + // CHECK: BinaryOperator + // CHECK-SAME: + // CHECK-SAME: + int x = 1 | 2; +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp b/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp new file mode 100644 index 0000000000000..96df69f8577ac --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/initializers_under_construction.cpp @@ -0,0 +1,25 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s +// REQUIRES: asserts + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +struct A { + A() {} +}; + +struct B { + A a; + B() : a() {} +}; + +void test() { + // CHECK: (construct into member variable) + // CHECK-SAME: + // CHECK-SAME: + B b; +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg b/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg index 7bc2e107f6423..87ce52cc53e0d 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg +++ b/clang/test/Analysis/exploded-graph-rewriter/lit.local.cfg @@ -8,11 +8,11 @@ use_lit_shell = os.environ.get("LIT_USE_INTERNAL_SHELL") config.test_format = lit.formats.ShTest(use_lit_shell == "0") config.substitutions.append(('%exploded_graph_rewriter', - '\'%s\' %s' % ( + '\'%s\' %s --dump-dot-only' % ( config.python_executable, lit.util.which('exploded-graph-rewriter.py', os.path.join( config.clang_src_dir, 'utils', 'analyzer'))))) -config.suffixes = ['.dot'] +config.suffixes.add('.dot') diff --git a/clang/test/Analysis/exploded-graph-rewriter/macros.c b/clang/test/Analysis/exploded-graph-rewriter/macros.c new file mode 100644 index 0000000000000..4dcbda95d225e --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/macros.c @@ -0,0 +1,18 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s +// REQUIRES: asserts + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// CHECK: macros.c:17:10 +// CHECK-SAME: +// CHECK-SAME: (spelling at macros.c:15:14) +// CHECK-SAME: +#define NULL 0 +void *foo() { + return NULL; +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot new file mode 100644 index 0000000000000..89d5070deedc4 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/node_labels.dot @@ -0,0 +1,60 @@ +// RUN: %exploded_graph_rewriter %s \ +// RUN: | FileCheck %s -check-prefixes=CHECK,LIGHT,COLOR +// RUN: %exploded_graph_rewriter %s --dark \ +// RUN: | FileCheck %s -check-prefixes CHECK,DARK,COLOR +// RUN: %exploded_graph_rewriter %s --gray \ +// RUN: | FileCheck %s -check-prefixes=CHECK,LIGHT,GRAY +// RUN: %exploded_graph_rewriter %s --gray --dark \ +// RUN: | FileCheck %s -check-prefixes CHECK,DARK,GRAY + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// LIGHT: Node0x1 [shape=record,label=< +// DARK: Node0x1 [shape=record,color="white",fontcolor="gray80",label=< +// CHECK-SAME: +// LIGHT-SAME: +// CHECK-SAME: +Node0x1 [shape=record,label= + "{ + { "state_id": 0, "program_state": null, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ] + } +\l}"]; + +// CHECK: Node0x2 [ +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +Node0x2 [shape=record,label= + "{ + { "state_id": 0, "program_state": null, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 2, + "has_report": 1, "is_sink": 1 + } + ] + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp new file mode 100644 index 0000000000000..e4d256247f98a --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/objects_under_construction.cpp @@ -0,0 +1,49 @@ +// FIXME: Figure out how to use %clang_analyze_cc1 with our lit.local.cfg. +// RUN: %clang_cc1 -analyze -triple x86_64-unknown-linux-gnu \ +// RUN: -analyze-function "test()" \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-dump-egraph=%t.dot %s +// RUN: %exploded_graph_rewriter %t.dot | FileCheck %s +// REQUIRES: asserts + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +struct S { + S() {} +}; + +void test() { + // CHECK: Objects Under Construction: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + // CHECK-SAME: + S s = S(); +} diff --git a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot index aadabf3955641..bee48d0f49f8c 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/program_points.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/program_points.dot @@ -3,31 +3,54 @@ // FIXME: Substitution doesn't seem to work on Windows. // UNSUPPORTED: system-windows -// CHECK: Program point: +// CHECK: Program points: // CHECK-SAME:
// CHECK-SAME: #0 Call // CHECK-SAME: -// CHECK-SAME: foo (line 4) +// CHECK-SAME: +// CHECK-SAME: foo +// CHECK-SAME: (environment.cpp:4:6 +// CHECK-SAME: +// CHECK-SAME: (spelling at environment.h:7:8) +// CHECK-SAME: ) // CHECK-SAME:
-S5bar()Unknown
+S9baz()Undefined
S9baz()Undefined
Store: (0x{{[0-9a-f]*}})foo0&Element\{"foo",0 S64b,char\}Expressions: "foo"&Element\{"foo",0 S64b,char\}1 \| 23 S32ba&b.a
+// DARK-SAME: +// CHECK-SAME: State 0 +// CHECK-SAME:
+// COLOR-SAME: Bug Report Attached +// GRAY-SAME: Bug Report Attached +// CHECK-SAME:
+// COLOR-SAME: Sink Node +// GRAY-SAME: Sink Node +// CHECK-SAME:
#0 Call + // CHECK-SAME: test + // CHECK-SAME:
S{{[0-9]*}} + // CHECK-SAME: (materialize temporary) + // CHECK-SAME: S()&s
S{{[0-9]*}} + // CHECK-SAME: (elide constructor) + // CHECK-SAME: S()&s
S{{[0-9]*}} + // CHECK-SAME: (construct into local variable) + // CHECK-SAME: S s = S();&s
// CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: // CHECK-SAME:
-// CHECK-SAME: - // CHECK-SAME: -// CHECK-SAME: Edge +// CHECK-SAME: BlockEdge // CHECK-SAME: // CHECK-SAME: [B0] -> [B1] // CHECK-SAME:
+// CHECK-SAME: +// CHECK-SAME: BlockEntrance +// CHECK-SAME: +// CHECK-SAME: [B1] +// CHECK-SAME:
Node0x1 [shape=record,label= "{ - { "node_id": 1, "pointer": "0x1", - "program_state": null, "program_points": [ + { + "state_id": 0, "program_state": null, "program_points": [ { "kind": "Edge", "src_id": 0, "dst_id": 1, "terminator": null, "term_kind": null, - "tag": null } + "tag": null, + "node_id": 1, + "has_report": 0, + "is_sink": 0 + }, + { + "kind": "BlockEntrance", + "block_id": 1, + "terminator": null, + "term_kind": null, + "tag": null, + "node_id": 2, + "has_report": 0, + "is_sink": 0 + } ]} \l}"]; @@ -35,29 +58,123 @@ Node0x1 [shape=record,label= // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: // CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: -// CHECK-SAME: // CHECK-SAME: // CHECK-SAME:
-// CHECK-SAME: (main file):4:5: +// CHECK-SAME: main.cpp:4:5: // CHECK-SAME: -// CHECK-SAME: DeclRefExpr +// CHECK-SAME: DeclRefExpr +// CHECK-SAME: S3 +// CHECK-SAME: PreStmt +// CHECK-SAME: x
+// CHECK-SAME: +// CHECK-SAME: Tag: +// CHECK-SAME: ExprEngine : Clean Node // CHECK-SAME: x
-Node0x2 [shape=record,label= +Node0x3 [shape=record,label= "{ - { "node_id": 2, "pointer": "0x2", - "program_state": null, "program_points": [ + { "state_id": 0, "program_state": null, "program_points": [ { "kind": "Statement", "stmt_kind": "DeclRefExpr", - "stmd_id": 3, + "stmt_point_kind": "PreStmt", + "stmt_id": 3, "pointer": "0x3", "pretty": "x", "location": { + "file": "main.cpp", "line": 4, "column": 5 }, - "tag": null + "tag": "ExprEngine : Clean Node", + "node_id": 3, + "pointer": "0x3", + "has_report": 0, + "is_sink": 0 + } + ]} +\l}"]; + +// Test collapsing large pretty prints with braces. + +// CHECK-NEXT: Program point: +// CHECK-SAME: \{ ... \} +Node0x4 [shape=record,label= + "{ + { + "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "Statement", + "stmt_kind": "CompoundStmt", + "stmt_point_kind": "PostStmt", + "stmt_id": 6, + "pointer": "0x6", + "pretty": "{ very very very very very very long pretty print }", + "location": { + "line": 7, + "column": 8 + }, + "tag": "ExprEngine : Clean Node", + "node_id": 4, + "has_report": 0, + "is_sink": 0 + } + ]} +\l}"]; + +// CHECK-NEXT: Program point: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME:
+// CHECK-SAME: main.cpp:8:9: +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: ImplicitCastExpr (LValueToRValue) +// CHECK-SAME: +// CHECK-SAME: S5 +// CHECK-SAME: PreStmt +// CHECK-SAME: y
+// CHECK-SAME: +// CHECK-SAME: Tag: +// CHECK-SAME: ExprEngine : Clean Node +// CHECK-SAME:
+Node0x5 [shape=record,label= + "{ + { "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "Statement", + "stmt_kind": "ImplicitCastExpr", + "cast_kind": "LValueToRValue", + "stmt_point_kind": "PreStmt", + "stmt_id": 5, + "pointer": "0x6", + "pretty": "y", + "location": { + "file": "main.cpp", + "line": 8, + "column": 9 + }, + "tag": "ExprEngine : Clean Node", + "node_id": 5, + "has_report": 0, + "is_sink": 0 } ]} \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/store.dot b/clang/test/Analysis/exploded-graph-rewriter/store.dot index 8152a9929fe90..c92901cbd7a34 100644 --- a/clang/test/Analysis/exploded-graph-rewriter/store.dot +++ b/clang/test/Analysis/exploded-graph-rewriter/store.dot @@ -4,6 +4,7 @@ // UNSUPPORTED: system-windows // CHECK: Store: +// CHECK-SAME: (0x2) // CHECK-SAME: // CHECK-SAME: // CHECK-SAME:
@@ -22,24 +23,37 @@ // CHECK-SAME:
Node0x1 [shape=record,label= "{ - { "node_id": 1, - "pointer": "0x1", - "state_id": 2, - "program_points": [], + { "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], "program_state": { "environment": null, - "store": [ - { - "cluster": "x", - "items": [ - { - "kind": "Default", - "offset": 0, - "value": "Undefined" - } - ] - } - ] + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "store": { + "pointer": "0x2", + "items": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Undefined" + } + ] + } + ] + } } } \l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot new file mode 100644 index 0000000000000..8dd5fb44cd747 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/store_diff.dot @@ -0,0 +1,117 @@ +// RUN: %exploded_graph_rewriter -d %s | FileCheck %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{ + { "node_id": 1, + "pointer": "0x1", + "has_report": false, + "is_sink": false, + "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "environment": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "store": { + "pointer": "0x2", + "items": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Undefined" + } + ] + } + ] + } + } + } +\l}"]; + +Node0x1 -> Node0x4; + +// CHECK: Node0x4 [ +// CHECK-SAME: +// CHECK-SAME: - +// CHECK-SAME: x0 +// CHECK-SAME: (Default) +// CHECK-SAME: Undefined +// CHECK-SAME: +// CHECK-SAME: +// CHECK-SAME: + +// CHECK-SAME: x +// CHECK-SAME: 0 +// CHECK-SAME: (Default) +// CHECK-SAME: Unknown +// CHECK-SAME: +Node0x4 [shape=record,label= + "{ + { + "state_id": 5, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "environment": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": null, + "store": { + "pointer": "0x5", + "items": [ + { + "cluster": "x", + "pointer": "0x3", + "items": [ + { + "kind": "Default", + "offset": 0, + "value": "Unknown" + } + ] + } + ] + } + } + } +\l}"]; + +Node0x4 -> Node0x6; + +Node0x6 [shape=record,label= + "{ + { + "state_id": 7, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": null + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/topology.dot b/clang/test/Analysis/exploded-graph-rewriter/topology.dot new file mode 100644 index 0000000000000..b85115ebeac72 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/topology.dot @@ -0,0 +1,36 @@ +// RUN: %exploded_graph_rewriter %s \ +// RUN: | FileCheck -check-prefixes=NORMAL %s +// RUN: %exploded_graph_rewriter -t %s \ +// RUN: | FileCheck -check-prefixes=TOPOLOGY %s + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +// NORMAL: Program point +// TOPOLOGY-NOT: Program point +// NORMAL: Checker State +// TOPOLOGY-NOT: Checker State +Node0x1 [shape=record,label= + "{ + { + "state_id": 2, + "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ], + "program_state": { + "environment": null, + "constraints": null, + "dynamic_types": null, + "constructing_objects": null, + "checker_messages": [ + { "checker": "foo", "messages": ["bar"] } + ], + "store": null + } + } +\l}"]; diff --git a/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot new file mode 100644 index 0000000000000..df6270d0ef1e2 --- /dev/null +++ b/clang/test/Analysis/exploded-graph-rewriter/trimmers.dot @@ -0,0 +1,71 @@ +// RUN: %exploded_graph_rewriter %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,FOUR +// RUN: %exploded_graph_rewriter -s %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,FOUR +// RUN: %exploded_graph_rewriter --to=0x2 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 2 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 2,3 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,NOFOUR +// RUN: %exploded_graph_rewriter --to 4 %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,THREE,FOUR +// RUN: %exploded_graph_rewriter --to 4 -s %s \ +// RUN: | FileCheck %s -check-prefixes=ONE,TWO,NOTHREE,FOUR + +// FIXME: Substitution doesn't seem to work on Windows. +// UNSUPPORTED: system-windows + +Node0x1 [shape=record,label= + "{{ "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 1, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; + +Node0x2 [shape=record,label= + "{{ "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 2, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; + +Node0x3 [shape=record,label= + "{{ "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 3, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; + +Node0x4 [shape=record,label= + "{{ "state_id": 0, "program_state": null, "program_points": [ + { + "kind": "BlockEntrance", "block_id": 1, + "terminator": null, "term_kind": null, + "tag": null, "node_id": 4, + "has_report": 0, "is_sink": 0 + } + ]}\l}"]; + +Node0x1 -> Node0x2; +Node0x1 -> Node0x3; +Node0x2 -> Node0x4; +Node0x3 -> Node0x4; + +// ONE: Node0x1 +// NOTONE-NOT: Node0x1 +// TWO: Node0x2 +// NOTTWO-NOT: Node0x2 +// THREE: Node0x3 +// NOTTHREE-NOT: Node0x3 +// FOUR: Node0x4 +// NOTFOUR-NOT: Node0x4 diff --git a/clang/test/Analysis/expr-inspection.c b/clang/test/Analysis/expr-inspection.c index a1fd952b26d59..7c5d9e5c49d35 100644 --- a/clang/test/Analysis/expr-inspection.c +++ b/clang/test/Analysis/expr-inspection.c @@ -5,6 +5,7 @@ // Self-tests for the debug.ExprInspection checker. void clang_analyzer_dump(int x); +void clang_analyzer_dump_pointer(int *p); void clang_analyzer_printState(); void clang_analyzer_numTimesReached(); @@ -24,21 +25,30 @@ void foo(int x) { } // CHECK: "program_state": { -// CHECK-NEXT: "store": [ -// CHECK-NEXT: { "cluster": "y", "items": [ +// CHECK-NEXT: "store": { "pointer": "{{0x[0-9a-f]+}}", "items": [ +// CHECK-NEXT: { "cluster": "y", "pointer": "{{0x[0-9a-f]+}}", "items": [ // CHECK-NEXT: { "kind": "Direct", "offset": 0, "value": "2 S32b" } // CHECK-NEXT: ]} -// CHECK-NEXT: ], -// CHECK-NEXT: "environment": [ -// CHECK-NEXT: { "location_context": "#0 Call", "calling": "foo", "call_line": null, "items": [ -// CHECK-NEXT: { "lctx_id": 1, "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } +// CHECK-NEXT: ]}, +// CHECK-NEXT: "environment": { "pointer": "{{0x[0-9a-f]+}}", "items": [ +// CHECK-NEXT: { "lctx_id": {{[0-9]+}}, "location_context": "#0 Call", "calling": "foo", "location": null, "items": [ +// CHECK-NEXT: { "stmt_id": {{[0-9]+}}, "pretty": "clang_analyzer_printState", "value": "&code{clang_analyzer_printState}" } // CHECK-NEXT: ]} -// CHECK-NEXT: ], +// CHECK-NEXT: ]}, // CHECK-NEXT: "constraints": [ // CHECK-NEXT: { "symbol": "reg_$0", "range": "{ [-2147483648, 13] }" } // CHECK-NEXT: ], // CHECK-NEXT: "dynamic_types": null, +// CHECK-NEXT: "dynamic_casts": null, // CHECK-NEXT: "constructing_objects": null, // CHECK-NEXT: "checker_messages": null // CHECK-NEXT: } +struct S { + int x, y; +}; + +void test_field_dumps(struct S s, struct S *p) { + clang_analyzer_dump_pointer(&s.x); // expected-warning{{&s.x}} + clang_analyzer_dump_pointer(&p->x); // expected-warning{{&SymRegion{reg_$0}.x}} +} diff --git a/clang/test/Analysis/globals.cpp b/clang/test/Analysis/globals.cpp index d3df6eb6d27c0..fc74161375f13 100644 --- a/clang/test/Analysis/globals.cpp +++ b/clang/test/Analysis/globals.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify -std=c++2a %s static const unsigned long long scull = 0; diff --git a/clang/test/Analysis/initialization.cpp b/clang/test/Analysis/initialization.cpp index db765930b6e5e..dd622e077e934 100644 --- a/clang/test/Analysis/initialization.cpp +++ b/clang/test/Analysis/initialization.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -triple i386-apple-darwin10 -analyze -analyzer-checker=core.builtin,debug.ExprInspection -verify %s +// RUN: %clang_cc1 -std=c++14 -triple i386-apple-darwin10 -analyze -analyzer-checker=core.builtin,debug.ExprInspection -verify %s void clang_analyzer_eval(int); diff --git a/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp b/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp index 5f75411716836..5a99ad11cc17e 100644 --- a/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp +++ b/clang/test/Analysis/inlining/placement-new-fp-suppression.cpp @@ -1,8 +1,8 @@ -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core.CallAndMessage \ // RUN: -analyzer-config suppress-null-return-paths=false \ // RUN: -verify %s -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core.CallAndMessage \ // RUN: -DSUPPRESSED \ // RUN: -verify %s diff --git a/clang/test/Analysis/inner-pointer.cpp b/clang/test/Analysis/inner-pointer.cpp index f4646c20fc294..d8b011a7aa64e 100644 --- a/clang/test/Analysis/inner-pointer.cpp +++ b/clang/test/Analysis/inner-pointer.cpp @@ -1,4 +1,5 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=cplusplus.InnerPointer \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=cplusplus.InnerPointer \ +// RUN: -Wno-dangling -Wno-dangling-field -Wno-return-stack-address \ // RUN: %s -analyzer-output=text -verify #include "Inputs/system-header-simulator-cxx.h" diff --git a/clang/test/Analysis/invalidated-iterator.cpp b/clang/test/Analysis/invalidated-iterator.cpp index 1151838bb8433..de37c9c055b97 100644 --- a/clang/test/Analysis/invalidated-iterator.cpp +++ b/clang/test/Analysis/invalidated-iterator.cpp @@ -3,397 +3,762 @@ #include "Inputs/system-header-simulator-cxx.h" -void bad_copy_assign_operator_list1(std::list &L1, +void clang_analyzer_warnIfReached(); + +void bad_copy_assign_operator1_list(std::list &L1, const std::list &L2) { auto i0 = L1.cbegin(); L1 = L2; *i0; // expected-warning{{Invalidated iterator accessed}} + clang_analyzer_warnIfReached(); } -void bad_copy_assign_operator_vector1(std::vector &V1, +void bad_copy_assign_operator1_vector(std::vector &V1, const std::vector &V2) { auto i0 = V1.cbegin(); V1 = V2; *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_copy_assign_operator_deque1(std::deque &D1, +void bad_copy_assign_operator1_deque(std::deque &D1, const std::deque &D2) { auto i0 = D1.cbegin(); D1 = D2; *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_copy_assign_operator_forward_list1(std::forward_list &FL1, +void bad_copy_assign_operator1_forward_list(std::forward_list &FL1, const std::forward_list &FL2) { auto i0 = FL1.cbegin(); FL1 = FL2; *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_list1(std::list &L, int n) { +void bad_assign1_list(std::list &L, int n) { auto i0 = L.cbegin(); L.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_vector1(std::vector &V, int n) { +void bad_assign1_vector(std::vector &V, int n) { auto i0 = V.cbegin(); V.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_deque1(std::deque &D, int n) { +void bad_assign1_deque(std::deque &D, int n) { auto i0 = D.cbegin(); D.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_assign_forward_list1(std::forward_list &FL, int n) { +void bad_assign1_forward_list(std::forward_list &FL, int n) { auto i0 = FL.cbegin(); FL.assign(10, n); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_clear_list1(std::list &L) { +void good_clear1_list(std::list &L) { auto i0 = L.cend(); L.clear(); --i0; // no-warning } -void bad_clear_list1(std::list &L) { +void bad_clear1_list(std::list &L) { auto i0 = L.cbegin(), i1 = L.cend(); L.clear(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void bad_clear_vector1(std::vector &V) { +void bad_clear1_vector(std::vector &V) { auto i0 = V.cbegin(), i1 = V.cend(); V.clear(); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_clear1_vector_decr(std::vector &V) { + auto i0 = V.cbegin(), i1 = V.cend(); + V.clear(); --i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_clear_deque1(std::deque &D) { +void bad_clear1_deque(std::deque &D) { auto i0 = D.cbegin(), i1 = D.cend(); D.clear(); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_clear1_deque_decr(std::deque &D) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.clear(); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_push_back_list1(std::list &L, int n) { +void good_push_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.push_back(n); *i0; // no-warning --i1; // no-warning } -void good_push_back_vector1(std::vector &V, int n) { +void good_push_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.push_back(n); *i0; // no-warning } -void bad_push_back_vector1(std::vector &V, int n) { +void bad_push_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.push_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_push_back_deque1(std::deque &D, int n) { +void bad_push_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.push_back(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_push_back1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.push_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_emplace_back_list1(std::list &L, int n) { +void good_emplace_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.emplace_back(n); *i0; // no-warning --i1; // no-warning } -void good_emplace_back_vector1(std::vector &V, int n) { +void good_emplace_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.emplace_back(n); *i0; // no-warning } -void bad_emplace_back_vector1(std::vector &V, int n) { +void bad_emplace_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(); V.emplace_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_emplace_back_deque1(std::deque &D, int n) { +void bad_emplace_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.emplace_back(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_emplace_back1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.emplace_back(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_back_list1(std::list &L, int n) { +void good_pop_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(), i2 = i1--; L.pop_back(); *i0; // no-warning *i2; // no-warning } -void bad_pop_back_list1(std::list &L, int n) { +void bad_pop_back1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(), i2 = i1--; L.pop_back(); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_back_vector1(std::vector &V, int n) { +void good_pop_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(), i2 = i1--; V.pop_back(); *i0; // no-warning } -void bad_pop_back_vector1(std::vector &V, int n) { +void bad_pop_back1_vector(std::vector &V, int n) { auto i0 = V.cbegin(), i1 = V.cend(), i2 = i1--; V.pop_back(); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_pop_back1_vector_decr(std::vector &V, int n) { + auto i0 = V.cbegin(), i1 = V.cend(), i2 = i1--; + V.pop_back(); --i2; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_back_deque1(std::deque &D, int n) { +void good_pop_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(), i2 = i1--; D.pop_back(); *i0; // no-warning } -void bad_pop_back_deque1(std::deque &D, int n) { +void bad_pop_back1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(), i2 = i1--; D.pop_back(); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_pop_back1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(), i2 = i1--; + D.pop_back(); --i2; // expected-warning{{Invalidated iterator accessed}} } -void good_push_front_list1(std::list &L, int n) { +void good_push_front1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.push_front(n); *i0; // no-warning --i1; // no-warning } -void bad_push_front_deque1(std::deque &D, int n) { +void bad_push_front1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.push_front(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_push_front1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.push_front(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_push_front_forward_list1(std::forward_list &FL, int n) { +void good_push_front1_forward_list(std::forward_list &FL, int n) { auto i0 = FL.cbegin(), i1 = FL.cend(); FL.push_front(n); *i0; // no-warning } -void good_emplace_front_list1(std::list &L, int n) { +void good_emplace_front1_list(std::list &L, int n) { auto i0 = L.cbegin(), i1 = L.cend(); L.emplace_front(n); *i0; // no-warning --i1; // no-warning } -void bad_emplace_front_deque1(std::deque &D, int n) { +void bad_emplace_front1_deque(std::deque &D, int n) { auto i0 = D.cbegin(), i1 = D.cend(); D.emplace_front(n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_emplace_front1_deque_decr(std::deque &D, int n) { + auto i0 = D.cbegin(), i1 = D.cend(); + D.emplace_front(n); --i1; // expected-warning{{Invalidated iterator accessed}} } -void good_emplace_front_forward_list1(std::forward_list &FL, int n) { +void good_emplace_front1_forward_list(std::forward_list &FL, int n) { auto i0 = FL.cbegin(), i1 = FL.cend(); FL.emplace_front(n); *i0; // no-warning } -void good_pop_front_list1(std::list &L, int n) { +void good_pop_front1_list(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.pop_front(); *i1; // no-warning } -void bad_pop_front_list1(std::list &L, int n) { +void bad_pop_front1_list(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.pop_front(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_front_deque1(std::deque &D, int n) { +void good_pop_front1_deque(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.pop_front(); *i1; // no-warning } -void bad_pop_front_deque1(std::deque &D, int n) { +void bad_pop_front1_deque(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.pop_front(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_pop_front_forward_list1(std::forward_list &FL, int n) { +void good_pop_front1_forward_list(std::forward_list &FL, int n) { auto i1 = FL.cbegin(), i0 = i1++; FL.pop_front(); *i1; // no-warning } -void bad_pop_front_forward_list1(std::forward_list &FL, int n) { +void bad_pop_front1_forward_list(std::forward_list &FL, int n) { auto i1 = FL.cbegin(), i0 = i1++; FL.pop_front(); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_insert_list1(std::list &L, int n) { +void good_insert1_list1(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.insert(i1, n); *i0; // no-warning *i1; // no-warning } -void good_insert_vector1(std::vector &V, int n) { +void good_insert1_list2(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, n); + *i1; // no-warning +} + +void good_insert1_vector1(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.insert(i1, n); *i0; // no-warning } -void bad_insert_vector1(std::vector &V, int n) { +void good_insert1_vector2(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, n); + *i1; // no-warning +} + +void bad_insert1_vector(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.insert(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_insert_deque1(std::deque &D, int n) { +void good_insert1_deque(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + i0 = D.insert(i1, n); + *i0; // no-warning +} + +void bad_insert1_deque1(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.insert(i1, n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert1_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_emplace_list1(std::list &L, int n) { +void good_insert2_list1(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + L.insert(i1, std::move(n)); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert2_list2(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, std::move(n)); + *i1; // no-warning +} + +void good_insert2_vector1(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, std::move(n)); + *i0; // no-warning +} + +void good_insert2_vector2(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, std::move(n)); + *i1; // no-warning +} + +void bad_insert2_vector(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, std::move(n)); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert2_deque(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + i1 = D.insert(i1, std::move(n)); + *i1; // no-warning +} + +void bad_insert2_deque1(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, std::move(n)); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert2_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, std::move(n)); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert3_list1(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + L.insert(i1, 10, n); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert3_list2(std::list &L, int n) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, 10, n); + *i1; // no-warning +} + +void good_insert3_vector1(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, 10, n); + *i0; // no-warning +} + +void good_insert3_vector2(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, 10, n); + *i1; // no-warning +} + +void bad_insert3_vector(std::vector &V, int n) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, 10, n); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert3_deque(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + i1 = D.insert(i1, 10, std::move(n)); + *i1; // no-warning +} + +void bad_insert3_deque1(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, 10, std::move(n)); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert3_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, 10, std::move(n)); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert4_list1(std::list &L1, std::list &L2, int n) { + auto i1 = L1.cbegin(), i0 = i1++; + L1.insert(i1, L2.cbegin(), L2.cend()); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert4_list2(std::list &L1, std::list &L2, int n) { + auto i1 = L1.cbegin(), i0 = i1++; + i1 = L1.insert(i1, L2.cbegin(), L2.cend()); + *i1; // no-warning +} + +void good_insert4_vector1(std::vector &V1, std::vector &V2, int n) { + auto i1 = V1.cbegin(), i0 = i1++; + V1.insert(i1, V2.cbegin(), V2.cend()); + *i0; // no-warning +} + +void good_insert4_vector2(std::vector &V1, std::vector &V2, int n) { + auto i1 = V1.cbegin(), i0 = i1++; + i1 = V1.insert(i1, V2.cbegin(), V2.cend()); + *i1; // no-warning +} + +void bad_insert4_vector(std::vector &V1, std::vector &V2, int n) { + auto i1 = V1.cbegin(), i0 = i1++; + V1.insert(i1, V2.cbegin(), V2.cend()); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert4_deque(std::deque &D1, std::deque &D2, int n) { + auto i1 = D1.cbegin(), i0 = i1++; + i1 = D1.insert(i1, D2.cbegin(), D2.cend()); + *i1; // no-warning +} + +void bad_insert4_deque1(std::deque &D1, std::deque &D2, int n) { + auto i1 = D1.cbegin(), i0 = i1++; + D1.insert(i1, D2.cbegin(), D2.cend()); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert4_deque2(std::deque &D1, std::deque &D2, int n) { + auto i1 = D1.cbegin(), i0 = i1++; + D1.insert(i1, D2.cbegin(), D2.cend()); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert5_list1(std::list &L) { + auto i1 = L.cbegin(), i0 = i1++; + L.insert(i1, {1, 2, 3, 4}); + *i0; // no-warning + *i1; // no-warning +} + +void good_insert5_list2(std::list &L) { + auto i1 = L.cbegin(), i0 = i1++; + i1 = L.insert(i1, {1, 2, 3, 4}); + *i1; // no-warning +} + +void good_insert5_vector1(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, {1, 2, 3, 4}); + *i0; // no-warning +} + +void good_insert5_vector2(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + i1 = V.insert(i1, {1, 2, 3, 4}); + *i1; // no-warning +} + +void bad_insert5_vector(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + V.insert(i1, {1, 2, 3, 4}); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_insert5_deque(std::deque &D) { + auto i1 = D.cbegin(), i0 = i1++; + i1 = D.insert(i1, {1, 2, 3, 4}); + *i1; // no-warning +} + +void bad_insert5_deque1(std::deque &D) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, {1, 2, 3, 4}); + *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_insert5_deque2(std::deque &D) { + auto i1 = D.cbegin(), i0 = i1++; + D.insert(i1, {1, 2, 3, 4}); + *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void good_emplace1_list(std::list &L, int n) { auto i1 = L.cbegin(), i0 = i1++; L.emplace(i1, n); *i0; // no-warning *i1; // no-warning } -void good_emplace_vector1(std::vector &V, int n) { +void good_emplace1_vector(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.emplace(i1, n); *i0; // no-warning } -void bad_emplace_vector1(std::vector &V, int n) { +void bad_emplace1_vector(std::vector &V, int n) { auto i1 = V.cbegin(), i0 = i1++; V.emplace(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_emplace_deque1(std::deque &D, int n) { +void bad_emplace1_deque1(std::deque &D, int n) { auto i1 = D.cbegin(), i0 = i1++; D.emplace(i1, n); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_emplace1_deque2(std::deque &D, int n) { + auto i1 = D.cbegin(), i0 = i1++; + D.emplace(i1, n); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_list1(std::list &L) { +void good_erase1_list1(std::list &L) { auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; L.erase(i1); *i0; // no-warning *i2; // no-warning } -void bad_erase_list1(std::list &L) { +void good_erase1_list2(std::list &L) { + auto i0 = L.cbegin(); + i0 = L.erase(i0); + *i0; // no-warning +} + +void bad_erase1_list(std::list &L) { auto i0 = L.cbegin(); L.erase(i0); *i0; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_vector1(std::vector &V) { +void good_erase1_vector1(std::vector &V) { auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; V.erase(i1); *i0; // no-warning } -void bad_erase_vector1(std::vector &V) { +void good_erase1_vector2(std::vector &V) { + auto i0 = V.cbegin(); + i0 = V.erase(i0); + *i0; // no-warning +} + +void bad_erase1_vector1(std::vector &V) { auto i1 = V.cbegin(), i0 = i1++; V.erase(i0); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase1_vector2(std::vector &V) { + auto i1 = V.cbegin(), i0 = i1++; + V.erase(i0); *i1; // expected-warning{{Invalidated iterator accessed}} } -void bad_erase_deque1(std::deque &D) { +void good_erase1_deque(std::deque &D) { + auto i0 = D.cbegin(); + i0 = D.erase(i0); + *i0; // no-warning +} + +void bad_erase1_deque1(std::deque &D) { auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; D.erase(i1); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase1_deque2(std::deque &D) { + auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; + D.erase(i1); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase1_deque3(std::deque &D) { + auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; + D.erase(i1); *i2; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_list2(std::list &L) { +void good_erase2_list1(std::list &L) { auto i3 = L.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; L.erase(i1, i3); *i0; // no-warning *i3; // no-warning } -void bad_erase_list2(std::list &L) { +void good_erase2_list2(std::list &L) { + auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; + i0 = L.erase(i0, i2); + *i0; // no-warning +} + +void bad_erase2_list1(std::list &L) { auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; L.erase(i0, i2); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_list2(std::list &L) { + auto i2 = L.cbegin(), i0 = i2++, i1 = i2++; + L.erase(i0, i2); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_vector2(std::vector &V) { +void good_erase2_vector1(std::vector &V) { auto i3 = V.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++;; V.erase(i1, i3); *i0; // no-warning } -void bad_erase_vector2(std::vector &V) { +void good_erase2_vector2(std::vector &V) { + auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; + i0 = V.erase(i0, i2); + *i0; // no-warning +} + +void bad_erase2_vector1(std::vector &V) { auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; V.erase(i0, i2); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_vector2(std::vector &V) { + auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; + V.erase(i0, i2); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_vector3(std::vector &V) { + auto i2 = V.cbegin(), i0 = i2++, i1 = i2++; + V.erase(i0, i2); *i2; // expected-warning{{Invalidated iterator accessed}} } -void bad_erase_deque2(std::deque &D) { +void good_erase2_deque(std::deque &D) { + auto i2 = D.cbegin(), i0 = i2++, i1 = i2++; + i0 = D.erase(i0, i2); + *i0; // no-warning +} + +void bad_erase2_deque1(std::deque &D) { auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; D.erase(i1, i3); *i0; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_deque2(std::deque &D) { + auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + D.erase(i1, i3); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_deque3(std::deque &D) { + auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + D.erase(i1, i3); *i2; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase2_deque4(std::deque &D) { + auto i3 = D.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + D.erase(i1, i3); *i3; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_after_forward_list1(std::forward_list &FL) { +void good_erase_after1_forward_list1(std::forward_list &FL) { auto i2 = FL.cbegin(), i0 = i2++, i1 = i2++; FL.erase_after(i0); *i0; // no-warning *i2; // no-warning } -void bad_erase_after_forward_list1(std::forward_list &FL) { +void good_erase_after1_forward_lis2(std::forward_list &FL) { + auto i1 = FL.cbegin(), i0 = i1++; + i1 = FL.erase_after(i0); + *i1; // no-warning +} + +void bad_erase_after1_forward_list(std::forward_list &FL) { auto i1 = FL.cbegin(), i0 = i1++; FL.erase_after(i0); *i1; // expected-warning{{Invalidated iterator accessed}} } -void good_erase_after_forward_list2(std::forward_list &FL) { +void good_erase_after2_forward_list1(std::forward_list &FL) { auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; FL.erase_after(i0, i3); *i0; // no-warning *i3; // no-warning } -void bad_erase_after_forward_list2(std::forward_list &FL) { +void good_erase_after2_forward_list2(std::forward_list &FL) { + auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + i2 = FL.erase_after(i0, i3); + *i2; // no-warning +} + +void bad_erase_after2_forward_list1(std::forward_list &FL) { auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; FL.erase_after(i0, i3); *i1; // expected-warning{{Invalidated iterator accessed}} +} + +void bad_erase_after2_forward_list2(std::forward_list &FL) { + auto i3 = FL.cbegin(), i0 = i3++, i1 = i3++, i2 = i3++; + FL.erase_after(i0, i3); *i2; // expected-warning{{Invalidated iterator accessed}} } diff --git a/clang/test/Analysis/iterator-range.cpp b/clang/test/Analysis/iterator-range.cpp index 6fc8939c8e846..93f69e47f563b 100644 --- a/clang/test/Analysis/iterator-range.cpp +++ b/clang/test/Analysis/iterator-range.cpp @@ -24,6 +24,7 @@ void simple_good_end_negated(const std::vector &v) { void simple_bad_end(const std::vector &v) { auto i = v.end(); *i; // expected-warning{{Past-the-end iterator dereferenced}} + clang_analyzer_warnIfReached(); } void copy(const std::vector &v) { @@ -236,3 +237,8 @@ void good_derived(simple_container c) { *i0; // no-warning } } + +void iter_diff(std::vector &V) { + auto i0 = V.begin(), i1 = V.end(); + ptrdiff_t len = i1 - i0; // no-crash +} diff --git a/clang/test/Analysis/kmalloc-linux.c b/clang/test/Analysis/kmalloc-linux.c index bac71388a7a81..f183446bc0638 100644 --- a/clang/test/Analysis/kmalloc-linux.c +++ b/clang/test/Analysis/kmalloc-linux.c @@ -24,7 +24,7 @@ void test_zeroed() { t = list[i]; foo(t); } - free(list); // no-warning + kfree(list); // no-warning } void test_nonzero() { @@ -39,7 +39,7 @@ void test_nonzero() { t = list[i]; // expected-warning{{undefined}} foo(t); } - free(list); + kfree(list); } void test_indeterminate(int flags) { @@ -54,5 +54,5 @@ void test_indeterminate(int flags) { t = list[i]; // expected-warning{{undefined}} foo(t); } - free(list); + kfree(list); } diff --git a/clang/test/Analysis/loop-block-counts.c b/clang/test/Analysis/loop-block-counts.c index 04a3f747c2ee9..66bb850780cb1 100644 --- a/clang/test/Analysis/loop-block-counts.c +++ b/clang/test/Analysis/loop-block-counts.c @@ -12,7 +12,7 @@ void loop() { for (int i = 0; i < 2; ++i) callee(&arr[i]); // FIXME: Should be UNKNOWN. - clang_analyzer_eval(arr[0] == arr[1]); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0] == arr[1]); // expected-warning{{FALSE}} } void loopWithCall() { diff --git a/clang/test/Analysis/loop-unrolling.cpp b/clang/test/Analysis/loop-unrolling.cpp index b7375df38b695..761bf5af6a8b0 100644 --- a/clang/test/Analysis/loop-unrolling.cpp +++ b/clang/test/Analysis/loop-unrolling.cpp @@ -347,9 +347,9 @@ int simple_known_bound_loop() { int simple_unknown_bound_loop() { for (int i = 2; i < getNum(); i++) { #ifdef DFS - clang_analyzer_numTimesReached(); // expected-warning {{10}} + clang_analyzer_numTimesReached(); // expected-warning {{16}} #else - clang_analyzer_numTimesReached(); // expected-warning {{13}} + clang_analyzer_numTimesReached(); // expected-warning {{8}} #endif } return 0; @@ -368,10 +368,10 @@ int nested_inlined_unroll1() { int nested_inlined_no_unroll1() { int k; for (int i = 0; i < 9; i++) { -#ifdef ANALYZER_CM_Z3 - clang_analyzer_numTimesReached(); // expected-warning {{13}} +#ifdef DFS + clang_analyzer_numTimesReached(); // expected-warning {{18}} #else - clang_analyzer_numTimesReached(); // expected-warning {{15}} + clang_analyzer_numTimesReached(); // expected-warning {{14}} #endif k = simple_unknown_bound_loop(); // reevaluation without inlining, splits the state as well } diff --git a/clang/test/Analysis/loop-widening.cpp b/clang/test/Analysis/loop-widening.cpp new file mode 100644 index 0000000000000..fbcb72dee160a --- /dev/null +++ b/clang/test/Analysis/loop-widening.cpp @@ -0,0 +1,27 @@ +// RUN: %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-config widen-loops=true \ +// RUN: -analyzer-config track-conditions=false \ +// RUN: -analyzer-max-loop 2 -analyzer-output=text + +namespace pr43102 { +class A { +public: + void m_fn1(); +}; +bool g; +void fn1() { + A a; + A *b = &a; + + for (;;) { // expected-note{{Loop condition is true. Entering loop body}} + // expected-note@-1{{Loop condition is true. Entering loop body}} + // expected-note@-2{{Value assigned to 'b'}} + // no crash during bug report construction + + g = !b; // expected-note{{Assuming 'b' is null}} + b->m_fn1(); // expected-warning{{Called C++ object pointer is null}} + // expected-note@-1{{Called C++ object pointer is null}} + } +} +} // end of namespace pr43102 diff --git a/clang/test/Analysis/main.c b/clang/test/Analysis/main.c new file mode 100644 index 0000000000000..aa4c506096002 --- /dev/null +++ b/clang/test/Analysis/main.c @@ -0,0 +1,32 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +int x = 1; + +struct { + int a, b; +} s = {2, 3}; + +int arr[] = {4, 5, 6}; + +void clang_analyzer_eval(int); + +int main() { + // In main() we know that the initial values are still valid. + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} + clang_analyzer_eval(s.a == 2); // expected-warning{{TRUE}} + clang_analyzer_eval(s.b == 3); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[0] == 4); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[1] == 5); // expected-warning{{TRUE}} + clang_analyzer_eval(arr[2] == 6); // expected-warning{{TRUE}} + return 0; +} + +void foo() { + // In other functions these values may already be overwritten. + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.a == 2); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.b == 3); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[0] == 4); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[1] == 5); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[2] == 6); // expected-warning{{TRUE}} // expected-warning{{FALSE}} +} diff --git a/clang/test/Analysis/main.cpp b/clang/test/Analysis/main.cpp new file mode 100644 index 0000000000000..c230c389cadb3 --- /dev/null +++ b/clang/test/Analysis/main.cpp @@ -0,0 +1,22 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection -verify %s + +int x = 1; + +struct { + int a, b; +} s = {2, 3}; + +int arr[] = {4, 5, 6}; + +void clang_analyzer_eval(int); + +int main() { + // Do not trust global initializers in C++. + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.a == 2); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(s.b == 3); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[0] == 4); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[1] == 5); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + clang_analyzer_eval(arr[2] == 6); // expected-warning{{TRUE}} // expected-warning{{FALSE}} + return 0; +} diff --git a/clang/test/Analysis/malloc.mm b/clang/test/Analysis/malloc.mm index d7bfbf3f34f33..e84644b9dd732 100644 --- a/clang/test/Analysis/malloc.mm +++ b/clang/test/Analysis/malloc.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify -fblocks %s +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,unix.Malloc -analyzer-store=region -verify -fblocks %s #import "Inputs/system-header-simulator-objc.h" #import "Inputs/system-header-simulator-for-malloc.h" diff --git a/clang/test/Analysis/mig.mm b/clang/test/Analysis/mig.mm index 0c7d729e9375d..e8d08f355d3ea 100644 --- a/clang/test/Analysis/mig.mm +++ b/clang/test/Analysis/mig.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,osx.MIG\ +// RUN: %clang_analyze_cc1 -w -analyzer-checker=core,osx.MIG -std=c++14 \ // RUN: -analyzer-output=text -fblocks -verify %s typedef unsigned uint32_t; diff --git a/clang/test/Analysis/misc-ps-region-store.m b/clang/test/Analysis/misc-ps-region-store.m index 1ef1005631263..d1011bda1615e 100644 --- a/clang/test/Analysis/misc-ps-region-store.m +++ b/clang/test/Analysis/misc-ps-region-store.m @@ -1205,7 +1205,7 @@ static void RDar8424269_B(RDar8424269_A *p, unsigned char *RDar8424269_D, void rdar_8642434_funcB(struct rdar_8642434_typeA *x, struct rdar_8642434_typeA *y) { rdar_8642434_funcA(x); if (!y) - rdar_8642434_funcA(y); // expected-warning{{Null pointer passed as an argument to a 'nonnull' parameter}} + rdar_8642434_funcA(y); // expected-warning{{Null pointer passed to 1st parameter expecting 'nonnull'}} } // - Handle loads and stores from a symbolic index diff --git a/clang/test/Analysis/missing-bind-temporary.cpp b/clang/test/Analysis/missing-bind-temporary.cpp index 7be4e2ddf3007..18204929bcbe0 100644 --- a/clang/test/Analysis/missing-bind-temporary.cpp +++ b/clang/test/Analysis/missing-bind-temporary.cpp @@ -31,7 +31,7 @@ class B { // CHECK-NEXT: 8: [B1.7] // CHECK-NEXT: 9: [B1.5] = [B1.8] (OperatorCall) // CHECK-NEXT: 10: ~variant_0::B() (Temporary object destructor) -// CHECK-NEXT: 11: [B1.2].~B() (Implicit destructor) +// CHECK-NEXT: 11: [B1.2].~variant_0::B() (Implicit destructor) void foo(int) { B i; i = {}; @@ -71,7 +71,7 @@ class B { // CHECK-NEXT: 6: {} (CXXConstructExpr, class variant_1::B) // CHECK-NEXT: 7: [B1.6] // CHECK-NEXT: 8: [B1.5] = [B1.7] (OperatorCall) -// CHECK-NEXT: 9: [B1.2].~B() (Implicit destructor) +// CHECK-NEXT: 9: [B1.2].~variant_1::B() (Implicit destructor) template void foo(T) { B i; i = {}; @@ -114,7 +114,7 @@ class B { // CHECK-NEXT: 9: [B1.8] // CHECK-NEXT: 10: [B1.5] = [B1.9] (OperatorCall) // CHECK-NEXT: 11: ~variant_2::B() (Temporary object destructor) -// CHECK-NEXT: 12: [B1.2].~B() (Implicit destructor) +// CHECK-NEXT: 12: [B1.2].~variant_2::B() (Implicit destructor) template void foo(T) { B i; i = {}; diff --git a/clang/test/Analysis/more-dtors-cfg-output.cpp b/clang/test/Analysis/more-dtors-cfg-output.cpp new file mode 100644 index 0000000000000..c0df5953aa6e1 --- /dev/null +++ b/clang/test/Analysis/more-dtors-cfg-output.cpp @@ -0,0 +1,317 @@ +// RUN: rm -f %t.14 %t.2a +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++14 -DCXX2A=0 -fblocks -Wall -Wno-unused -Werror %s > %t.14 2>&1 +// RUN: %clang_analyze_cc1 -analyzer-checker=debug.DumpCFG -std=c++2a -DCXX2A=1 -fblocks -Wall -Wno-unused -Werror %s > %t.2a 2>&1 +// RUN: FileCheck --input-file=%t.14 -check-prefixes=CHECK,CXX14 -implicit-check-not=destructor %s +// RUN: FileCheck --input-file=%t.2a -check-prefixes=CHECK,CXX2A -implicit-check-not=destructor %s + +int puts(const char *); + +struct Foo { + Foo() = delete; +#if CXX2A + // Guarantee that the elided examples are actually elided by deleting the + // copy constructor. + Foo(const Foo &) = delete; +#else + // No elision support, so we need a copy constructor. + Foo(const Foo &); +#endif + ~Foo(); +}; + +struct TwoFoos { + Foo foo1, foo2; + ~TwoFoos(); +}; + +Foo get_foo(); + +struct Bar { + Bar(); + Bar(const Bar &); + ~Bar(); + Bar &operator=(const Bar &); +}; + +Bar get_bar(); + +struct TwoBars { + Bar foo1, foo2; + ~TwoBars(); +}; + +// Start of tests: + +void elided_assign() { + Foo x = get_foo(); +} +// CHECK: void elided_assign() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Implicit destructor) + +void nonelided_assign() { + Bar x = (const Bar &)get_bar(); +} +// CHECK: void nonelided_assign() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Implicit destructor) + +void elided_paren_init() { + Foo x(get_foo()); +} +// CHECK: void elided_paren_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Implicit destructor) + +void nonelided_paren_init() { + Bar x((const Bar &)get_bar()); +} +// CHECK: void nonelided_paren_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Implicit destructor) + +void elided_brace_init() { + Foo x{get_foo()}; +} +// CHECK: void elided_brace_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Implicit destructor) + +void nonelided_brace_init() { + Bar x{(const Bar &)get_bar()}; +} +// CHECK: void nonelided_brace_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Implicit destructor) + +void elided_lambda_capture_init() { + // The copy from get_foo() into the lambda should be elided. Should call + // the lambda's destructor, but not ~Foo() separately. + // (This syntax is C++14 'generalized lambda captures'.) + auto z = [x=get_foo()]() {}; +} +// CHECK: void elided_lambda_capture_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~(lambda at {{.*}})() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~(lambda at {{.*}})() (Implicit destructor) + +void nonelided_lambda_capture_init() { + // Should call the lambda's destructor as well as ~Bar() for the temporary. + auto z = [x((const Bar &)get_bar())]() {}; +} +// CHECK: void nonelided_lambda_capture_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CXX14: ~(lambda at {{.*}})() (Temporary object destructor) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~(lambda at {{.*}})() (Implicit destructor) + +Foo elided_return_stmt_expr() { + // Two copies, both elided in C++17. + return ({ get_foo(); }); +} +// CHECK: Foo elided_return_stmt_expr() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) + +void elided_stmt_expr() { + // One copy, elided in C++17. + ({ get_foo(); }); +} +// CHECK: void elided_stmt_expr() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + + +void elided_stmt_expr_multiple_stmts() { + // Make sure that only the value returned out of a statement expression is + // elided. + ({ get_bar(); get_foo(); }); +} +// CHECK: void elided_stmt_expr_multiple_stmts() +// CHECK: ~Bar() (Temporary object destructor) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + + +void unelided_stmt_expr() { + ({ (const Bar &)get_bar(); }); +} +// CHECK: void unelided_stmt_expr() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Temporary object destructor) + +void elided_aggregate_init() { + TwoFoos x{get_foo(), get_foo()}; +} +// CHECK: void elided_aggregate_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~TwoFoos() (Implicit destructor) + +void nonelided_aggregate_init() { + TwoBars x{(const Bar &)get_bar(), (const Bar &)get_bar()}; +} +// CHECK: void nonelided_aggregate_init() +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: (CXXConstructExpr{{.*}}, struct Bar) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~Bar() (Temporary object destructor) +// CHECK: ~TwoBars() (Implicit destructor) + +TwoFoos return_aggregate_init() { + return TwoFoos{get_foo(), get_foo()}; +} +// CHECK: TwoFoos return_aggregate_init() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~TwoFoos() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) + +void lifetime_extended() { + const Foo &x = (get_foo(), get_foo()); + puts("one destroyed before, one destroyed after"); +} +// CHECK: void lifetime_extended() +// CHECK: ~Foo() (Temporary object destructor) +// CHECK: one destroyed before, one destroyed after +// CHECK: ~Foo() (Implicit destructor) + +void not_lifetime_extended() { + Foo x = (get_foo(), get_foo()); + puts("one destroyed before, one destroyed after"); +} +// CHECK: void not_lifetime_extended() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CHECK: ~Foo() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: one destroyed before, one destroyed after +// CHECK: ~Foo() (Implicit destructor) + +void compound_literal() { + (void)(Bar[]){{}, {}}; +} +// CHECK: void compound_literal() +// CHECK: (CXXConstructExpr, struct Bar) +// CHECK: (CXXConstructExpr, struct Bar) +// CHECK: ~Bar [2]() (Temporary object destructor) + +Foo elided_return() { + return get_foo(); +} +// CHECK: Foo elided_return() +// CXX14: (CXXConstructExpr{{.*}}, struct Foo) +// CXX14: ~Foo() (Temporary object destructor) + +auto elided_return_lambda() { + return [x=get_foo()]() {}; +} +// CHECK: (lambda at {{.*}}) elided_return_lambda() +// CXX14: (CXXConstructExpr{{.*}}, class (lambda at {{.*}})) +// CXX14: ~(lambda at {{.*}})() (Temporary object destructor) +// CXX14: ~Foo() (Temporary object destructor) + +void const_auto_obj() { + const Bar bar; +} +// CHECK: void const_auto_obj() +// CHECK: .~Bar() (Implicit destructor) + +void has_default_arg(Foo foo = get_foo()); +void test_default_arg() { + // FIXME: This emits a destructor but no constructor. Search CFG.cpp for + // 'PR13385' for details. + has_default_arg(); +} +// CHECK: void test_default_arg() +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + +struct DefaultArgInCtor { + DefaultArgInCtor(Foo foo = get_foo()); + ~DefaultArgInCtor(); +}; + +void default_ctor_with_default_arg() { + // FIXME: Default arguments are mishandled in two ways: + // - The constructor is not emitted at all (not specific to arrays; see fixme + // in CFG.cpp that mentions PR13385). + // - The destructor is emitted once, even though the default argument will be + // constructed and destructed once per array element. + // Ideally, the CFG would expand array constructions into a loop that + // constructs each array element, allowing default argument + // constructor/destructor calls to be correctly placed inside the loop. + DefaultArgInCtor qux[3]; +} +// CHECK: void default_ctor_with_default_arg() +// CHECK: CXXConstructExpr, {{.*}}, struct DefaultArgInCtor [3] +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) +// CHECK: .~DefaultArgInCtor [3]() (Implicit destructor) + +void new_default_ctor_with_default_arg(long count) { + // Same problems as above. + new DefaultArgInCtor[count]; +} +// CHECK: void new_default_ctor_with_default_arg(long count) +// CHECK: CXXConstructExpr, {{.*}}, struct DefaultArgInCtor [] +// CXX14: ~Foo() (Temporary object destructor) +// CHECK: ~Foo() (Temporary object destructor) + +#if CXX2A +// Boilerplate needed to test co_return: + +namespace std::experimental { + template + struct coroutine_handle { + static coroutine_handle from_address(void *); + }; +} + +struct TestPromise { + TestPromise initial_suspend(); + TestPromise final_suspend(); + bool await_ready(); + void await_suspend(const std::experimental::coroutine_handle &); + void await_resume(); + Foo return_value(const Bar &); + Bar get_return_object(); + void unhandled_exception(); +}; + +namespace std::experimental { + template + struct coroutine_traits; + template <> + struct coroutine_traits { + using promise_type = TestPromise; + }; +} + +Bar coreturn() { + co_return get_bar(); + // This expands to something like: + // promise.return_value(get_bar()); + // get_bar() is passed by reference to return_value() and is then destroyed; + // there is no equivalent of RVO. TestPromise::return_value also returns a + // Foo, which should be immediately destroyed. + // FIXME: The generated CFG completely ignores get_return_object(). +} +// CXX2A: Bar coreturn() +// CXX2A: ~Foo() (Temporary object destructor) +// CXX2A: ~Bar() (Temporary object destructor) +#endif diff --git a/clang/test/Analysis/new-ctor-null-throw.cpp b/clang/test/Analysis/new-ctor-null-throw.cpp index dfa7cba763fcf..28922c0fad4b7 100644 --- a/clang/test/Analysis/new-ctor-null-throw.cpp +++ b/clang/test/Analysis/new-ctor-null-throw.cpp @@ -1,7 +1,7 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core \ // RUN: -analyzer-config suppress-null-return-paths=false \ // RUN: -verify %s -// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core \ // RUN: -DSUPPRESSED \ // RUN: -verify %s diff --git a/clang/test/Analysis/new-ctor-null.cpp b/clang/test/Analysis/new-ctor-null.cpp index 32f2f9500cce3..f3c07e2123731 100644 --- a/clang/test/Analysis/new-ctor-null.cpp +++ b/clang/test/Analysis/new-ctor-null.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 \ +// RUN: %clang_analyze_cc1 -std=c++14 \ // RUN: -analyzer-checker=core,debug.ExprInspection \ // RUN: -verify %s diff --git a/clang/test/Analysis/nonnull-global-constants.mm b/clang/test/Analysis/nonnull-global-constants.mm index 7900b9dd1286a..9e1a588ba47a8 100644 --- a/clang/test/Analysis/nonnull-global-constants.mm +++ b/clang/test/Analysis/nonnull-global-constants.mm @@ -101,3 +101,15 @@ void testNonnullBool() { void testNonnullNonconstBool() { clang_analyzer_eval(kBoolMutable); // expected-warning{{UNKNOWN}} } + +// If it's annotated as nonnull, it doesn't even need to be const. +extern CFStringRef _Nonnull str3; +void testNonnullNonconstCFString() { + clang_analyzer_eval(str3); // expected-warning{{TRUE}} +} + +// This one's nonnull for two reasons. +extern const CFStringRef _Nonnull str4; +void testNonnullNonnullCFString() { + clang_analyzer_eval(str4); // expected-warning{{TRUE}} +} diff --git a/clang/test/Analysis/null-deref-ps.c b/clang/test/Analysis/null-deref-ps.c index d0e1f9f5cc330..d1c19e533deb4 100644 --- a/clang/test/Analysis/null-deref-ps.c +++ b/clang/test/Analysis/null-deref-ps.c @@ -88,21 +88,21 @@ int f5() { int bar(int* p, int q) __attribute__((nonnull)); int f6(int *p) { - return !p ? bar(p, 1) // expected-warning {{Null pointer passed as an argument to a 'nonnull' parameter}} + return !p ? bar(p, 1) // expected-warning {{Null pointer passed to 1st parameter expecting 'nonnull'}} : bar(p, 0); // no-warning } int bar2(int* p, int q) __attribute__((nonnull(1))); int f6b(int *p) { - return !p ? bar2(p, 1) // expected-warning {{Null pointer passed as an argument to a 'nonnull' parameter}} + return !p ? bar2(p, 1) // expected-warning {{Null pointer passed to 1st parameter expecting 'nonnull'}} : bar2(p, 0); // no-warning } int bar3(int*p, int q, int *r) __attribute__((nonnull(1,3))); int f6c(int *p, int *q) { - return !p ? bar3(q, 2, p) // expected-warning {{Null pointer passed as an argument to a 'nonnull' parameter}} + return !p ? bar3(q, 2, p) // expected-warning {{Null pointer passed to 3rd parameter expecting 'nonnull'}} : bar3(p, 2, q); // no-warning } diff --git a/clang/test/Analysis/objc-arc.m b/clang/test/Analysis/objc-arc.m index cab6618a4362a..7127232f0de5c 100644 --- a/clang/test/Analysis/objc-arc.m +++ b/clang/test/Analysis/objc-arc.m @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,osx.cocoa.RetainCount,deadcode -verify -fblocks -analyzer-opt-analyze-nested-blocks -fobjc-arc -analyzer-output=plist-multi-file -o %t.plist %s +// RUN: %clang_analyze_cc1 -triple x86_64-apple-darwin10 -analyzer-checker=core,osx.cocoa.RetainCount,deadcode -verify -fblocks -analyzer-opt-analyze-nested-blocks -fobjc-arc -analyzer-output=plist-multi-file -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t.plist %s // RUN: %normalize_plist <%t.plist | diff -ub %S/Inputs/expected-plists/objc-arc.m.plist - typedef signed char BOOL; diff --git a/clang/test/Analysis/os_object_base.h b/clang/test/Analysis/os_object_base.h index cd59e4f0bca09..4698185f2b3cb 100644 --- a/clang/test/Analysis/os_object_base.h +++ b/clang/test/Analysis/os_object_base.h @@ -12,16 +12,23 @@ #define OSDynamicCast(type, inst) \ ((type *) OSMetaClassBase::safeMetaCast((inst), OSTypeID(type))) +#define OSRequiredCast(type, inst) \ + ((type *) OSMetaClassBase::requiredMetaCast((inst), OSTypeID(type))) #define OSTypeAlloc(type) ((type *) ((type::metaClass)->alloc())) using size_t = decltype(sizeof(int)); +typedef int kern_return_t; +struct IORPC {}; + struct OSMetaClass; struct OSMetaClassBase { static OSMetaClassBase *safeMetaCast(const OSMetaClassBase *inst, const OSMetaClass *meta); + static OSMetaClassBase *requiredMetaCast(const OSMetaClassBase *inst, + const OSMetaClass *meta); OSMetaClassBase *metaCast(const char *toMeta); @@ -33,8 +40,13 @@ struct OSMetaClassBase { virtual void free(); virtual ~OSMetaClassBase(){}; + + kern_return_t Invoke(IORPC invoke); }; +typedef kern_return_t (*OSDispatchMethod)(OSMetaClassBase *self, + const IORPC rpc); + struct OSObject : public OSMetaClassBase { virtual ~OSObject(){} diff --git a/clang/test/Analysis/osobject-retain-release.cpp b/clang/test/Analysis/osobject-retain-release.cpp index 10ef144bf36e9..42675fc70e785 100644 --- a/clang/test/Analysis/osobject-retain-release.cpp +++ b/clang/test/Analysis/osobject-retain-release.cpp @@ -1,9 +1,11 @@ -// RUN: %clang_analyze_cc1 -fblocks -analyze -analyzer-output=text\ -// RUN: -analyzer-checker=core,osx -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -fblocks -analyze -analyzer-output=text\ +// RUN: -analyzer-checker=core,osx,debug.ExprInspection -verify %s #include "os_object_base.h" #include "os_smart_ptr.h" +void clang_analyzer_eval(bool); + struct OSIterator : public OSObject { static const OSMetaClass * const metaClass; }; @@ -483,6 +485,23 @@ void check_dynamic_cast() { arr->release(); } +void check_required_cast() { + OSArray *arr = OSRequiredCast(OSArray, OSObject::generateObject(1)); + arr->release(); // no-warning +} + +void check_cast_behavior(OSObject *obj) { + OSArray *arr1 = OSDynamicCast(OSArray, obj); + clang_analyzer_eval(arr1 == obj); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} + // expected-note@-2{{Assuming 'arr1' is not equal to 'obj'}} + // expected-warning@-3{{FALSE}} + // expected-note@-4 {{FALSE}} + OSArray *arr2 = OSRequiredCast(OSArray, obj); + clang_analyzer_eval(arr2 == obj); // expected-warning{{TRUE}} + // expected-note@-1{{TRUE}} +} + unsigned int check_dynamic_cast_no_null_on_orig(OSObject *obj) { OSArray *arr = OSDynamicCast(OSArray, obj); if (arr) { diff --git a/clang/test/Analysis/osobjectcstylecastchecker_test.cpp b/clang/test/Analysis/osobjectcstylecastchecker_test.cpp index 07f878cd39d55..fabed7ee34b1b 100644 --- a/clang/test/Analysis/osobjectcstylecastchecker_test.cpp +++ b/clang/test/Analysis/osobjectcstylecastchecker_test.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=optin.osx.OSObjectCStyleCast %s -verify +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=optin.osx.OSObjectCStyleCast %s -verify #include "os_object_base.h" struct OSArray : public OSObject { diff --git a/clang/test/Analysis/plist-macros-with-expansion-ctu.c b/clang/test/Analysis/plist-macros-with-expansion-ctu.c new file mode 100644 index 0000000000000..193b8f37480e7 --- /dev/null +++ b/clang/test/Analysis/plist-macros-with-expansion-ctu.c @@ -0,0 +1,79 @@ +// RUN: rm -rf %t && mkdir %t +// RUN: mkdir -p %t/ctudir +// RUN: %clang_cc1 -emit-pch -o %t/ctudir/plist-macros-ctu.c.ast %S/Inputs/plist-macros-ctu.c +// RUN: cp %S/Inputs/plist-macros-with-expansion-ctu.c.externalDefMap.txt %t/ctudir/externalDefMap.txt + +// RUN: %clang_analyze_cc1 -analyzer-checker=core \ +// RUN: -analyzer-config experimental-enable-naive-ctu-analysis=true \ +// RUN: -analyzer-config ctu-dir=%t/ctudir \ +// RUN: -analyzer-config expand-macros=true \ +// RUN: -analyzer-output=plist-multi-file -o %t.plist -verify %s + +// Check the macro expansions from the plist output here, to make the test more +// understandable. +// RUN: FileCheck --input-file=%t.plist %s + +extern void F1(int **); +extern void F2(int **); +extern void F3(int **); +extern void F_H(int **); + +void test0() { + int *X; + F3(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM1 +// CHECK-NEXT: expansion*Z = (int *)0 + + +void test1() { + int *X; + F1(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM +// CHECK-NEXT: expansion*X = (int *)0 + +void test2() { + int *X; + F2(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM +// CHECK-NEXT: expansion*Y = (int *)0 + +#define M F1(&X) + +void test3() { + int *X; + M; + *X = 1; // expected-warning{{Dereference of null pointer}} +} +// CHECK: nameM +// CHECK-NEXT: expansionF1(&X) +// CHECK: nameM +// CHECK-NEXT: expansion*X = (int *)0 + +#undef M +#define M F2(&X) + +void test4() { + int *X; + M; + *X = 1; // expected-warning{{Dereference of null pointer}} +} + +// CHECK: nameM +// CHECK-NEXT: expansionF2(&X) +// CHECK: nameM +// CHECK-NEXT: expansion*Y = (int *)0 + +void test_h() { + int *X; + F_H(&X); + *X = 1; // expected-warning{{Dereference of null pointer}} +} + +// CHECK: nameM_H +// CHECK-NEXT: expansion*A = (int *)0 diff --git a/clang/test/Analysis/plist-macros-with-expansion.cpp b/clang/test/Analysis/plist-macros-with-expansion.cpp index e836c78b4bb35..e07747eaec74d 100644 --- a/clang/test/Analysis/plist-macros-with-expansion.cpp +++ b/clang/test/Analysis/plist-macros-with-expansion.cpp @@ -1,6 +1,6 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core -verify %s // -// RUN: %clang_analyze_cc1 -analyzer-checker=core %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core %s \ // RUN: -analyzer-output=plist -o %t.plist \ // RUN: -analyzer-config expand-macros=true // diff --git a/clang/test/Analysis/plist-output.m b/clang/test/Analysis/plist-output.m index 32916333cbfa0..553982b32fd84 100644 --- a/clang/test/Analysis/plist-output.m +++ b/clang/test/Analysis/plist-output.m @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-config eagerly-assume=false %s -analyzer-checker=osx.cocoa.RetainCount,deadcode.DeadStores,core -analyzer-output=plist -o %t.plist +// RUN: %clang_analyze_cc1 -analyzer-config eagerly-assume=false %s -analyzer-checker=osx.cocoa.RetainCount,deadcode.DeadStores,core -analyzer-output=plist -analyzer-config deadcode.DeadStores:ShowFixIts=true -o %t.plist // RUN: %normalize_plist <%t.plist | diff -ub %S/Inputs/expected-plists/plist-output.m.plist - void test_null_init(void) { diff --git a/clang/test/Analysis/ptr-iter.cpp b/clang/test/Analysis/ptr-iter.cpp index a35fae470a7ef..a94288cd1c8cc 100644 --- a/clang/test/Analysis/ptr-iter.cpp +++ b/clang/test/Analysis/ptr-iter.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 %s -analyzer-output=text -verify \ +// RUN: %clang_analyze_cc1 %s -std=c++14 -analyzer-output=text -verify \ // RUN: -analyzer-checker=core,alpha.nondeterminism.PointerIteration #include "Inputs/system-header-simulator-cxx.h" diff --git a/clang/test/Analysis/ptr-sort.cpp b/clang/test/Analysis/ptr-sort.cpp index a4f94817f13b3..d238b390bdc23 100644 --- a/clang/test/Analysis/ptr-sort.cpp +++ b/clang/test/Analysis/ptr-sort.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 %s -analyzer-output=text -verify \ +// RUN: %clang_analyze_cc1 %s -std=c++14 -analyzer-output=text -verify \ // RUN: -analyzer-checker=core,alpha.nondeterminism.PointerSorting #include "Inputs/system-header-simulator-cxx.h" diff --git a/clang/test/Analysis/retain-count-alloc.cpp b/clang/test/Analysis/retain-count-alloc.cpp new file mode 100644 index 0000000000000..472cbbf0705e2 --- /dev/null +++ b/clang/test/Analysis/retain-count-alloc.cpp @@ -0,0 +1,37 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,unix.Malloc \ +// RUN: -verify %s + +// expected-no-diagnostics: We do not model Integer Set Library's retain-count +// based allocation. If any of the parameters has an +// '__isl_' prefixed macro definition we escape every +// of them when we are about to 'free()' something. + +#define __isl_take +#define __isl_keep + +struct Object { int Ref; }; +void free(void *); + +Object *copyObj(__isl_keep Object *O) { + O->Ref++; + return O; +} + +void freeObj(__isl_take Object *O) { + if (--O->Ref > 0) + return; + + free(O); // Here we notice that the parameter contains '__isl_', escape it. +} + +void useAfterFree(__isl_take Object *A) { + if (!A) + return; + + Object *B = copyObj(A); + freeObj(B); + + A->Ref = 13; + // no-warning: 'Use of memory after it is freed' was here. +} diff --git a/clang/test/Analysis/return-value-guaranteed.cpp b/clang/test/Analysis/return-value-guaranteed.cpp new file mode 100644 index 0000000000000..2d04a264ad813 --- /dev/null +++ b/clang/test/Analysis/return-value-guaranteed.cpp @@ -0,0 +1,94 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,apiModeling.llvm.ReturnValue \ +// RUN: -analyzer-output=text -verify=class %s + +struct Foo { int Field; }; +bool problem(); +void doSomething(); + +// We predefined the return value of 'MCAsmParser::Error' as true and we cannot +// take the false-branches which leads to a "garbage value" false positive. +namespace test_classes { +struct MCAsmParser { + static bool Error(); +}; + +bool parseFoo(Foo &F) { + if (problem()) { + // class-note@-1 {{Assuming the condition is false}} + // class-note@-2 {{Taking false branch}} + return MCAsmParser::Error(); + } + + F.Field = 0; + // class-note@-1 {{The value 0 is assigned to 'F.Field'}} + return !MCAsmParser::Error(); + // class-note@-1 {{'MCAsmParser::Error' returns true}} + // class-note@-2 {{Returning zero, which participates in a condition later}} +} + +bool parseFile() { + Foo F; + if (parseFoo(F)) { + // class-note@-1 {{Calling 'parseFoo'}} + // class-note@-2 {{Returning from 'parseFoo'}} + // class-note@-3 {{Taking false branch}} + return true; + } + + if (F.Field == 0) { + // class-note@-1 {{Field 'Field' is equal to 0}} + // class-note@-2 {{Taking true branch}} + + // no-warning: "The left operand of '==' is a garbage value" was here. + doSomething(); + } + + (void)(1 / F.Field); + // class-warning@-1 {{Division by zero}} + // class-note@-2 {{Division by zero}} + return false; +} +} // namespace test_classes + + +// We predefined 'MCAsmParser::Error' as returning true, but now it returns +// false, which breaks our invariant. Test the notes. +namespace test_break { +struct MCAsmParser { + static bool Error() { + return false; // class-note {{'MCAsmParser::Error' returns false}} + // class-note@-1 {{Returning zero, which participates in a condition later}} + } +}; + +bool parseFoo(Foo &F) { + if (problem()) { + // class-note@-1 {{Assuming the condition is false}} + // class-note@-2 {{Taking false branch}} + return !MCAsmParser::Error(); + } + + F.Field = 0; + // class-note@-1 {{The value 0 is assigned to 'F.Field'}} + return MCAsmParser::Error(); + // class-note@-1 {{Calling 'MCAsmParser::Error'}} + // class-note@-2 {{Returning from 'MCAsmParser::Error'}} + // class-note@-3 {{Returning zero, which participates in a condition later}} +} + +bool parseFile() { + Foo F; + if (parseFoo(F)) { + // class-note@-1 {{Calling 'parseFoo'}} + // class-note@-2 {{Returning from 'parseFoo'}} + // class-note@-3 {{Taking false branch}} + return true; + } + + (void)(1 / F.Field); + // class-warning@-1 {{Division by zero}} + // class-note@-2 {{Division by zero}} + return false; +} +} // namespace test_classes diff --git a/clang/test/Analysis/rvo.cpp b/clang/test/Analysis/rvo.cpp new file mode 100644 index 0000000000000..7215fbbded461 --- /dev/null +++ b/clang/test/Analysis/rvo.cpp @@ -0,0 +1,25 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker core,cplusplus -std=c++14 \ +// RUN: -analyzer-checker debug.ExprInspection -verify %s + +void clang_analyzer_eval(bool); + +struct A { + int x; +}; + +A getA(); + +struct B { + int *p; + A a; + + B(int *p) : p(p), a(getA()) {} +}; + +void foo() { + B b1(nullptr); + clang_analyzer_eval(b1.p == nullptr); // expected-warning{{TRUE}} + B b2(new int); // No leak yet! + clang_analyzer_eval(b2.p == nullptr); // expected-warning{{FALSE}} + // expected-warning@-1{{Potential leak of memory pointed to by 'b2.p'}} +} diff --git a/clang/test/Analysis/scopes-cfg-output.cpp b/clang/test/Analysis/scopes-cfg-output.cpp index f8d84b60deb1d..4b6e2a92c53ad 100644 --- a/clang/test/Analysis/scopes-cfg-output.cpp +++ b/clang/test/Analysis/scopes-cfg-output.cpp @@ -38,7 +38,7 @@ extern const bool UV; // CHECK-NEXT: 3: A a[2]; // CHECK-NEXT: 4: (CXXConstructExpr, [B1.5], class A [0]) // CHECK-NEXT: 5: A b[0]; -// CHECK-NEXT: 6: [B1.3].~A() (Implicit destructor) +// CHECK-NEXT: 6: [B1.3].~A [2]() (Implicit destructor) // CHECK-NEXT: 7: CFGScopeEnd(a) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 @@ -810,7 +810,7 @@ void test_for_compound_and_break() { // CHECK-NEXT: 1: CFGScopeEnd(__end1) // CHECK-NEXT: 2: CFGScopeEnd(__begin1) // CHECK-NEXT: 3: CFGScopeEnd(__range1) -// CHECK-NEXT: 4: [B5.3].~A() (Implicit destructor) +// CHECK-NEXT: 4: [B5.3].~A [10]() (Implicit destructor) // CHECK-NEXT: 5: CFGScopeEnd(a) // CHECK-NEXT: Preds (1): B2 // CHECK-NEXT: Succs (1): B0 diff --git a/clang/test/Analysis/silence-checkers-and-packages-core-all.cpp b/clang/test/Analysis/silence-checkers-and-packages-core-all.cpp new file mode 100644 index 0000000000000..0805cfc877c28 --- /dev/null +++ b/clang/test/Analysis/silence-checkers-and-packages-core-all.cpp @@ -0,0 +1,39 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=core \ +// RUN: -verify %s + +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers="core.DivideZero;core.NullDereference" \ +// RUN: -verify %s + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=core.NullDeref \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-CHECKER-ERROR + +// CHECK-CHECKER-ERROR: (frontend): no analyzer checkers or packages +// CHECK-CHECKER-ERROR-SAME: are associated with 'core.NullDeref' + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=coreModeling \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-PACKAGE-ERROR + +// CHECK-PACKAGE-ERROR: (frontend): no analyzer checkers or packages +// CHECK-PACKAGE-ERROR-SAME: are associated with 'coreModeling' + +void test_disable_core_div_by_zero() { + (void)(1 / 0); + // expected-warning@-1 {{division by zero is undefined}} + // no-warning: 'Division by zero' +} + +void test_disable_null_deref(int *p) { + if (p) + return; + + int x = p[0]; + // no-warning: Array access (from variable 'p') results in a null pointer dereference +} diff --git a/clang/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp b/clang/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp new file mode 100644 index 0000000000000..3930f5a602534 --- /dev/null +++ b/clang/test/Analysis/silence-checkers-and-packages-core-div-by-zero.cpp @@ -0,0 +1,18 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core -analyzer-config \ +// RUN: silence-checkers=core.DivideZero \ +// RUN: -verify %s + +void test_disable_core_div_by_zero() { + (void)(1 / 0); + // expected-warning@-1 {{division by zero is undefined}} + // no-warning: 'Division by zero' +} + +void test_disable_null_deref(int *p) { + if (p) + return; + + int x = p[0]; + // expected-warning@-1 {{Array access (from variable 'p') results in a null pointer dereference}} +} diff --git a/clang/test/Analysis/sizeofpack.cpp b/clang/test/Analysis/sizeofpack.cpp new file mode 100644 index 0000000000000..8c0ca02b0710a --- /dev/null +++ b/clang/test/Analysis/sizeofpack.cpp @@ -0,0 +1,15 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,debug.ExprInspection \ +// RUN: -std=c++14 -verify %s + +typedef __typeof(sizeof(int)) size_t; + +void clang_analyzer_eval(bool); + +template size_t foo() { + return sizeof...(N); +} + +void bar() { + clang_analyzer_eval(foo<>() == 0); // expected-warning{{TRUE}} + clang_analyzer_eval(foo<1, 2, 3>() == 3); // expected-warning{{TRUE}} +} diff --git a/clang/test/Analysis/stack-frame-context-revision.cpp b/clang/test/Analysis/stack-frame-context-revision.cpp new file mode 100644 index 0000000000000..51f86defe3154 --- /dev/null +++ b/clang/test/Analysis/stack-frame-context-revision.cpp @@ -0,0 +1,37 @@ +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,cplusplus.NewDelete -verify %s + +// expected-no-diagnostics: +// From now the profile of the 'StackFrameContext' also contains the +// 'NodeBuilderContext::blockCount()'. With this addition we can distinguish +// between the 'StackArgumentsSpaceRegion' of the 'P' arguments being different +// on every iteration. + +typedef __INTPTR_TYPE__ intptr_t; + +template +struct SmarterPointer { + PointerTy getFromVoidPointer(void *P) const { + return static_cast(P); + } + + PointerTy getPointer() const { + return getFromVoidPointer(reinterpret_cast(Value)); + } + + intptr_t Value = 13; +}; + +struct Node { + SmarterPointer Pred; +}; + +void test(Node *N) { + while (N) { + SmarterPointer Next = N->Pred; + delete N; + + N = Next.getPointer(); + // no-warning: 'Use of memory after it is freed' was here as the same + // 'StackArgumentsSpaceRegion' purged out twice as 'P'. + } +} diff --git a/clang/test/Analysis/string.c b/clang/test/Analysis/string.c index d3b131ec9a64f..841bc157ab57b 100644 --- a/clang/test/Analysis/string.c +++ b/clang/test/Analysis/string.c @@ -1598,3 +1598,9 @@ void memset29_plain_int_zero() { memset(&x, 0, sizeof(short)); clang_analyzer_eval(x == 0); // expected-warning{{TRUE}} } + +void test_memset_chk() { + int x; + __builtin___memset_chk(&x, 0, sizeof(x), __builtin_object_size(&x, 0)); + clang_analyzer_eval(x == 0); // expected-warning{{TRUE}} +} diff --git a/clang/test/Analysis/symbol-escape.cpp b/clang/test/Analysis/symbol-escape.cpp new file mode 100644 index 0000000000000..be5dfbcd9ef58 --- /dev/null +++ b/clang/test/Analysis/symbol-escape.cpp @@ -0,0 +1,33 @@ +// RUN: %clang_analyze_cc1 \ +// RUN: -analyzer-checker=core,cplusplus.NewDeleteLeaks \ +// RUN: -verify %s + +// expected-no-diagnostics: Whenever we cannot evaluate an operation we escape +// the operands. After the evaluation it would be an +// Unknown value and the tracking would be lost. + +typedef unsigned __INTPTR_TYPE__ uintptr_t; + +class C {}; + +C *simple_escape_in_bitwise_op(C *Foo) { + C *Bar = new C(); + Bar = reinterpret_cast(reinterpret_cast(Bar) & 0x1); + (void)Bar; + // no-warning: "Potential leak of memory pointed to by 'Bar'" was here. + + return Bar; +} + +C **indirect_escape_in_bitwise_op() { + C *Qux = new C(); + C **Baz = &Qux; + Baz = reinterpret_cast(reinterpret_cast(Baz) | 0x1); + Baz = reinterpret_cast(reinterpret_cast(Baz) & + ~static_cast(0x1)); + // no-warning: "Potential leak of memory pointed to by 'Qux'" was here. + + delete *Baz; + return Baz; +} + diff --git a/clang/test/Analysis/taint-generic.c b/clang/test/Analysis/taint-generic.c index cdac02bf9e37b..4d933e7b75cc3 100644 --- a/clang/test/Analysis/taint-generic.c +++ b/clang/test/Analysis/taint-generic.c @@ -1,5 +1,49 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=alpha.security.taint,core,alpha.security.ArrayBoundV2 -Wno-format-security -verify %s -// RUN: %clang_analyze_cc1 -DFILE_IS_STRUCT -analyzer-checker=alpha.security.taint,core,alpha.security.ArrayBoundV2 -Wno-format-security -verify %s +// RUN: %clang_analyze_cc1 -Wno-format-security -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml + +// RUN: %clang_analyze_cc1 -Wno-format-security -verify %s \ +// RUN: -DFILE_IS_STRUCT \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-checker=alpha.security.ArrayBoundV2 \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config.yaml + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=justguessit \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-FILE + +// CHECK-INVALID-FILE: (frontend): invalid input for checker option +// CHECK-INVALID-FILE-SAME: 'alpha.security.taint.TaintPropagation:Config', +// CHECK-INVALID-FILE-SAME: that expects a valid filename instead of +// CHECK-INVALID-FILE-SAME: 'justguessit' + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config-ill-formed.yaml \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-ILL-FORMED + +// CHECK-ILL-FORMED: (frontend): invalid input for checker option +// CHECK-ILL-FORMED-SAME: 'alpha.security.taint.TaintPropagation:Config', +// CHECK-ILL-FORMED-SAME: that expects a valid yaml file: {{[Ii]}}nvalid argument + +// RUN: not %clang_analyze_cc1 -verify %s \ +// RUN: -analyzer-checker=alpha.security.taint \ +// RUN: -analyzer-config \ +// RUN: alpha.security.taint.TaintPropagation:Config=%S/Inputs/taint-generic-config-invalid-arg.yaml \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-ARG + +// CHECK-INVALID-ARG: (frontend): invalid input for checker option +// CHECK-INVALID-ARG-SAME: 'alpha.security.taint.TaintPropagation:Config', +// CHECK-INVALID-ARG-SAME: that expects an argument number for propagation +// CHECK-INVALID-ARG-SAME: rules greater or equal to -1 int scanf(const char *restrict format, ...); char *gets(char *str); @@ -294,3 +338,45 @@ void constraintManagerShouldTreatAsOpaque(int rhs) { if (i < rhs) *(volatile int *) 0; // no-warning } + + +// Test configuration +int mySource1(); +void mySource2(int*); +void myScanf(const char*, ...); +int myPropagator(int, int*); +int mySnprintf(char*, size_t, const char*, ...); +void mySink(int, int, int); + +void testConfigurationSources1() { + int x = mySource1(); + Buffer[x] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationSources2() { + int x; + mySource2(&x); + Buffer[x] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationSources3() { + int x, y; + myScanf("%d %d", &x, &y); + Buffer[y] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationPropagation() { + int x = mySource1(); + int y; + myPropagator(x, &y); + Buffer[y] = 1; // expected-warning {{Out of bound memory access }} +} + +void testConfigurationSinks() { + int x = mySource1(); + mySink(x, 1, 2); + // expected-warning@-1 {{Untrusted data is passed to a user-defined sink}} + mySink(1, x, 2); // no-warning + mySink(1, 2, x); + // expected-warning@-1 {{Untrusted data is passed to a user-defined sink}} +} diff --git a/clang/test/Analysis/temporaries.cpp b/clang/test/Analysis/temporaries.cpp index 6191abfb4d2ea..325b689c0debd 100644 --- a/clang/test/Analysis/temporaries.cpp +++ b/clang/test/Analysis/temporaries.cpp @@ -830,12 +830,7 @@ void test_ternary_temporary_with_copy(int coin) { // On each branch the variable is constructed directly. if (coin) { clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} -#if __cplusplus < 201703L clang_analyzer_eval(y == 1); // expected-warning{{TRUE}} -#else - // FIXME: Destructor called twice in C++17? - clang_analyzer_eval(y == 2); // expected-warning{{TRUE}} -#endif clang_analyzer_eval(z == 0); // expected-warning{{TRUE}} clang_analyzer_eval(w == 0); // expected-warning{{TRUE}} @@ -843,12 +838,7 @@ void test_ternary_temporary_with_copy(int coin) { clang_analyzer_eval(x == 0); // expected-warning{{TRUE}} clang_analyzer_eval(y == 0); // expected-warning{{TRUE}} clang_analyzer_eval(z == 1); // expected-warning{{TRUE}} -#if __cplusplus < 201703L clang_analyzer_eval(w == 1); // expected-warning{{TRUE}} -#else - // FIXME: Destructor called twice in C++17? - clang_analyzer_eval(w == 2); // expected-warning{{TRUE}} -#endif } } } // namespace test_match_constructors_and_destructors @@ -1055,16 +1045,11 @@ void foo(void (*bar4)(S)) { #endif bar2(S(2)); - // FIXME: Why are we losing information in C++17? clang_analyzer_eval(glob == 2); #ifdef TEMPORARY_DTORS -#if __cplusplus < 201703L - // expected-warning@-3{{TRUE}} -#else - // expected-warning@-5{{UNKNOWN}} -#endif + // expected-warning@-2{{TRUE}} #else - // expected-warning@-8{{UNKNOWN}} + // expected-warning@-4{{UNKNOWN}} #endif C *c = new D(); @@ -1246,3 +1231,19 @@ S bar3(int coin) { return coin ? S() : foo(); // no-warning } } // namespace return_from_top_frame + +#if __cplusplus >= 201103L +namespace arguments_of_operators { +struct S { + S() {} + S(const S &) {} +}; + +void test() { + int x = 0; + auto foo = [](S s, int &y) { y = 1; }; + foo(S(), x); + clang_analyzer_eval(x == 1); // expected-warning{{TRUE}} +} +} // namespace arguments_of_operators +#endif // __cplusplus >= 201103L diff --git a/clang/test/Analysis/temporaries.mm b/clang/test/Analysis/temporaries.mm index 43546ae3441d8..44d30d5d7d535 100644 --- a/clang/test/Analysis/temporaries.mm +++ b/clang/test/Analysis/temporaries.mm @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker core,cplusplus -verify %s +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker core,cplusplus -verify %s // expected-no-diagnostics diff --git a/clang/test/Analysis/test-separate-retaincount.cpp b/clang/test/Analysis/test-separate-retaincount.cpp index 5fda2b2e22112..621e1d120bbb2 100644 --- a/clang/test/Analysis/test-separate-retaincount.cpp +++ b/clang/test/Analysis/test-separate-retaincount.cpp @@ -1,12 +1,12 @@ -// RUN: %clang_analyze_cc1 -DNO_CF_OBJECT -verify %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -DNO_CF_OBJECT -verify %s \ // RUN: -analyzer-checker=core,osx \ // RUN: -analyzer-disable-checker osx.cocoa.RetainCount // -// RUN: %clang_analyze_cc1 -DNO_OS_OBJECT -verify %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -DNO_OS_OBJECT -verify %s \ // RUN: -analyzer-checker=core,osx \ // RUN: -analyzer-disable-checker osx.OSObjectRetainCount // -// RUN: %clang_analyze_cc1 -DNO_OS_OBJECT -verify %s \ +// RUN: %clang_analyze_cc1 -std=c++14 -DNO_OS_OBJECT -verify %s \ // RUN: -analyzer-checker=core,osx \ // RUN: -analyzer-config "osx.cocoa.RetainCount:CheckOSObject=false" diff --git a/clang/test/Analysis/track-control-dependency-conditions.cpp b/clang/test/Analysis/track-control-dependency-conditions.cpp new file mode 100644 index 0000000000000..11eb1c56a0388 --- /dev/null +++ b/clang/test/Analysis/track-control-dependency-conditions.cpp @@ -0,0 +1,1002 @@ +// RUN: %clang_analyze_cc1 %s -std=c++14 \ +// RUN: -verify=expected,tracking \ +// RUN: -analyzer-config track-conditions=true \ +// RUN: -analyzer-output=text \ +// RUN: -analyzer-checker=core + +// RUN: not %clang_analyze_cc1 -std=c++14 -verify %s \ +// RUN: -analyzer-checker=core \ +// RUN: -analyzer-config track-conditions=false \ +// RUN: -analyzer-config track-conditions-debug=true \ +// RUN: 2>&1 | FileCheck %s -check-prefix=CHECK-INVALID-DEBUG + +// CHECK-INVALID-DEBUG: (frontend): invalid input for analyzer-config option +// CHECK-INVALID-DEBUG-SAME: 'track-conditions-debug', that expects +// CHECK-INVALID-DEBUG-SAME: 'track-conditions' to also be enabled +// +// RUN: %clang_analyze_cc1 %s -std=c++14 \ +// RUN: -verify=expected,tracking,debug \ +// RUN: -analyzer-config track-conditions=true \ +// RUN: -analyzer-config track-conditions-debug=true \ +// RUN: -analyzer-output=text \ +// RUN: -analyzer-checker=core + +// RUN: %clang_analyze_cc1 %s -std=c++14 -verify \ +// RUN: -analyzer-output=text \ +// RUN: -analyzer-config track-conditions=false \ +// RUN: -analyzer-checker=core + +namespace example_1 { +int flag; +bool coin(); + +void foo() { + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void test() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + flag = 1; + + foo(); // TODO: Add nodes here about flag's value being invalidated. + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + x = new int; + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace example_1 + +namespace example_2 { +int flag; +bool coin(); + +void foo() { + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void test() { + int *x = 0; + flag = 1; + + foo(); + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + x = new int; + + x = 0; // expected-note-re{{{{^}}Null pointer value stored to 'x'{{$}}}} + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace example_2 + +namespace global_variable_invalidation { +int flag; +bool coin(); + +void foo() { + // coin() could write bar, do it's invalidated. + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Value assigned to 'bar', which participates in a condition later{{$}}}} +} + +int bar; + +void test() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + flag = 1; + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + + if (bar) // expected-note-re {{{{^}}Assuming 'bar' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'bar'{{$}}}} + if (flag) // expected-note-re {{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace global_variable_invalidation + +namespace variable_declaration_in_condition { +bool coin(); + +bool foo() { + return coin(); +} + +int bar; + +void test() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + if (int flag = foo()) // debug-note-re{{{{^}}Tracking condition 'flag'{{$}}}} + // expected-note-re@-1{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Taking true branch{{$}}}} + + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace variable_declaration_in_condition + +namespace conversion_to_bool { +bool coin(); + +struct ConvertsToBool { + operator bool() const { return coin(); } +}; + +void test() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + if (ConvertsToBool()) + // debug-note-re@-1{{{{^}}Tracking condition 'ConvertsToBool()'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking true branch{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace variable_declaration_in_condition + +namespace note_from_different_but_not_nested_stackframe { + +void nullptrDeref(int *ptr, bool True) { + if (True) // expected-note-re{{{{^}}'True' is true{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'True'{{$}}}} + *ptr = 5; + // expected-note@-1{{Dereference of null pointer (loaded from variable 'ptr')}} + // expected-warning@-2{{Dereference of null pointer (loaded from variable 'ptr')}} +} + +void f() { + int *ptr = nullptr; + // expected-note-re@-1{{{{^}}'ptr' initialized to a null pointer value{{$}}}} + bool True = true; + nullptrDeref(ptr, True); + // expected-note-re@-1{{{{^}}Passing null pointer value via 1st parameter 'ptr'{{$}}}} + // expected-note-re@-2{{{{^}}Calling 'nullptrDeref'{{$}}}} +} + +} // end of namespace note_from_different_but_not_nested_stackframe + +namespace important_returning_pointer_loaded_from { +bool coin(); + +int *getIntPtr(); + +void storeValue(int **i) { + *i = getIntPtr(); // tracking-note-re{{{{^}}Value assigned to 'i', which participates in a condition later{{$}}}} +} + +int *conjurePointer() { + int *i; + storeValue(&i); // tracking-note-re{{{{^}}Calling 'storeValue'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'storeValue'{{$}}}} + return i; // tracking-note-re{{{{^}}Returning pointer (loaded from 'i'), which participates in a condition later{{$}}}} +} + +void f(int *ptr) { + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + ; + if (!conjurePointer()) + // tracking-note-re@-1{{{{^}}Calling 'conjurePointer'{{$}}}} + // tracking-note-re@-2{{{{^}}Returning from 'conjurePointer'{{$}}}} + // debug-note-re@-3{{{{^}}Tracking condition '!conjurePointer()'{{$}}}} + // expected-note-re@-4{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-5{{{{^}}Taking true branch{{$}}}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace important_returning_pointer_loaded_from + +namespace unimportant_returning_pointer_loaded_from { +bool coin(); + +int *getIntPtr(); + +int *conjurePointer() { + int *i = getIntPtr(); + return i; +} + +void f(int *ptr) { + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + ; + if (!conjurePointer()) + // debug-note-re@-1{{{{^}}Tracking condition '!conjurePointer()'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking true branch{{$}}}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace unimportant_returning_pointer_loaded_from + +namespace unimportant_returning_pointer_loaded_from_through_cast { + +void *conjure(); + +int *cast(void *P) { + return static_cast(P); +} + +void f() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + if (cast(conjure())) + // debug-note-re@-1{{{{^}}Tracking condition 'cast(conjure())'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is false{{$}}}} + // expected-note-re@-3{{{{^}}Taking false branch{{$}}}} + return; + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace unimportant_returning_pointer_loaded_from_through_cast + +namespace unimportant_returning_value_note { +bool coin(); + +bool flipCoin() { return coin(); } + +void i(int *ptr) { + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + ; + if (!flipCoin()) + // debug-note-re@-1{{{{^}}Tracking condition '!flipCoin()'{{$}}}} + // expected-note-re@-2{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking true branch{{$}}}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace unimportant_returning_value_note + +namespace important_returning_value_note { +bool coin(); + +bool flipCoin() { + if (coin()) // tracking-note-re{{{{^}}Assuming the condition is false{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'coin()'{{$}}}} + return true; + return coin(); // tracking-note-re{{{{^}}Returning value, which participates in a condition later{{$}}}} +} + +void i(int *ptr) { + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + ; + if (!flipCoin()) + // tracking-note-re@-1{{{{^}}Calling 'flipCoin'{{$}}}} + // tracking-note-re@-2{{{{^}}Returning from 'flipCoin'{{$}}}} + // debug-note-re@-3{{{{^}}Tracking condition '!flipCoin()'{{$}}}} + // expected-note-re@-4{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-5{{{{^}}Taking true branch{{$}}}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace important_returning_value_note + +namespace important_returning_value_note_in_linear_function { +bool coin(); + +struct super_complicated_template_hackery { + static constexpr bool value = false; +}; + +bool flipCoin() { + if (super_complicated_template_hackery::value) + // tracking-note-re@-1{{{{^}}'value' is false{{$}}}} + // tracking-note-re@-2{{{{^}}Taking false branch{{$}}}} + return true; + return coin(); // tracking-note-re{{{{^}}Returning value, which participates in a condition later{{$}}}} +} + +void i(int *ptr) { + if (ptr) // expected-note-re{{{{^}}Assuming 'ptr' is null{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + ; + if (!flipCoin()) + // tracking-note-re@-1{{{{^}}Calling 'flipCoin'{{$}}}} + // tracking-note-re@-2{{{{^}}Returning from 'flipCoin'{{$}}}} + // debug-note-re@-3{{{{^}}Tracking condition '!flipCoin()'{{$}}}} + // expected-note-re@-4{{{{^}}Assuming the condition is true{{$}}}} + // expected-note-re@-5{{{{^}}Taking true branch{{$}}}} + *ptr = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace important_returning_value_note_in_linear_function + +namespace tracked_condition_is_only_initialized { +int getInt(); + +void f() { + int flag = getInt(); + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace tracked_condition_is_only_initialized + +namespace tracked_condition_written_in_same_stackframe { +int flag; +int getInt(); + +void f(int y) { + y = 1; + flag = y; + + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + if (flag) // expected-note-re{{{{^}}'flag' is 1{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace tracked_condition_written_in_same_stackframe + +namespace tracked_condition_written_in_nested_stackframe { +int flag; +int getInt(); + +void foo() { + int y; + y = 1; + flag = y; // tracking-note-re{{{{^}}The value 1 is assigned to 'flag', which participates in a condition later{{$}}}} +} + +void f(int y) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + + if (flag) // expected-note-re{{{{^}}'flag' is 1{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace tracked_condition_written_in_nested_stackframe + +namespace condition_written_in_nested_stackframe_before_assignment { +int flag = 0; +int getInt(); + +void foo() { + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void f() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + int y = 0; + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + y = flag; + + if (y) // expected-note-re{{{{^}}Assuming 'y' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'y'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_written_in_nested_stackframe_before_assignment + +namespace dont_explain_foreach_loops { + +struct Iterator { + int *pos; + bool operator!=(Iterator other) const { + return pos && other.pos && pos != other.pos; + } + int operator*(); + Iterator operator++(); +}; + +struct Container { + Iterator begin(); + Iterator end(); +}; + +void f(Container Cont) { + int flag = 0; + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + for (int i : Cont) + if (i) // expected-note-re {{{{^}}Assuming 'i' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'i'{{$}}}} + flag = i; + + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace dont_explain_foreach_loops + +namespace condition_lambda_capture_by_reference_last_write { +int getInt(); + +[[noreturn]] void halt(); + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + auto lambda = [&flag]() { + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} + }; + + lambda(); // tracking-note-re{{{{^}}Calling 'operator()'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'operator()'{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_lambda_capture_by_reference_last_write + +namespace condition_lambda_capture_by_value_assumption { +int getInt(); + +[[noreturn]] void halt(); + +void bar(int &flag) { + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + auto lambda = [flag]() { + if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); + }; + + bar(flag); // tracking-note-re{{{{^}}Calling 'bar'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'bar'{{$}}}} + lambda(); // tracking-note-re{{{{^}}Calling 'operator()'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'operator()'{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_lambda_capture_by_value_assumption + +namespace condition_lambda_capture_by_reference_assumption { +int getInt(); + +[[noreturn]] void halt(); + +void bar(int &flag) { + flag = getInt(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + auto lambda = [&flag]() { + if (!flag) // tracking-note-re{{{{^}}Assuming 'flag' is not equal to 0, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); + }; + + bar(flag); // tracking-note-re{{{{^}}Calling 'bar'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'bar'{{$}}}} + lambda(); // tracking-note-re{{{{^}}Calling 'operator()'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'operator()'{{$}}}} + + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_lambda_capture_by_reference_assumption + +namespace collapse_point_not_in_condition_bool { + +[[noreturn]] void halt(); + +void check(bool b) { + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is true, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); +} + +void f(bool flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + check(flag); // tracking-note-re{{{{^}}Calling 'check'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'check'{{$}}}} + + if (flag) // expected-note-re{{{{^}}'flag' is true{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace collapse_point_not_in_condition_bool + +namespace collapse_point_not_in_condition { + +[[noreturn]] void halt(); + +void assert(int b) { + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + assert(flag); // tracking-note-re{{{{^}}Calling 'assert'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'assert'{{$}}}} + + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace collapse_point_not_in_condition + +namespace unimportant_write_before_collapse_point { + +[[noreturn]] void halt(); + +void assert(int b) { + if (!b) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 0, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); +} +int getInt(); + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + flag = getInt(); + assert(flag); // tracking-note-re{{{{^}}Calling 'assert'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'assert'{{$}}}} + + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace unimportant_write_before_collapse_point + +namespace dont_crash_on_nonlogical_binary_operator { + +void f6(int x) { + int a[20]; + if (x == 25) {} // expected-note{{Assuming 'x' is equal to 25}} + // expected-note@-1{{Taking true branch}} + if (a[x] == 123) {} // expected-warning{{The left operand of '==' is a garbage value due to array index out of bounds}} + // expected-note@-1{{The left operand of '==' is a garbage value due to array index out of bounds}} +} + +} // end of namespace dont_crash_on_nonlogical_binary_operator + +namespace collapse_point_not_in_condition_binary_op { + +[[noreturn]] void halt(); + +void check(int b) { + if (b == 1) // tracking-note-re{{{{^}}Assuming 'b' is not equal to 1, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + check(flag); // tracking-note-re{{{{^}}Calling 'check'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'check'{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace collapse_point_not_in_condition_binary_op + +namespace collapse_point_not_in_condition_as_field { + +[[noreturn]] void halt(); +struct IntWrapper { + int b; + IntWrapper(); + + void check() { + if (!b) // tracking-note-re{{{{^}}Assuming field 'b' is not equal to 0, which participates in a condition later{{$}}}} + // tracking-note-re@-1{{{{^}}Taking false branch{{$}}}} + halt(); + return; + } +}; + +void f(IntWrapper i) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + i.check(); // tracking-note-re{{{{^}}Calling 'IntWrapper::check'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'IntWrapper::check'{{$}}}} + if (i.b) // expected-note-re{{{{^}}Field 'b' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'i.b'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace collapse_point_not_in_condition_as_field + +namespace assignemnt_in_condition_in_nested_stackframe { +int flag; + +bool coin(); + +[[noreturn]] void halt(); + +void foo() { + if ((flag = coin())) + // tracking-note-re@-1{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} + // tracking-note-re@-2{{{{^}}Assuming 'flag' is not equal to 0, which participates in a condition later{{$}}}} + // tracking-note-re@-3{{{{^}}Taking true branch{{$}}}} + return; + halt(); + return; +} + +void f() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace assignemnt_in_condition_in_nested_stackframe + +namespace condition_variable_less { +int flag; + +bool coin(); + +[[noreturn]] void halt(); + +void foo() { + if (flag > 0) + // tracking-note-re@-1{{{{^}}Assuming 'flag' is > 0, which participates in a condition later{{$}}}} + // tracking-note-re@-2{{{{^}}Taking true branch{{$}}}} + return; + halt(); + return; +} + +void f() { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + foo(); // tracking-note-re{{{{^}}Calling 'foo'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'foo'{{$}}}} + if (flag) // expected-note-re{{{{^}}'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} +} // end of namespace condition_variable_less + +namespace dont_track_assertlike_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail(#expr, __FILE__, __LINE__, __func__)) + +int getInt(); + +int cond1; + +void bar() { + cond1 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + flag = getInt(); + + bar(); + assert(cond1); // expected-note-re{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}'?' condition is true{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assertlike_conditions + +namespace dont_track_assertlike_and_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail(#expr, __FILE__, __LINE__, __func__)) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + flag = getInt(); + + bar(); + assert(cond1 && cond2); + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Assuming 'cond2' is not equal to 0{{$}}}} + // expected-note-re@-3{{{{^}}'?' condition is true{{$}}}} + // expected-note-re@-4{{{{^}}Left side of '&&' is true{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assertlike_and_conditions + +namespace dont_track_assertlike_or_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + ((expr) ? (void)(0) : __assert_fail(#expr, __FILE__, __LINE__, __func__)) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + flag = getInt(); + + bar(); + assert(cond1 || cond2); + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Left side of '||' is true{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assertlike_or_conditions + +namespace dont_track_assert2like_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + do { \ + if (!(expr)) \ + __assert_fail(#expr, __FILE__, __LINE__, __func__); \ + } while (0) + +int getInt(); + +int cond1; + +void bar() { + cond1 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + flag = getInt(); + + bar(); + assert(cond1); // expected-note-re{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking false branch{{$}}}} + // expected-note-re@-2{{{{^}}Loop condition is false. Exiting loop{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assert2like_conditions + +namespace dont_track_assert2like_and_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + do { \ + if (!(expr)) \ + __assert_fail(#expr, __FILE__, __LINE__, __func__); \ + } while (0) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + flag = getInt(); + + bar(); + assert(cond1 && cond2); + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Left side of '&&' is true{{$}}}} + // expected-note-re@-3{{{{^}}Assuming the condition is false{{$}}}} + // expected-note-re@-4{{{{^}}Taking false branch{{$}}}} + // expected-note-re@-5{{{{^}}Loop condition is false. Exiting loop{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assert2like_and_conditions + +namespace dont_track_assert2like_or_conditions { + +extern void __assert_fail(__const char *__assertion, __const char *__file, + unsigned int __line, __const char *__function) + __attribute__((__noreturn__)); +#define assert(expr) \ + do { \ + if (!(expr)) \ + __assert_fail(#expr, __FILE__, __LINE__, __func__); \ + } while (0) + +int getInt(); + +int cond1; +int cond2; + +void bar() { + cond1 = getInt(); + cond2 = getInt(); +} + +void f(int flag) { + int *x = 0; // expected-note-re{{{{^}}'x' initialized to a null pointer value{{$}}}} + + flag = getInt(); + + bar(); + assert(cond1 || cond2); + // expected-note-re@-1{{{{^}}Assuming 'cond1' is not equal to 0{{$}}}} + // expected-note-re@-2{{{{^}}Left side of '||' is true{{$}}}} + // expected-note-re@-3{{{{^}}Taking false branch{{$}}}} + // expected-note-re@-4{{{{^}}Loop condition is false. Exiting loop{{$}}}} + + if (flag) // expected-note-re{{{{^}}Assuming 'flag' is not equal to 0{{$}}}} + // expected-note-re@-1{{{{^}}Taking true branch{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +#undef assert +} // end of namespace dont_track_assert2like_or_conditions + +namespace only_track_the_evaluated_condition { + +bool coin(); + +void bar(int &flag) { + flag = coin(); // tracking-note-re{{{{^}}Value assigned to 'flag', which participates in a condition later{{$}}}} +} + +void bar2(int &flag2) { + flag2 = coin(); +} + +void f(int *x) { + if (x) // expected-note-re{{{{^}}Assuming 'x' is null{{$}}}} + // debug-note-re@-1{{{{^}}Tracking condition 'x'{{$}}}} + // expected-note-re@-2{{{{^}}Taking false branch{{$}}}} + return; + + int flag, flag2; + bar(flag); // tracking-note-re{{{{^}}Calling 'bar'{{$}}}} + // tracking-note-re@-1{{{{^}}Returning from 'bar'{{$}}}} + bar2(flag2); + + if (flag && flag2) // expected-note-re {{{{^}}Assuming 'flag' is 0{{$}}}} + // expected-note-re@-1{{{{^}}Left side of '&&' is false{{$}}}} + // debug-note-re@-2{{{{^}}Tracking condition 'flag'{{$}}}} + return; + + *x = 5; // expected-warning{{Dereference of null pointer}} + // expected-note@-1{{Dereference of null pointer}} +} + +} // end of namespace only_track_the_evaluated_condition diff --git a/clang/test/Analysis/uninit-const.c b/clang/test/Analysis/uninit-const.c index 407c28a5e8bab..ec29c236069cb 100644 --- a/clang/test/Analysis/uninit-const.c +++ b/clang/test/Analysis/uninit-const.c @@ -24,15 +24,15 @@ void doStuff_constStaticSizedArray(const int a[static 10]) {} void doStuff_variadic(const int *u, ...){}; void f_1(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_pointerToConstInt(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} } void f_1_1(void) { - int t; - int* tp1 = &t; + int t; // expected-note {{'t' declared without an initial value}} + int *tp1 = &t; // expected-note {{'tp1' initialized here}} int* tp2 = tp1; // expected-note {{'tp2' initialized here}} doStuff_pointerToConstInt(tp2); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -40,12 +40,15 @@ void f_1_1(void) { int *f_2_sub(int *p) { - return p; + return p; // expected-note {{Returning pointer (loaded from 'p')}} } void f_2(void) { - int t; - int* p = f_2_sub(&t); + int t; // expected-note {{'t' declared without an initial value}} + int *p = f_2_sub(&t); // expected-note {{Passing value via 1st parameter 'p'}} + // expected-note@-1{{Calling 'f_2_sub'}} + // expected-note@-2{{Returning from 'f_2_sub'}} + // expected-note@-3{{'p' initialized here}} int* tp = p; // expected-note {{'tp' initialized here}} doStuff_pointerToConstInt(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -62,7 +65,7 @@ void f_4(void) { } void f_5(void) { - int ta[5]; + int ta[5]; // expected-note {{'ta' initialized here}} int* tp = ta; // expected-note {{'tp' initialized here}} doStuff_pointerToConstInt(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -99,7 +102,7 @@ void f_8(void) { } void f_9(void) { - int a[6]; + int a[6]; // expected-note {{'a' initialized here}} int const *ptau = a; // expected-note {{'ptau' initialized here}} doStuff_arrayOfConstInt(ptau); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} @@ -173,7 +176,7 @@ int f_malloc_2(void) { // uninit pointer, uninit val void f_variadic_unp_unv(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int v; int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_variadic(tp,v); // expected-warning {{1st function call argument is a pointer to uninitialized value}} @@ -181,7 +184,7 @@ void f_variadic_unp_unv(void) { } // uninit pointer, init val void f_variadic_unp_inv(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int v = 3; int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_variadic(tp,v); // expected-warning {{1st function call argument is a pointer to uninitialized value}} @@ -216,7 +219,7 @@ void f_variadic_inp_inp(void) { //uninit pointer, init pointer void f_variadic_unp_inp(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int u=3; int *vp = &u ; int *tp = &t; // expected-note {{'tp' initialized here}} @@ -235,7 +238,7 @@ void f_variadic_inp_unp(void) { //uninit pointer, uninit pointer void f_variadic_unp_unp(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int u; int *vp = &u ; int *tp = &t; // expected-note {{'tp' initialized here}} diff --git a/clang/test/Analysis/uninit-const.cpp b/clang/test/Analysis/uninit-const.cpp index b6430307dc520..3b7089cbcacd6 100644 --- a/clang/test/Analysis/uninit-const.cpp +++ b/clang/test/Analysis/uninit-const.cpp @@ -66,8 +66,8 @@ void f6_1(void) { void f6_2(void) { int t; //expected-note {{'t' declared without an initial value}} - int &p = t; - int &s = p; + int &p = t; //expected-note {{'p' initialized here}} + int &s = p; //expected-note {{'s' initialized here}} int &q = s; //expected-note {{'q' initialized here}} doStuff6(q); //expected-warning {{1st function call argument is an uninitialized value}} //expected-note@-1 {{1st function call argument is an uninitialized value}} @@ -96,7 +96,7 @@ void f6(void) { void f5(void) { - int t; + int t; // expected-note {{'t' declared without an initial value}} int* tp = &t; // expected-note {{'tp' initialized here}} doStuff_uninit(tp); // expected-warning {{1st function call argument is a pointer to uninitialized value}} // expected-note@-1 {{1st function call argument is a pointer to uninitialized value}} diff --git a/clang/test/Analysis/uninit-vals.c b/clang/test/Analysis/uninit-vals.c index d24b458747384..c0c6034e80328 100644 --- a/clang/test/Analysis/uninit-vals.c +++ b/clang/test/Analysis/uninit-vals.c @@ -149,8 +149,6 @@ int test_radar12278788_FP() { RetVoidFuncType f = foo_radar12278788_fp; return ((RetIntFuncType)f)(); //expected-warning {{Undefined or garbage value returned to caller}} //expected-note@-1 {{Undefined or garbage value returned to caller}} - //expected-note@-2 {{Calling 'foo_radar12278788_fp'}} - //expected-note@-3 {{Returning from 'foo_radar12278788_fp'}} } void rdar13665798() { @@ -164,8 +162,6 @@ void rdar13665798() { RetVoidFuncType f = foo_radar12278788_fp; return ((RetIntFuncType)f)(); //expected-warning {{Undefined or garbage value returned to caller}} //expected-note@-1 {{Undefined or garbage value returned to caller}} - //expected-note@-2 {{Calling 'foo_radar12278788_fp'}} - //expected-note@-3 {{Returning from 'foo_radar12278788_fp'}} }(); } @@ -182,18 +178,14 @@ struct Point getHalfPoint() { void use(struct Point p); void testUseHalfPoint() { - struct Point p = getHalfPoint(); // expected-note{{Calling 'getHalfPoint'}} - // expected-note@-1{{Returning from 'getHalfPoint'}} - // expected-note@-2{{'p' initialized here}} + struct Point p = getHalfPoint(); // expected-note{{'p' initialized here}} use(p); // expected-warning{{uninitialized}} // expected-note@-1{{uninitialized}} } void testUseHalfPoint2() { struct Point p; - p = getHalfPoint(); // expected-note{{Calling 'getHalfPoint'}} - // expected-note@-1{{Returning from 'getHalfPoint'}} - // expected-note@-2{{Value assigned to 'p'}} + p = getHalfPoint(); // expected-note{{Value assigned to 'p'}} use(p); // expected-warning{{uninitialized}} // expected-note@-1{{uninitialized}} } diff --git a/clang/test/Analysis/unions.cpp b/clang/test/Analysis/unions.cpp index 6fd35d1a43f4a..76eb20550fdb4 100644 --- a/clang/test/Analysis/unions.cpp +++ b/clang/test/Analysis/unions.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=core,unix.Malloc,debug.ExprInspection %s -analyzer-config eagerly-assume=false -verify +// RUN: %clang_analyze_cc1 -std=c++14 -analyzer-checker=core,unix.Malloc,debug.ExprInspection %s -analyzer-config eagerly-assume=false -verify extern void clang_analyzer_eval(bool); extern void clang_analyzer_warnIfReached(); diff --git a/clang/test/Analysis/virtualcall-fixits.cpp b/clang/test/Analysis/virtualcall-fixits.cpp new file mode 100644 index 0000000000000..ea149d52fcd21 --- /dev/null +++ b/clang/test/Analysis/virtualcall-fixits.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config optin.cplusplus.VirtualCall:ShowFixIts=true \ +// RUN: %s 2>&1 | FileCheck -check-prefix=TEXT %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config optin.cplusplus.VirtualCall:ShowFixIts=true \ +// RUN: -analyzer-config fixits-as-remarks=true \ +// RUN: -analyzer-output=plist -o %t.plist -verify %s +// RUN: cat %t.plist | FileCheck -check-prefix=PLIST %s + +struct S { + virtual void foo(); + S() { + foo(); + // expected-warning@-1{{Call to virtual method 'S::foo' during construction bypasses virtual dispatch}} + // expected-remark@-2{{5-5: 'S::'}} + } + ~S(); +}; + +// TEXT: warning: Call to virtual method 'S::foo' during construction +// TEXT-SAME: bypasses virtual dispatch +// TEXT-NEXT: foo(); +// TEXT-NEXT: ^~~~~ +// TEXT-NEXT: S:: +// TEXT-NEXT: 1 warning generated. + +// PLIST: fixits +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: remove_range +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: line13 +// PLIST-NEXT: col5 +// PLIST-NEXT: file0 +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: line13 +// PLIST-NEXT: col4 +// PLIST-NEXT: file0 +// PLIST-NEXT: +// PLIST-NEXT: +// PLIST-NEXT: insert_stringS:: +// PLIST-NEXT: +// PLIST-NEXT: diff --git a/clang/test/Analysis/virtualcall-plist.cpp b/clang/test/Analysis/virtualcall-plist.cpp new file mode 100644 index 0000000000000..a85dc4b6a23ea --- /dev/null +++ b/clang/test/Analysis/virtualcall-plist.cpp @@ -0,0 +1,23 @@ +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus \ +// RUN: -analyzer-output=plist -o %t.plist -w -verify=pure %s +// RUN: cat %t.plist | FileCheck --check-prefixes=PURE %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus \ +// RUN: -analyzer-output=plist -o %t.plist -w -verify=impure %s +// RUN: cat %t.plist | FileCheck --check-prefixes=IMPURE %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus,optin.cplusplus \ +// RUN: -analyzer-output=plist -o %t.plist -w -verify=pure,impure %s +// RUN: cat %t.plist | FileCheck --check-prefixes=PURE,IMPURE %s + +struct S { + virtual void foo(); + virtual void bar() = 0; + + S() { + // IMPURE: Call to virtual method 'S::foo' during construction bypasses virtual dispatch + // IMPURE: optin.cplusplus.VirtualCall + foo(); // impure-warning{{Call to virtual method 'S::foo' during construction bypasses virtual dispatch}} + // PURE: Call to pure virtual method 'S::bar' during construction has undefined behavior + // PURE: cplusplus.PureVirtualCall + bar(); // pure-warning{{Call to pure virtual method 'S::bar' during construction has undefined behavior}} + } +}; diff --git a/clang/test/Analysis/virtualcall.cpp b/clang/test/Analysis/virtualcall.cpp index 5847110c093eb..80f89d14ea97c 100644 --- a/clang/test/Analysis/virtualcall.cpp +++ b/clang/test/Analysis/virtualcall.cpp @@ -1,9 +1,38 @@ -// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-output=text -verify -std=c++11 %s +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=impure %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.PureVirtualCall \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=pure -std=c++11 %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config \ +// RUN: optin.cplusplus.VirtualCall:PureOnly=true \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=none %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.PureVirtualCall \ +// RUN: -analyzer-checker=optin.cplusplus.VirtualCall \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=pure,impure -std=c++11 %s + +// RUN: %clang_analyze_cc1 -analyzer-checker=core,cplusplus.PureVirtualCall \ +// RUN: -analyzer-checker=optin.cplusplus.VirtualCall \ +// RUN: -analyzer-config \ +// RUN: optin.cplusplus.VirtualCall:PureOnly=true \ +// RUN: -analyzer-checker=debug.ExprInspection \ +// RUN: -std=c++11 -verify=pure %s + + +// We expect no diagnostics when all checks are disabled. +// none-no-diagnostics -// RUN: %clang_analyze_cc1 -analyzer-checker=optin.cplusplus.VirtualCall -analyzer-store region -analyzer-config optin.cplusplus.VirtualCall:PureOnly=true -DPUREONLY=1 -analyzer-output=text -verify -std=c++11 %s #include "virtualcall.h" +void clang_analyzer_warnIfReached(); + class A { public: A(); @@ -13,54 +42,32 @@ class A { virtual int foo() = 0; virtual void bar() = 0; void f() { - foo(); - // expected-warning-re@-1 {{{{^}}Call to pure virtual function during construction}} - // expected-note-re@-2 {{{{^}}Call to pure virtual function during construction}} + foo(); // pure-warning{{Call to pure virtual method 'A::foo' during construction has undefined behavior}} + clang_analyzer_warnIfReached(); // no-warning } }; -class B : public A { +A::A() { + f(); +} + +class B { public: - B() { // expected-note {{Calling default constructor for 'A'}} - foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'B' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + B() { + foo(); // impure-warning {{Call to virtual method 'B::foo' during construction bypasses virtual dispatch}} } ~B(); virtual int foo(); virtual void bar() { - foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during destruction}} - // expected-note-re@-3 {{{{^}}Call to virtual function during destruction}} -#endif - } + foo(); // impure-warning {{Call to virtual method 'B::foo' during destruction bypasses virtual dispatch}} + } }; -A::A() { - f(); -// expected-note-re@-1 {{{{^}}This constructor of an object of type 'A' has not returned when the virtual method was called}} -// expected-note-re@-2 {{{{^}}Calling 'A::f'}} -} - B::~B() { this->B::foo(); // no-warning this->B::bar(); -#if !PUREONLY - // expected-note-re@-2 {{{{^}}This destructor of an object of type '~B' has not returned when the virtual method was called}} - // expected-note-re@-3 {{{{^}}Calling 'B::bar'}} -#endif - this->foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during destruction}} - // expected-note-re@-3 {{{{^}}This destructor of an object of type '~B' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during destruction}} -#endif - + this->foo(); // impure-warning {{Call to virtual method 'B::foo' during destruction bypasses virtual dispatch}} } class C : public B { @@ -73,12 +80,7 @@ class C : public B { }; C::C() { - f(foo()); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'C' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + f(foo()); // impure-warning {{Call to virtual method 'C::foo' during construction bypasses virtual dispatch}} } class D : public B { @@ -97,9 +99,6 @@ class E final : public B { foo(); // no-warning } ~E() { bar(); } -#if !PUREONLY - // expected-note-re@-2 2{{{{^}}Calling '~B'}} -#endif int foo() override; }; @@ -135,52 +134,23 @@ class H { G g; g.foo(); g.bar(); // no warning - f(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'H' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + f(); // impure-warning {{Call to virtual method 'H::f' during construction bypasses virtual dispatch}} H &h = *this; - h.f(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'H' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + h.f(); // impure-warning {{Call to virtual method 'H::f' during construction bypasses virtual dispatch}} } }; class X { public: X() { - g(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'X' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + g(); // impure-warning {{Call to virtual method 'X::g' during construction bypasses virtual dispatch}} } X(int i) { if (i > 0) { -#if !PUREONLY - // expected-note-re@-2 {{{{^}}'i' is > 0}} - // expected-note-re@-3 {{{{^}}Taking true branch}} - // expected-note-re@-4 {{{{^}}'i' is <= 0}} - // expected-note-re@-5 {{{{^}}Taking false branch}} -#endif X x(i - 1); -#if !PUREONLY - // expected-note-re@-2 {{{{^}}Calling constructor for 'X'}} -#endif x.g(); // no warning } - g(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'X' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + g(); // impure-warning {{Call to virtual method 'X::g' during construction bypasses virtual dispatch}} } virtual void g(); }; @@ -197,19 +167,11 @@ class M { N n; n.virtualMethod(); // no warning n.callFooOfM(this); -#if !PUREONLY - // expected-note-re@-2 {{{{^}}This constructor of an object of type 'M' has not returned when the virtual method was called}} - // expected-note-re@-3 {{{{^}}Calling 'N::callFooOfM'}} -#endif } virtual void foo(); }; void N::callFooOfM(M *m) { - m->foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}Call to virtual function during construction}} -#endif + m->foo(); // impure-warning {{Call to virtual method 'M::foo' during construction bypasses virtual dispatch}} } class Y { @@ -217,65 +179,27 @@ class Y { virtual void foobar(); void fooY() { F f1; - foobar(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}Call to virtual function during construction}} -#endif + foobar(); // impure-warning {{Call to virtual method 'Y::foobar' during construction bypasses virtual dispatch}} } Y() { fooY(); } -#if !PUREONLY - // expected-note-re@-2 {{{{^}}This constructor of an object of type 'Y' has not returned when the virtual method was called}} - // expected-note-re@-3 {{{{^}}Calling 'Y::fooY'}} -#endif }; int main() { B b; -#if PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'B'}} -#else - //expected-note-re@-4 2{{{{^}}Calling default constructor for 'B'}} -#endif C c; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'C'}} -#endif D d; E e; F f; G g; H h; H h1(1); -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling constructor for 'H'}} - //expected-note-re@-3 {{{{^}}Calling constructor for 'H'}} -#endif X x; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'X'}} -#endif X x1(1); -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling constructor for 'X'}} -#endif M m; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'M'}} -#endif Y *y = new Y; -#if !PUREONLY - //expected-note-re@-2 {{{{^}}Calling default constructor for 'Y'}} -#endif delete y; header::Z z; -#if !PUREONLY - // expected-note-re@-2 {{{{^}}Calling default constructor for 'Z'}} -#endif } -#if !PUREONLY - //expected-note-re@-2 2{{{{^}}Calling '~E'}} -#endif namespace PR34451 { struct a { diff --git a/clang/test/Analysis/virtualcall.h b/clang/test/Analysis/virtualcall.h index e2fde2415ec15..f591aab2cacfc 100644 --- a/clang/test/Analysis/virtualcall.h +++ b/clang/test/Analysis/virtualcall.h @@ -2,12 +2,7 @@ namespace header { class Z { public: Z() { - foo(); -#if !PUREONLY - // expected-warning-re@-2 {{{{^}}Call to virtual function during construction}} - // expected-note-re@-3 {{{{^}}This constructor of an object of type 'Z' has not returned when the virtual method was called}} - // expected-note-re@-4 {{{{^}}Call to virtual function during construction}} -#endif + foo(); // impure-warning {{Call to virtual method 'Z::foo' during construction bypasses virtual dispatch}} } virtual int foo(); }; diff --git a/clang/test/CodeGenCXX/noescape.cpp b/clang/test/CodeGenCXX/noescape.cpp index 40bc839788ac8..dbd97daa5e064 100644 --- a/clang/test/CodeGenCXX/noescape.cpp +++ b/clang/test/CodeGenCXX/noescape.cpp @@ -45,7 +45,7 @@ void *operator new(std::size_t, void * __attribute__((noescape)) p) { } // CHECK-LABEL: define i8* @_Z5test1Pv( -// CHECK : %call = call {{.*}} @_ZnwmPv({{.*}}, {{.*}} nocapture {{.*}}) +// CHECK: %call = call {{.*}} @_ZnwmPv({{.*}}, {{.*}} nocapture {{.*}}) void *test1(void *p0) { return ::operator new(16, p0); } diff --git a/clang/test/CodeGenObjC/externally-retained.m b/clang/test/CodeGenObjC/externally-retained.m index 0b4d0d648b440..f68696879768f 100644 --- a/clang/test/CodeGenObjC/externally-retained.m +++ b/clang/test/CodeGenObjC/externally-retained.m @@ -22,7 +22,7 @@ @interface ObjTy @end void param(ObjTy *p) EXT_RET { // CHECK-LABEL: define void @param // CHECK-NOT: llvm.objc. - // CHECK ret + // CHECK: ret } void local() { diff --git a/clang/test/Sema/return.c b/clang/test/Sema/return.c index debf5ab55f5b5..68c2251c46330 100644 --- a/clang/test/Sema/return.c +++ b/clang/test/Sema/return.c @@ -328,3 +328,14 @@ int sizeof_long() { if (sizeof(long) == 8) return 2; } // no-warning + +int return_statement_expression() { + if (unknown()) + return ({ + while (0) + ; + 0; + }); + else + return 0; +} // no-warning (used to be "control may reach end of non-void function") diff --git a/clang/tools/scan-build/bin/scan-build b/clang/tools/scan-build/bin/scan-build index 903e19a2909a6..1574b10f20548 100755 --- a/clang/tools/scan-build/bin/scan-build +++ b/clang/tools/scan-build/bin/scan-build @@ -57,6 +57,7 @@ my %Options = ( KeepEmpty => 0, # Don't remove output directory even with 0 results. EnableCheckers => {}, DisableCheckers => {}, + SilenceCheckers => {}, Excludes => [], UseCC => undef, # C compiler to use for compilation. UseCXX => undef, # C++ compiler to use for compilation. @@ -1742,9 +1743,15 @@ sub ProcessArgs { if ($arg eq "-disable-checker") { shift @$Args; my $Checker = shift @$Args; - # Store $NumArgs to preserve the order the checkers were disabled. - $Options{DisableCheckers}{$Checker} = $NumArgs; - delete $Options{EnableCheckers}{$Checker}; + # Store $NumArgs to preserve the order the checkers are disabled/silenced. + # See whether it is a core checker to disable. That means we do not want + # to emit a report from that checker so we have to silence it. + if (index($Checker, "core") == 0) { + $Options{SilenceCheckers}{$Checker} = $NumArgs; + } else { + $Options{DisableCheckers}{$Checker} = $NumArgs; + delete $Options{EnableCheckers}{$Checker}; + } next; } @@ -1891,6 +1898,14 @@ if ($Options{MaxLoop} > 0) { push @AnalysesToRun, "-analyzer-max-loop $Options{M my $CCC_ANALYZER_ANALYSIS = join ' ', @AnalysesToRun; my $CCC_ANALYZER_PLUGINS = join ' ', map { "-load ".$_ } @{$Options{PluginsToLoad}}; my $CCC_ANALYZER_CONFIG = join ' ', map { "-analyzer-config ".$_ } @{$Options{ConfigOptions}}; + +foreach (sort { $Options{SilenceCheckers}{$a} <=> $Options{SilenceCheckers}{$b} } + keys %{$Options{SilenceCheckers}}) { + # Add checkers in order they were silenced. + $CCC_ANALYZER_CONFIG = + $CCC_ANALYZER_CONFIG." -analyzer-config silence-checkers=".$_; +} + my %EnvVars = ( 'CC' => $Cmd, 'CXX' => $CmdCXX, diff --git a/clang/tools/scan-build/libexec/ccc-analyzer b/clang/tools/scan-build/libexec/ccc-analyzer index 9a4548f1671a5..800f38b5ba241 100755 --- a/clang/tools/scan-build/libexec/ccc-analyzer +++ b/clang/tools/scan-build/libexec/ccc-analyzer @@ -498,7 +498,8 @@ my $HasSDK = 0; # Process the arguments. foreach (my $i = 0; $i < scalar(@ARGV); ++$i) { my $Arg = $ARGV[$i]; - my ($ArgKey) = split /=/,$Arg,2; + my @ArgParts = split /=/,$Arg,2; + my $ArgKey = $ArgParts[0]; # Be friendly to "" in the argument list. if (!defined($ArgKey)) { @@ -566,10 +567,12 @@ foreach (my $i = 0; $i < scalar(@ARGV); ++$i) { push @CompileOpts,$Arg; push @LinkOpts,$Arg; - while ($Cnt > 0) { - ++$i; --$Cnt; - push @CompileOpts, $ARGV[$i]; - push @LinkOpts, $ARGV[$i]; + if (scalar @ArgParts == 1) { + while ($Cnt > 0) { + ++$i; --$Cnt; + push @CompileOpts, $ARGV[$i]; + push @LinkOpts, $ARGV[$i]; + } } next; } @@ -752,7 +755,7 @@ if ($Action eq 'compile' or $Action eq 'link') { DIR => $HtmlDir); $ResultFile = $f; # If the HtmlDir is not set, we should clean up the plist files. - if (!defined $HtmlDir || -z $HtmlDir) { + if (!defined $HtmlDir || $HtmlDir eq "") { $CleanupFile = $f; } } diff --git a/clang/unittests/Analysis/CFGBuildResult.h b/clang/unittests/Analysis/CFGBuildResult.h new file mode 100644 index 0000000000000..17511dcd46c8e --- /dev/null +++ b/clang/unittests/Analysis/CFGBuildResult.h @@ -0,0 +1,69 @@ +//===- unittests/Analysis/CFGBuildResult.h - CFG tests --------------------===// +// +// 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/Analysis/CFG.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Tooling/Tooling.h" + +namespace clang { +namespace analysis { + +class BuildResult { +public: + enum Status { + ToolFailed, + ToolRan, + SawFunctionBody, + BuiltCFG, + }; + + BuildResult(Status S, std::unique_ptr Cfg = nullptr) + : S(S), Cfg(std::move(Cfg)) {} + + Status getStatus() const { return S; } + CFG *getCFG() const { return Cfg.get(); } + +private: + Status S; + std::unique_ptr Cfg; +}; + +class CFGCallback : public ast_matchers::MatchFinder::MatchCallback { +public: + BuildResult TheBuildResult = BuildResult::ToolRan; + + void run(const ast_matchers::MatchFinder::MatchResult &Result) override { + const auto *Func = Result.Nodes.getNodeAs("func"); + Stmt *Body = Func->getBody(); + if (!Body) + return; + TheBuildResult = BuildResult::SawFunctionBody; + CFG::BuildOptions Options; + Options.AddImplicitDtors = true; + if (std::unique_ptr Cfg = + CFG::buildCFG(nullptr, Body, Result.Context, Options)) + TheBuildResult = {BuildResult::BuiltCFG, std::move(Cfg)}; + } +}; + +inline BuildResult BuildCFG(const char *Code) { + CFGCallback Callback; + + ast_matchers::MatchFinder Finder; + Finder.addMatcher(ast_matchers::functionDecl().bind("func"), &Callback); + std::unique_ptr Factory( + tooling::newFrontendActionFactory(&Finder)); + std::vector Args = {"-std=c++11", + "-fno-delayed-template-parsing"}; + if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args)) + return BuildResult::ToolFailed; + return std::move(Callback.TheBuildResult); +} + +} // namespace analysis +} // namespace clang diff --git a/clang/unittests/Analysis/CFGDominatorTree.cpp b/clang/unittests/Analysis/CFGDominatorTree.cpp new file mode 100644 index 0000000000000..8cbd72c94e677 --- /dev/null +++ b/clang/unittests/Analysis/CFGDominatorTree.cpp @@ -0,0 +1,194 @@ +//===- unittests/Analysis/CFGDominatorTree.cpp - CFG tests ----------------===// +// +// 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 "CFGBuildResult.h" +#include "clang/Analysis/Analyses/Dominators.h" +#include "gtest/gtest.h" + +namespace clang { +namespace analysis { +namespace { + +TEST(CFGDominatorTree, DomTree) { + const char *Code = R"(enum Kind { + A + }; + + void f() { + switch(Kind{}) { + case A: + break; + } + })"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // [B3 (ENTRY)] -> [B1] -> [B2] -> [B0 (EXIT)] + // switch case A + + CFG *cfg = Result.getCFG(); + + // Sanity checks. + EXPECT_EQ(cfg->size(), 4u); + + CFGBlock *ExitBlock = *cfg->begin(); + EXPECT_EQ(ExitBlock, &cfg->getExit()); + + CFGBlock *SwitchBlock = *(cfg->begin() + 1); + + CFGBlock *CaseABlock = *(cfg->begin() + 2); + + CFGBlock *EntryBlock = *(cfg->begin() + 3); + EXPECT_EQ(EntryBlock, &cfg->getEntry()); + + // Test the dominator tree. + CFGDomTree Dom; + Dom.buildDominatorTree(cfg); + + EXPECT_TRUE(Dom.dominates(ExitBlock, ExitBlock)); + EXPECT_FALSE(Dom.properlyDominates(ExitBlock, ExitBlock)); + EXPECT_TRUE(Dom.dominates(CaseABlock, ExitBlock)); + EXPECT_TRUE(Dom.dominates(SwitchBlock, ExitBlock)); + EXPECT_TRUE(Dom.dominates(EntryBlock, ExitBlock)); + + EXPECT_TRUE(Dom.dominates(CaseABlock, CaseABlock)); + EXPECT_FALSE(Dom.properlyDominates(CaseABlock, CaseABlock)); + EXPECT_TRUE(Dom.dominates(SwitchBlock, CaseABlock)); + EXPECT_TRUE(Dom.dominates(EntryBlock, CaseABlock)); + + EXPECT_TRUE(Dom.dominates(SwitchBlock, SwitchBlock)); + EXPECT_FALSE(Dom.properlyDominates(SwitchBlock, SwitchBlock)); + EXPECT_TRUE(Dom.dominates(EntryBlock, SwitchBlock)); + + EXPECT_TRUE(Dom.dominates(EntryBlock, EntryBlock)); + EXPECT_FALSE(Dom.properlyDominates(EntryBlock, EntryBlock)); + + // Test the post dominator tree. + + CFGPostDomTree PostDom; + PostDom.buildDominatorTree(cfg); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(CaseABlock, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(SwitchBlock, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(EntryBlock, EntryBlock)); + EXPECT_FALSE(Dom.properlyDominates(EntryBlock, EntryBlock)); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, SwitchBlock)); + EXPECT_TRUE(PostDom.dominates(CaseABlock, SwitchBlock)); + EXPECT_TRUE(PostDom.dominates(SwitchBlock, SwitchBlock)); + EXPECT_FALSE(Dom.properlyDominates(SwitchBlock, SwitchBlock)); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, CaseABlock)); + EXPECT_TRUE(PostDom.dominates(CaseABlock, CaseABlock)); + EXPECT_FALSE(Dom.properlyDominates(CaseABlock, CaseABlock)); + + EXPECT_TRUE(PostDom.dominates(ExitBlock, ExitBlock)); + EXPECT_FALSE(Dom.properlyDominates(ExitBlock, ExitBlock)); + + // Tests for the post dominator tree's virtual root. + EXPECT_TRUE(PostDom.dominates(nullptr, EntryBlock)); + EXPECT_TRUE(PostDom.dominates(nullptr, SwitchBlock)); + EXPECT_TRUE(PostDom.dominates(nullptr, CaseABlock)); + EXPECT_TRUE(PostDom.dominates(nullptr, ExitBlock)); +} + +TEST(CFGDominatorTree, ControlDependency) { + const char *Code = R"(bool coin(); + + void funcWithBranch() { + int x = 0; + if (coin()) { + if (coin()) { + x = 5; + } + int j = 10 / x; + (void)j; + } + };)"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // 1st if 2nd if + // [B5 (ENTRY)] -> [B4] -> [B3] -> [B2] -> [B1] -> [B0 (EXIT)] + // \ \ / / + // \ -------------> / + // ------------------------------> + + CFG *cfg = Result.getCFG(); + + // Sanity checks. + EXPECT_EQ(cfg->size(), 6u); + + CFGBlock *ExitBlock = *cfg->begin(); + EXPECT_EQ(ExitBlock, &cfg->getExit()); + + CFGBlock *NullDerefBlock = *(cfg->begin() + 1); + + CFGBlock *SecondThenBlock = *(cfg->begin() + 2); + + CFGBlock *SecondIfBlock = *(cfg->begin() + 3); + + CFGBlock *FirstIfBlock = *(cfg->begin() + 4); + + CFGBlock *EntryBlock = *(cfg->begin() + 5); + EXPECT_EQ(EntryBlock, &cfg->getEntry()); + + ControlDependencyCalculator Control(cfg); + + EXPECT_TRUE(Control.isControlDependent(SecondThenBlock, SecondIfBlock)); + EXPECT_TRUE(Control.isControlDependent(SecondIfBlock, FirstIfBlock)); + EXPECT_FALSE(Control.isControlDependent(NullDerefBlock, SecondIfBlock)); +} + +TEST(CFGDominatorTree, ControlDependencyWithLoops) { + const char *Code = R"(int test3() { + int x,y,z; + + x = y = z = 1; + if (x > 0) { + while (x >= 0){ + while (y >= x) { + x = x-1; + y = y/2; + } + } + } + z = y; + + return 0; + })"; + BuildResult Result = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, Result.getStatus()); + + // <- [B2] <- + // / \ + // [B8 (ENTRY)] -> [B7] -> [B6] ---> [B5] -> [B4] -> [B3] + // \ | \ / + // \ | <------------- + // \ \ + // --------> [B1] -> [B0 (EXIT)] + + CFG *cfg = Result.getCFG(); + + ControlDependencyCalculator Control(cfg); + + auto GetBlock = [cfg] (unsigned Index) -> CFGBlock * { + assert(Index < cfg->size()); + return *(cfg->begin() + Index); + }; + + // While not immediately obvious, the second block in fact post dominates the + // fifth, hence B5 is not a control dependency of 2. + EXPECT_FALSE(Control.isControlDependent(GetBlock(5), GetBlock(2))); +} + + +} // namespace +} // namespace analysis +} // namespace clang diff --git a/clang/unittests/Analysis/CFGTest.cpp b/clang/unittests/Analysis/CFGTest.cpp index 2c2522d2620dc..27071dc5a5d99 100644 --- a/clang/unittests/Analysis/CFGTest.cpp +++ b/clang/unittests/Analysis/CFGTest.cpp @@ -6,6 +6,7 @@ // //===----------------------------------------------------------------------===// +#include "CFGBuildResult.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include "clang/Analysis/CFG.h" #include "clang/Tooling/Tooling.h" @@ -17,57 +18,6 @@ namespace clang { namespace analysis { namespace { -class BuildResult { -public: - enum Status { - ToolFailed, - ToolRan, - SawFunctionBody, - BuiltCFG, - }; - - BuildResult(Status S, std::unique_ptr Cfg = nullptr) - : S(S), Cfg(std::move(Cfg)) {} - - Status getStatus() const { return S; } - CFG *getCFG() const { return Cfg.get(); } - -private: - Status S; - std::unique_ptr Cfg; -}; - -class CFGCallback : public ast_matchers::MatchFinder::MatchCallback { -public: - BuildResult TheBuildResult = BuildResult::ToolRan; - - void run(const ast_matchers::MatchFinder::MatchResult &Result) override { - const auto *Func = Result.Nodes.getNodeAs("func"); - Stmt *Body = Func->getBody(); - if (!Body) - return; - TheBuildResult = BuildResult::SawFunctionBody; - CFG::BuildOptions Options; - Options.AddImplicitDtors = true; - if (std::unique_ptr Cfg = - CFG::buildCFG(nullptr, Body, Result.Context, Options)) - TheBuildResult = {BuildResult::BuiltCFG, std::move(Cfg)}; - } -}; - -BuildResult BuildCFG(const char *Code) { - CFGCallback Callback; - - ast_matchers::MatchFinder Finder; - Finder.addMatcher(ast_matchers::functionDecl().bind("func"), &Callback); - std::unique_ptr Factory( - tooling::newFrontendActionFactory(&Finder)); - std::vector Args = {"-std=c++11", "-fno-delayed-template-parsing"}; - if (!tooling::runToolOnCodeWithArgs(Factory->create(), Code, Args)) - return BuildResult::ToolFailed; - return std::move(Callback.TheBuildResult); -} - // Constructing a CFG for a range-based for over a dependent type fails (but // should not crash). TEST(CFG, RangeBasedForOverDependentType) { @@ -117,6 +67,139 @@ TEST(CFG, IsLinear) { expectLinear(true, "void foo() { foo(); }"); // Recursion is not our problem. } +TEST(CFG, ElementRefIterator) { + const char *Code = R"(void f() { + int i; + int j; + i = 5; + i = 6; + j = 7; + })"; + + BuildResult B = BuildCFG(Code); + EXPECT_EQ(BuildResult::BuiltCFG, B.getStatus()); + CFG *Cfg = B.getCFG(); + + // [B2 (ENTRY)] + // Succs (1): B1 + + // [B1] + // 1: int i; + // 2: int j; + // 3: i = 5 + // 4: i = 6 + // 5: j = 7 + // Preds (1): B2 + // Succs (1): B0 + + // [B0 (EXIT)] + // Preds (1): B1 + CFGBlock *MainBlock = *(Cfg->begin() + 1); + + constexpr CFGBlock::ref_iterator::difference_type MainBlockSize = 4; + + // The rest of this test looks totally insane, but there iterators are + // templates under the hood, to it's important to instantiate everything for + // proper converage. + + // Non-reverse, non-const version + size_t Index = 0; + for (CFGBlock::CFGElementRef ElementRef : MainBlock->refs()) { + EXPECT_EQ(ElementRef.getParent(), MainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + ++Index; + } + EXPECT_TRUE(*MainBlock->ref_begin() < *(MainBlock->ref_begin() + 1)); + EXPECT_TRUE(*MainBlock->ref_begin() == *MainBlock->ref_begin()); + EXPECT_FALSE(*MainBlock->ref_begin() != *MainBlock->ref_begin()); + + EXPECT_TRUE(MainBlock->ref_begin() < MainBlock->ref_begin() + 1); + EXPECT_TRUE(MainBlock->ref_begin() == MainBlock->ref_begin()); + EXPECT_FALSE(MainBlock->ref_begin() != MainBlock->ref_begin()); + EXPECT_EQ(MainBlock->ref_end() - MainBlock->ref_begin(), MainBlockSize + 1); + EXPECT_EQ(MainBlock->ref_end() - MainBlockSize - 1, MainBlock->ref_begin()); + EXPECT_EQ(MainBlock->ref_begin() + MainBlockSize + 1, MainBlock->ref_end()); + EXPECT_EQ(MainBlock->ref_begin()++, MainBlock->ref_begin()); + EXPECT_EQ(++(MainBlock->ref_begin()), MainBlock->ref_begin() + 1); + + // Non-reverse, non-const version + const CFGBlock *CMainBlock = MainBlock; + Index = 0; + for (CFGBlock::ConstCFGElementRef ElementRef : CMainBlock->refs()) { + EXPECT_EQ(ElementRef.getParent(), CMainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + ++Index; + } + EXPECT_TRUE(*CMainBlock->ref_begin() < *(CMainBlock->ref_begin() + 1)); + EXPECT_TRUE(*CMainBlock->ref_begin() == *CMainBlock->ref_begin()); + EXPECT_FALSE(*CMainBlock->ref_begin() != *CMainBlock->ref_begin()); + + EXPECT_TRUE(CMainBlock->ref_begin() < CMainBlock->ref_begin() + 1); + EXPECT_TRUE(CMainBlock->ref_begin() == CMainBlock->ref_begin()); + EXPECT_FALSE(CMainBlock->ref_begin() != CMainBlock->ref_begin()); + EXPECT_EQ(CMainBlock->ref_end() - CMainBlock->ref_begin(), MainBlockSize + 1); + EXPECT_EQ(CMainBlock->ref_end() - MainBlockSize - 1, CMainBlock->ref_begin()); + EXPECT_EQ(CMainBlock->ref_begin() + MainBlockSize + 1, CMainBlock->ref_end()); + EXPECT_EQ(CMainBlock->ref_begin()++, CMainBlock->ref_begin()); + EXPECT_EQ(++(CMainBlock->ref_begin()), CMainBlock->ref_begin() + 1); + + // Reverse, non-const version + Index = MainBlockSize; + for (CFGBlock::CFGElementRef ElementRef : MainBlock->rrefs()) { + llvm::errs() << Index << '\n'; + EXPECT_EQ(ElementRef.getParent(), MainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + --Index; + } + EXPECT_FALSE(*MainBlock->rref_begin() < *(MainBlock->rref_begin() + 1)); + EXPECT_TRUE(*MainBlock->rref_begin() == *MainBlock->rref_begin()); + EXPECT_FALSE(*MainBlock->rref_begin() != *MainBlock->rref_begin()); + + EXPECT_TRUE(MainBlock->rref_begin() < MainBlock->rref_begin() + 1); + EXPECT_TRUE(MainBlock->rref_begin() == MainBlock->rref_begin()); + EXPECT_FALSE(MainBlock->rref_begin() != MainBlock->rref_begin()); + EXPECT_EQ(MainBlock->rref_end() - MainBlock->rref_begin(), MainBlockSize + 1); + EXPECT_EQ(MainBlock->rref_end() - MainBlockSize - 1, MainBlock->rref_begin()); + EXPECT_EQ(MainBlock->rref_begin() + MainBlockSize + 1, MainBlock->rref_end()); + EXPECT_EQ(MainBlock->rref_begin()++, MainBlock->rref_begin()); + EXPECT_EQ(++(MainBlock->rref_begin()), MainBlock->rref_begin() + 1); + + // Reverse, const version + Index = MainBlockSize; + for (CFGBlock::ConstCFGElementRef ElementRef : CMainBlock->rrefs()) { + EXPECT_EQ(ElementRef.getParent(), CMainBlock); + EXPECT_EQ(ElementRef.getIndexInBlock(), Index); + EXPECT_TRUE(ElementRef->getAs()); + EXPECT_TRUE((*ElementRef).getAs()); + EXPECT_EQ(ElementRef.getParent(), MainBlock); + --Index; + } + EXPECT_FALSE(*CMainBlock->rref_begin() < *(CMainBlock->rref_begin() + 1)); + EXPECT_TRUE(*CMainBlock->rref_begin() == *CMainBlock->rref_begin()); + EXPECT_FALSE(*CMainBlock->rref_begin() != *CMainBlock->rref_begin()); + + EXPECT_TRUE(CMainBlock->rref_begin() < CMainBlock->rref_begin() + 1); + EXPECT_TRUE(CMainBlock->rref_begin() == CMainBlock->rref_begin()); + EXPECT_FALSE(CMainBlock->rref_begin() != CMainBlock->rref_begin()); + EXPECT_EQ(CMainBlock->rref_end() - CMainBlock->rref_begin(), + MainBlockSize + 1); + EXPECT_EQ(CMainBlock->rref_end() - MainBlockSize - 1, + CMainBlock->rref_begin()); + EXPECT_EQ(CMainBlock->rref_begin() + MainBlockSize + 1, + CMainBlock->rref_end()); + EXPECT_EQ(CMainBlock->rref_begin()++, CMainBlock->rref_begin()); + EXPECT_EQ(++(CMainBlock->rref_begin()), CMainBlock->rref_begin() + 1); +} + } // namespace } // namespace analysis } // namespace clang diff --git a/clang/unittests/Analysis/CMakeLists.txt b/clang/unittests/Analysis/CMakeLists.txt index c760ae2d82b7e..e1f7a16a450d3 100644 --- a/clang/unittests/Analysis/CMakeLists.txt +++ b/clang/unittests/Analysis/CMakeLists.txt @@ -3,6 +3,7 @@ set(LLVM_LINK_COMPONENTS ) add_clang_unittest(ClangAnalysisTests + CFGDominatorTree.cpp CFGTest.cpp CloneDetectionTest.cpp ExprMutationAnalyzerTest.cpp diff --git a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp index 43e0e75c31446..b4f22d5cb0324 100644 --- a/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp +++ b/clang/unittests/CrossTU/CrossTranslationUnitTest.cpp @@ -7,6 +7,7 @@ //===----------------------------------------------------------------------===// #include "clang/CrossTU/CrossTranslationUnit.h" +#include "clang/Frontend/CompilerInstance.h" #include "clang/AST/ASTConsumer.h" #include "clang/Frontend/FrontendAction.h" #include "clang/Tooling/Tooling.h" @@ -27,13 +28,18 @@ class CTUASTConsumer : public clang::ASTConsumer { : CTU(CI), Success(Success) {} void HandleTranslationUnit(ASTContext &Ctx) { + auto FindFInTU = [](const TranslationUnitDecl *TU) { + const FunctionDecl *FD = nullptr; + for (const Decl *D : TU->decls()) { + FD = dyn_cast(D); + if (FD && FD->getName() == "f") + break; + } + return FD; + }; + const TranslationUnitDecl *TU = Ctx.getTranslationUnitDecl(); - const FunctionDecl *FD = nullptr; - for (const Decl *D : TU->decls()) { - FD = dyn_cast(D); - if (FD && FD->getName() == "f") - break; - } + const FunctionDecl *FD = FindFInTU(TU); assert(FD && FD->getName() == "f"); bool OrigFDHasBody = FD->hasBody(); @@ -70,12 +76,36 @@ class CTUASTConsumer : public clang::ASTConsumer { EXPECT_TRUE(llvm::sys::fs::exists(ASTFileName)); // Load the definition from the AST file. - llvm::Expected NewFDorError = - CTU.getCrossTUDefinition(FD, "", IndexFileName); - EXPECT_TRUE((bool)NewFDorError); - const FunctionDecl *NewFD = *NewFDorError; - - *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; + llvm::Expected NewFDorError = handleExpected( + CTU.getCrossTUDefinition(FD, "", IndexFileName, false), + []() { return nullptr; }, [](IndexError &) {}); + + if (NewFDorError) { + const FunctionDecl *NewFD = *NewFDorError; + *Success = NewFD && NewFD->hasBody() && !OrigFDHasBody; + + if (NewFD) { + // Check GetImportedFromSourceLocation. + llvm::Optional> SLocResult = + CTU.getImportedFromSourceLocation(NewFD->getLocation()); + EXPECT_TRUE(SLocResult); + if (SLocResult) { + SourceLocation OrigSLoc = (*SLocResult).first; + ASTUnit *OrigUnit = (*SLocResult).second; + // OrigUnit is created internally by CTU (is not the + // ASTWithDefinition). + TranslationUnitDecl *OrigTU = + OrigUnit->getASTContext().getTranslationUnitDecl(); + const FunctionDecl *FDWithDefinition = FindFInTU(OrigTU); + EXPECT_TRUE(FDWithDefinition); + if (FDWithDefinition) { + EXPECT_EQ(FDWithDefinition->getName(), "f"); + EXPECT_TRUE(FDWithDefinition->isThisDeclarationADefinition()); + EXPECT_EQ(OrigSLoc, FDWithDefinition->getLocation()); + } + } + } + } } private: @@ -85,26 +115,37 @@ class CTUASTConsumer : public clang::ASTConsumer { class CTUAction : public clang::ASTFrontendAction { public: - CTUAction(bool *Success) : Success(Success) {} + CTUAction(bool *Success, unsigned OverrideLimit) + : Success(Success), OverrideLimit(OverrideLimit) {} protected: std::unique_ptr CreateASTConsumer(clang::CompilerInstance &CI, StringRef) override { + CI.getAnalyzerOpts()->CTUImportThreshold = OverrideLimit; return llvm::make_unique(CI, Success); } private: bool *Success; + const unsigned OverrideLimit; }; } // end namespace TEST(CrossTranslationUnit, CanLoadFunctionDefinition) { bool Success = false; - EXPECT_TRUE(tooling::runToolOnCode(new CTUAction(&Success), "int f(int);")); + EXPECT_TRUE( + tooling::runToolOnCode(new CTUAction(&Success, 1u), "int f(int);")); EXPECT_TRUE(Success); } +TEST(CrossTranslationUnit, RespectsLoadThreshold) { + bool Success = false; + EXPECT_TRUE( + tooling::runToolOnCode(new CTUAction(&Success, 0u), "int f(int);")); + EXPECT_FALSE(Success); +} + TEST(CrossTranslationUnit, IndexFormatCanBeParsed) { llvm::StringMap Index; Index["a"] = "/b/f1"; diff --git a/clang/unittests/StaticAnalyzer/CMakeLists.txt b/clang/unittests/StaticAnalyzer/CMakeLists.txt index 5348a0a2bc8b7..1b8044c9e1fd7 100644 --- a/clang/unittests/StaticAnalyzer/CMakeLists.txt +++ b/clang/unittests/StaticAnalyzer/CMakeLists.txt @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS add_clang_unittest(StaticAnalysisTests AnalyzerOptionsTest.cpp + CallDescriptionTest.cpp StoreTest.cpp RegisterCustomCheckersTest.cpp SymbolReaperTest.cpp diff --git a/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp b/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp new file mode 100644 index 0000000000000..9201922f5be05 --- /dev/null +++ b/clang/unittests/StaticAnalyzer/CallDescriptionTest.cpp @@ -0,0 +1,162 @@ +//===- unittests/StaticAnalyzer/CallDescriptionTest.cpp -------------------===// +// +// 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 "Reusables.h" + +#include "clang/StaticAnalyzer/Core/PathSensitive/CallEvent.h" +#include "clang/Tooling/Tooling.h" +#include "gtest/gtest.h" + +namespace clang { +namespace ento { +namespace { + +// A wrapper around CallDescriptionMap that allows verifying that +// all functions have been found. This is needed because CallDescriptionMap +// isn't supposed to support iteration. +class ResultMap { + size_t Found, Total; + CallDescriptionMap Impl; + +public: + ResultMap(std::initializer_list> Data) + : Found(0), + Total(std::count_if(Data.begin(), Data.end(), + [](const std::pair &Pair) { + return Pair.second == true; + })), + Impl(std::move(Data)) {} + + const bool *lookup(const CallEvent &Call) { + const bool *Result = Impl.lookup(Call); + // If it's a function we expected to find, remember that we've found it. + if (Result && *Result) + ++Found; + return Result; + } + + // Fail the test if we haven't found all the true-calls we were looking for. + ~ResultMap() { EXPECT_EQ(Found, Total); } +}; + +// Scan the code body for call expressions and see if we find all calls that +// we were supposed to find ("true" in the provided ResultMap) and that we +// don't find the ones that we weren't supposed to find +// ("false" in the ResultMap). +class CallDescriptionConsumer : public ExprEngineConsumer { + ResultMap &RM; + void performTest(const Decl *D) { + using namespace ast_matchers; + + if (!D->hasBody()) + return; + + const CallExpr *CE = findNode(D, callExpr()); + const StackFrameContext *SFC = + Eng.getAnalysisDeclContextManager().getStackFrame(D); + ProgramStateRef State = Eng.getInitialState(SFC); + CallEventRef<> Call = + Eng.getStateManager().getCallEventManager().getCall(CE, State, SFC); + + const bool *LookupResult = RM.lookup(*Call); + // Check that we've found the function in the map + // with the correct description. + EXPECT_TRUE(LookupResult && *LookupResult); + + // ResultMap is responsible for making sure that we've found *all* calls. + } + +public: + CallDescriptionConsumer(CompilerInstance &C, + ResultMap &RM) + : ExprEngineConsumer(C), RM(RM) {} + + bool HandleTopLevelDecl(DeclGroupRef DG) override { + for (const auto *D : DG) + performTest(D); + return true; + } +}; + +class CallDescriptionAction : public ASTFrontendAction { + ResultMap RM; + +public: + CallDescriptionAction( + std::initializer_list> Data) + : RM(Data) {} + + std::unique_ptr CreateASTConsumer(CompilerInstance &Compiler, + StringRef File) override { + return llvm::make_unique(Compiler, RM); + } +}; + +TEST(CallEvent, CallDescription) { + // Test simple name matching. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"bar"}, false}, // false: there's no call to 'bar' in this code. + {{"foo"}, true}, // true: there's a call to 'foo' in this code. + }), "void foo(); void bar() { foo(); }")); + + // Test arguments check. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"foo", 1}, true}, + {{"foo", 2}, false}, + }), "void foo(int); void foo(int, int); void bar() { foo(1); }")); + + // Test lack of arguments check. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"foo", None}, true}, + {{"foo", 2}, false}, + }), "void foo(int); void foo(int, int); void bar() { foo(1); }")); + + // Test qualified names. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{{"std", "basic_string", "c_str"}}, true}, + }), + "namespace std { inline namespace __1 {" + " template class basic_string {" + " public:" + " T *c_str();" + " };" + "}}" + "void foo() {" + " using namespace std;" + " basic_string s;" + " s.c_str();" + "}")); + + // A negative test for qualified names. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{{"foo", "bar"}}, false}, + {{{"bar", "foo"}}, false}, + {{"foo"}, true}, + }), "void foo(); struct bar { void foo(); }; void test() { foo(); }")); + + // Test CDF_MaybeBuiltin - a flag that allows matching weird builtins. + EXPECT_TRUE(tooling::runToolOnCode( + new CallDescriptionAction({ + {{"memset", 3}, false}, + {{CDF_MaybeBuiltin, "memset", 3}, true} + }), + "void foo() {" + " int x;" + " __builtin___memset_chk(&x, 0, sizeof(x)," + " __builtin_object_size(&x, 0));" + "}")); +} + +} // namespace +} // namespace ento +} // namespace clang diff --git a/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp b/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp index d8988a0ee30ef..940e9a8d8f0b6 100644 --- a/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp +++ b/clang/unittests/StaticAnalyzer/RegisterCustomCheckersTest.cpp @@ -30,7 +30,7 @@ class TestAction : public ASTFrontendAction { void FlushDiagnosticsImpl(std::vector &Diags, FilesMade *filesMade) override { for (const auto *PD : Diags) - Output << PD->getCheckName() << ":" << PD->getShortDescription(); + Output << PD->getCheckerName() << ":" << PD->getShortDescription(); } StringRef getName() const override { return "Test"; } @@ -46,7 +46,7 @@ class TestAction : public ASTFrontendAction { std::unique_ptr AnalysisConsumer = CreateAnalysisConsumer(Compiler); AnalysisConsumer->AddDiagnosticConsumer(new DiagConsumer(DiagsOutput)); - Compiler.getAnalyzerOpts()->CheckersControlList = { + Compiler.getAnalyzerOpts()->CheckersAndPackages = { {"custom.CustomChecker", true}}; AnalysisConsumer->AddCheckerRegistrationFn([](CheckerRegistry &Registry) { Registry.addChecker("custom.CustomChecker", "Description", ""); diff --git a/clang/unittests/StaticAnalyzer/Reusables.h b/clang/unittests/StaticAnalyzer/Reusables.h index 06aed884f6bf4..bac2808369c24 100644 --- a/clang/unittests/StaticAnalyzer/Reusables.h +++ b/clang/unittests/StaticAnalyzer/Reusables.h @@ -17,16 +17,24 @@ namespace clang { namespace ento { +// Find a node in the current AST that matches a matcher. +template +const T *findNode(const Decl *Where, MatcherT What) { + using namespace ast_matchers; + auto Matches = match(decl(hasDescendant(What.bind("root"))), + *Where, Where->getASTContext()); + assert(Matches.size() <= 1 && "Ambiguous match!"); + assert(Matches.size() >= 1 && "Match not found!"); + const T *Node = selectFirst("root", Matches); + assert(Node && "Type mismatch!"); + return Node; +} + // Find a declaration in the current AST by name. template const T *findDeclByName(const Decl *Where, StringRef Name) { using namespace ast_matchers; - auto Matcher = decl(hasDescendant(namedDecl(hasName(Name)).bind("d"))); - auto Matches = match(Matcher, *Where, Where->getASTContext()); - assert(Matches.size() == 1 && "Ambiguous name!"); - const T *Node = selectFirst("d", Matches); - assert(Node && "Name not found!"); - return Node; + return findNode(Where, namedDecl(hasName(Name))); } // A re-usable consumer that constructs ExprEngine out of CompilerInvocation. @@ -50,7 +58,7 @@ class ExprEngineConsumer : public ASTConsumer { ExprEngineConsumer(CompilerInstance &C) : C(C), ChkMgr(C.getASTContext(), *C.getAnalyzerOpts()), CTU(C), Consumers(), - AMgr(C.getASTContext(), C.getDiagnostics(), Consumers, + AMgr(C.getASTContext(), Consumers, CreateRegionStoreManager, CreateRangeConstraintManager, &ChkMgr, *C.getAnalyzerOpts()), VisitedCallees(), FS(), diff --git a/clang/utils/analyzer/exploded-graph-rewriter.py b/clang/utils/analyzer/exploded-graph-rewriter.py index dbfd086215385..79055b433e8de 100755 --- a/clang/utils/analyzer/exploded-graph-rewriter.py +++ b/clang/utils/analyzer/exploded-graph-rewriter.py @@ -13,19 +13,52 @@ import argparse import collections +import difflib import json import logging +import os import re +#===-----------------------------------------------------------------------===# +# These data structures represent a deserialized ExplodedGraph. +#===-----------------------------------------------------------------------===# + + +# A helper function for finding the difference between two dictionaries. +def diff_dicts(curr, prev): + removed = [k for k in prev if k not in curr or curr[k] != prev[k]] + added = [k for k in curr if k not in prev or curr[k] != prev[k]] + return (removed, added) + + +# Represents any program state trait that is a dictionary of key-value pairs. +class GenericMap(object): + def __init__(self, items): + self.generic_map = collections.OrderedDict(items) + + def diff(self, prev): + return diff_dicts(self.generic_map, prev.generic_map) + + def is_different(self, prev): + removed, added = self.diff(prev) + return len(removed) != 0 or len(added) != 0 + + # A deserialized source location. class SourceLocation(object): def __init__(self, json_loc): super(SourceLocation, self).__init__() + logging.debug('json: %s' % json_loc) self.line = json_loc['line'] self.col = json_loc['column'] - self.filename = json_loc['filename'] \ - if 'filename' in json_loc else '(main file)' + self.filename = os.path.basename(json_loc['file']) \ + if 'file' in json_loc else '(main file)' + self.spelling = SourceLocation(json_loc['spelling']) \ + if 'spelling' in json_loc else None + + def is_macro(self): + return self.spelling is not None # A deserialized program point. @@ -34,11 +67,19 @@ def __init__(self, json_pp): super(ProgramPoint, self).__init__() self.kind = json_pp['kind'] self.tag = json_pp['tag'] + self.node_id = json_pp['node_id'] + self.is_sink = bool(json_pp['is_sink']) + self.has_report = bool(json_pp['has_report']) if self.kind == 'Edge': self.src_id = json_pp['src_id'] self.dst_id = json_pp['dst_id'] elif self.kind == 'Statement': + logging.debug(json_pp) self.stmt_kind = json_pp['stmt_kind'] + self.cast_kind = json_pp['cast_kind'] \ + if 'cast_kind' in json_pp else None + self.stmt_point_kind = json_pp['stmt_point_kind'] + self.stmt_id = json_pp['stmt_id'] self.pointer = json_pp['pointer'] self.pretty = json_pp['pretty'] self.loc = SourceLocation(json_pp['location']) \ @@ -47,13 +88,24 @@ def __init__(self, json_pp): self.block_id = json_pp['block_id'] -# A value of a single expression in a deserialized Environment. -class EnvironmentBinding(object): - def __init__(self, json_eb): - super(EnvironmentBinding, self).__init__() - self.stmt_id = json_eb['stmt_id'] - self.pretty = json_eb['pretty'] - self.value = json_eb['value'] +# A single expression acting as a key in a deserialized Environment. +class EnvironmentBindingKey(object): + def __init__(self, json_ek): + super(EnvironmentBindingKey, self).__init__() + # CXXCtorInitializer is not a Stmt! + self.stmt_id = json_ek['stmt_id'] if 'stmt_id' in json_ek \ + else json_ek['init_id'] + self.pretty = json_ek['pretty'] + self.kind = json_ek['kind'] if 'kind' in json_ek else None + + def _key(self): + return self.stmt_id + + def __eq__(self, other): + return self._key() == other._key() + + def __hash__(self): + return hash(self._key()) # Deserialized description of a location context. @@ -63,7 +115,17 @@ def __init__(self, json_frame): self.lctx_id = json_frame['lctx_id'] self.caption = json_frame['location_context'] self.decl = json_frame['calling'] - self.line = json_frame['call_line'] + self.loc = SourceLocation(json_frame['location']) \ + if json_frame['location'] is not None else None + + def _key(self): + return self.lctx_id + + def __eq__(self, other): + return self._key() == other._key() + + def __hash__(self): + return hash(self._key()) # A group of deserialized Environment bindings that correspond to a specific @@ -72,24 +134,66 @@ class EnvironmentFrame(object): def __init__(self, json_frame): super(EnvironmentFrame, self).__init__() self.location_context = LocationContext(json_frame) - self.bindings = [EnvironmentBinding(b) for b in json_frame['items']] \ - if json_frame['items'] is not None else [] + self.bindings = collections.OrderedDict( + [(EnvironmentBindingKey(b), + b['value']) for b in json_frame['items']] + if json_frame['items'] is not None else []) + def diff_bindings(self, prev): + return diff_dicts(self.bindings, prev.bindings) -# A deserialized Environment. -class Environment(object): + def is_different(self, prev): + removed, added = self.diff_bindings(prev) + return len(removed) != 0 or len(added) != 0 + + +# A deserialized Environment. This class can also hold other entities that +# are similar to Environment, such as Objects Under Construction. +class GenericEnvironment(object): def __init__(self, json_e): - super(Environment, self).__init__() + super(GenericEnvironment, self).__init__() self.frames = [EnvironmentFrame(f) for f in json_e] + def diff_frames(self, prev): + # TODO: It's difficult to display a good diff when frame numbers shift. + if len(self.frames) != len(prev.frames): + return None + + updated = [] + for i in range(len(self.frames)): + f = self.frames[i] + prev_f = prev.frames[i] + if f.location_context == prev_f.location_context: + if f.is_different(prev_f): + updated.append(i) + else: + # We have the whole frame replaced with another frame. + # TODO: Produce a nice diff. + return None + + # TODO: Add support for added/removed. + return updated + + def is_different(self, prev): + updated = self.diff_frames(prev) + return updated is None or len(updated) > 0 + + +# A single binding key in a deserialized RegionStore cluster. +class StoreBindingKey(object): + def __init__(self, json_sk): + super(StoreBindingKey, self).__init__() + self.kind = json_sk['kind'] + self.offset = json_sk['offset'] + + def _key(self): + return (self.kind, self.offset) -# A single binding in a deserialized RegionStore cluster. -class StoreBinding(object): - def __init__(self, json_sb): - super(StoreBinding, self).__init__() - self.kind = json_sb['kind'] - self.offset = json_sb['offset'] - self.value = json_sb['value'] + def __eq__(self, other): + return self._key() == other._key() + + def __hash__(self): + return hash(self._key()) # A single cluster of the deserialized RegionStore. @@ -97,14 +201,70 @@ class StoreCluster(object): def __init__(self, json_sc): super(StoreCluster, self).__init__() self.base_region = json_sc['cluster'] - self.bindings = [StoreBinding(b) for b in json_sc['items']] + self.bindings = collections.OrderedDict( + [(StoreBindingKey(b), b['value']) for b in json_sc['items']]) + + def diff_bindings(self, prev): + return diff_dicts(self.bindings, prev.bindings) + + def is_different(self, prev): + removed, added = self.diff_bindings(prev) + return len(removed) != 0 or len(added) != 0 # A deserialized RegionStore. class Store(object): def __init__(self, json_s): super(Store, self).__init__() - self.clusters = [StoreCluster(c) for c in json_s] + self.ptr = json_s['pointer'] + self.clusters = collections.OrderedDict( + [(c['pointer'], StoreCluster(c)) for c in json_s['items']]) + + def diff_clusters(self, prev): + removed = [k for k in prev.clusters if k not in self.clusters] + added = [k for k in self.clusters if k not in prev.clusters] + updated = [k for k in prev.clusters if k in self.clusters + and prev.clusters[k].is_different(self.clusters[k])] + return (removed, added, updated) + + def is_different(self, prev): + removed, added, updated = self.diff_clusters(prev) + return len(removed) != 0 or len(added) != 0 or len(updated) != 0 + + +# Deserialized messages from a single checker in a single program state. +# Basically a list of raw strings. +class CheckerLines(object): + def __init__(self, json_lines): + super(CheckerLines, self).__init__() + self.lines = json_lines + + def diff_lines(self, prev): + lines = difflib.ndiff(prev.lines, self.lines) + return [l.strip() for l in lines + if l.startswith('+') or l.startswith('-')] + + def is_different(self, prev): + return len(self.diff_lines(prev)) > 0 + + +# Deserialized messages of all checkers, separated by checker. +class CheckerMessages(object): + def __init__(self, json_m): + super(CheckerMessages, self).__init__() + self.items = collections.OrderedDict( + [(m['checker'], CheckerLines(m['messages'])) for m in json_m]) + + def diff_messages(self, prev): + removed = [k for k in prev.items if k not in self.items] + added = [k for k in self.items if k not in prev.items] + updated = [k for k in prev.items if k in self.items + and prev.items[k].is_different(self.items[k])] + return (removed, added, updated) + + def is_different(self, prev): + removed, added, updated = self.diff_messages(prev) + return len(removed) != 0 or len(added) != 0 or len(updated) != 0 # A deserialized program state. @@ -113,15 +273,42 @@ def __init__(self, state_id, json_ps): super(ProgramState, self).__init__() logging.debug('Adding ProgramState ' + str(state_id)) + if json_ps is None: + json_ps = { + 'store': None, + 'environment': None, + 'constraints': None, + 'dynamic_types': None, + 'constructing_objects': None, + 'checker_messages': None + } + self.state_id = state_id + self.store = Store(json_ps['store']) \ if json_ps['store'] is not None else None - self.environment = Environment(json_ps['environment']) \ + + self.environment = \ + GenericEnvironment(json_ps['environment']['items']) \ if json_ps['environment'] is not None else None - # TODO: Objects under construction. - # TODO: Constraint ranges. - # TODO: Dynamic types of objects. - # TODO: Checker messages. + + self.constraints = GenericMap([ + (c['symbol'], c['range']) for c in json_ps['constraints'] + ]) if json_ps['constraints'] is not None else None + + self.dynamic_types = GenericMap([ + (t['region'], '%s%s' % (t['dyn_type'], + ' (or a sub-class)' + if t['sub_classable'] else '')) + for t in json_ps['dynamic_types']]) \ + if json_ps['dynamic_types'] is not None else None + + self.constructing_objects = \ + GenericEnvironment(json_ps['constructing_objects']) \ + if json_ps['constructing_objects'] is not None else None + + self.checker_messages = CheckerMessages(json_ps['checker_messages']) \ + if json_ps['checker_messages'] is not None else None # A deserialized exploded graph node. Has a default constructor because it @@ -135,12 +322,12 @@ def __init__(self): def construct(self, node_id, json_node): logging.debug('Adding ' + node_id) - self.node_id = json_node['node_id'] - self.ptr = json_node['pointer'] + self.ptr = node_id[4:] self.points = [ProgramPoint(p) for p in json_node['program_points']] + self.node_id = self.points[-1].node_id self.state = ProgramState(json_node['state_id'], - json_node['program_state']) \ - if json_node['program_state'] is not None else None + json_node['program_state'] + if json_node['program_state'] is not None else None); assert self.node_name() == node_id @@ -199,6 +386,8 @@ def add_raw_line(self, raw_line): .replace('\\"', '"') \ .replace('\\{', '{') \ .replace('\\}', '}') \ + .replace('\\\\', '\\') \ + .replace('\\|', '|') \ .replace('\\<', '\\\\<') \ .replace('\\>', '\\\\>') \ .rstrip(',') @@ -209,29 +398,92 @@ def add_raw_line(self, raw_line): logging.debug('Skipping.') +#===-----------------------------------------------------------------------===# +# Visitors traverse a deserialized ExplodedGraph and do different things +# with every node and edge. +#===-----------------------------------------------------------------------===# + + # A visitor that dumps the ExplodedGraph into a DOT file with fancy HTML-based # syntax highlighing. class DotDumpVisitor(object): - def __init__(self): + def __init__(self, do_diffs, dark_mode, gray_mode, + topo_mode, dump_dot_only): super(DotDumpVisitor, self).__init__() + self._do_diffs = do_diffs + self._dark_mode = dark_mode + self._gray_mode = gray_mode + self._topo_mode = topo_mode + self._dump_dot_only = dump_dot_only + self._output = [] + + def _dump_raw(self, s): + if self._dump_dot_only: + print(s, end='') + else: + self._output.append(s) + + def output(self): + assert not self._dump_dot_only + return ''.join(self._output) + + def _dump(self, s): + s = s.replace('&', '&') \ + .replace('{', '\\{') \ + .replace('}', '\\}') \ + .replace('\\<', '<') \ + .replace('\\>', '>') \ + .replace('\\l', '
') \ + .replace('|', '\\|') + if self._gray_mode: + s = re.sub(r'', '', s) + s = re.sub(r'', '', s) + self._dump_raw(s) + + @staticmethod + def _diff_plus_minus(is_added): + if is_added is None: + return '' + if is_added: + return '+' + return '-' @staticmethod - def _dump_raw(s): - print(s, end='') + def _short_pretty(s): + if s is None: + return None + if len(s) < 20: + return s + left = s.find('{') + right = s.rfind('}') + if left == -1 or right == -1 or left >= right: + return s + candidate = s[0:left + 1] + ' ... ' + s[right:] + if len(candidate) >= len(s): + return s + return candidate @staticmethod - def _dump(s): - print(s.replace('&', '&') - .replace('{', '\\{') - .replace('}', '\\}') - .replace('\\<', '<') - .replace('\\>', '>') - .replace('\\l', '
') - .replace('|', ''), end='') + def _make_sloc(loc): + if loc is None: + return 'Invalid Source Location' + + def make_plain_loc(loc): + return '%s:%s:%s' \ + % (loc.filename, loc.line, loc.col) + + if loc.is_macro(): + return '%s ' \ + '(spelling at %s)' \ + % (make_plain_loc(loc), make_plain_loc(loc.spelling)) + + return make_plain_loc(loc) def visit_begin_graph(self, graph): self._graph = graph self._dump_raw('digraph "ExplodedGraph" {\n') + if self._dark_mode: + self._dump_raw('bgcolor="gray10";\n') self._dump_raw('label="";\n') def visit_program_point(self, p): @@ -241,132 +493,408 @@ def visit_program_point(self, p): 'PostStmtPurgeDeadSymbols']: color = 'red' elif p.kind in ['CallEnter', 'CallExitBegin', 'CallExitEnd']: - color = 'blue' + color = 'dodgerblue' if self._dark_mode else 'blue' elif p.kind in ['Statement']: - color = 'cyan3' + color = 'cyan4' else: color = 'forestgreen' + self._dump('%s.' % p.node_id) + if p.kind == 'Statement': - if p.loc is not None: - self._dump('' - '%s:%s:%s:' - '' - '%s%s' - % (p.loc.filename, p.loc.line, - p.loc.col, color, p.stmt_kind, p.pretty)) - else: - self._dump('' - 'Invalid Source Location:' - '' - '%s%s' - % (color, p.stmt_kind, p.pretty)) + # This avoids pretty-printing huge statements such as CompoundStmt. + # Such statements show up only at [Pre|Post]StmtPurgeDeadSymbols + skip_pretty = 'PurgeDeadSymbols' in p.stmt_point_kind + stmt_color = 'cyan3' + self._dump('%s:' + '' + '%s ' + 'S%s' + '%s' + '%s' + % (self._make_sloc(p.loc), color, + '%s (%s)' % (p.stmt_kind, p.cast_kind) + if p.cast_kind is not None else p.stmt_kind, + p.stmt_id, stmt_color, p.stmt_point_kind, + self._short_pretty(p.pretty) + if not skip_pretty else '')) elif p.kind == 'Edge': - self._dump('-' + self._dump('' '' '%s' '[B%d] -\\> [B%d]' - % (color, p.kind, p.src_id, p.dst_id)) + % (color, 'BlockEdge', p.src_id, p.dst_id)) + elif p.kind == 'BlockEntrance': + self._dump('' + '' + '%s' + '[B%d]' + % (color, p.kind, p.block_id)) else: # TODO: Print more stuff for other kinds of points. - self._dump('-' + self._dump('' '' '%s' % (color, p.kind)) - def visit_environment(self, e): + if p.tag is not None: + self._dump('' + '' + 'Tag: ' + '%s' % p.tag) + + if p.has_report: + self._dump('' + '' + 'Bug Report Attached' + '') + if p.is_sink: + self._dump('' + '' + 'Sink Node' + '') + + def visit_environment(self, e, prev_e=None): self._dump('') - for f in e.frames: - self._dump('' - '' + '' + '' - % (f.location_context.caption, - f.location_context.decl, - ('(line %s)' % f.location_context.line) - if f.location_context.line is not None else '')) - for b in f.bindings: - self._dump('' - '' - '' - % (b.stmt_id, b.pretty, b.value)) + % (self._diff_plus_minus(is_added), + lc.caption, lc.decl, + ('(%s)' % self._make_sloc(lc.loc)) + if lc.loc is not None else '')) + + def dump_binding(f, b, is_added=None): + self._dump('' + '' + '%s' + '' + '' + % (self._diff_plus_minus(is_added), + b.stmt_id, + '' % ( + 'lavender' if self._dark_mode else 'darkgreen', + ('(%s)' % b.kind) if b.kind is not None else ' ' + ), + self._short_pretty(b.pretty), f.bindings[b])) + + frames_updated = e.diff_frames(prev_e) if prev_e is not None else None + if frames_updated: + for i in frames_updated: + f = e.frames[i] + prev_f = prev_e.frames[i] + dump_location_context(f.location_context) + bindings_removed, bindings_added = f.diff_bindings(prev_f) + for b in bindings_removed: + dump_binding(prev_f, b, False) + for b in bindings_added: + dump_binding(f, b, True) + else: + for f in e.frames: + dump_location_context(f.location_context) + for b in f.bindings: + dump_binding(f, b) self._dump('
%s%s ' + def dump_location_context(lc, is_added=None): + self._dump('
%s%s' + '%s ' '%s
S%s%s%s
%sS%s%s%s
' + '%s
') - def visit_store(self, s): + def visit_environment_in_state(self, selector, title, s, prev_s=None): + e = getattr(s, selector) + prev_e = getattr(prev_s, selector) if prev_s is not None else None + if e is None and prev_e is None: + return + + self._dump('
%s: ' % title) + if e is None: + self._dump(' Nothing!') + else: + if prev_e is not None: + if e.is_different(prev_e): + self._dump('') + self.visit_environment(e, prev_e) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_environment(e) + + self._dump('') + + def visit_store(self, s, prev_s=None): self._dump('') - for c in s.clusters: - for b in c.bindings: - self._dump('' - '' - '' - '' - % (c.base_region, b.offset, - '(Default)' if b.kind == 'Default' - else '', - b.value)) + def dump_binding(s, c, b, is_added=None): + self._dump('' + '' + '' + '' + '' + % (self._diff_plus_minus(is_added), + s.clusters[c].base_region, b.offset, + '(Default)' if b.kind == 'Default' + else '', + s.clusters[c].bindings[b])) + + if prev_s is not None: + clusters_removed, clusters_added, clusters_updated = \ + s.diff_clusters(prev_s) + for c in clusters_removed: + for b in prev_s.clusters[c].bindings: + dump_binding(prev_s, c, b, False) + for c in clusters_updated: + bindings_removed, bindings_added = \ + s.clusters[c].diff_bindings(prev_s.clusters[c]) + for b in bindings_removed: + dump_binding(prev_s, c, b, False) + for b in bindings_added: + dump_binding(s, c, b, True) + for c in clusters_added: + for b in s.clusters[c].bindings: + dump_binding(s, c, b, True) + else: + for c in s.clusters: + for b in s.clusters[c].bindings: + dump_binding(s, c, b) + + self._dump('
%s%s%s%s
%s%s%s%s%s
') + + def visit_store_in_state(self, s, prev_s=None): + st = s.store + prev_st = prev_s.store if prev_s is not None else None + if st is None and prev_st is None: + return + + self._dump('
Store: ') + if st is None: + self._dump(' Nothing!') + else: + if self._dark_mode: + self._dump(' (%s)' % st.ptr) + else: + self._dump(' (%s)' % st.ptr) + if prev_st is not None: + if s.store.is_different(prev_st): + self._dump('') + self.visit_store(st, prev_st) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_store(st) + self._dump('') + + def visit_generic_map(self, m, prev_m=None): + self._dump('') + + def dump_pair(m, k, is_added=None): + self._dump('' + '' + '' + % (self._diff_plus_minus(is_added), + k, m.generic_map[k])) + + if prev_m is not None: + removed, added = m.diff(prev_m) + for k in removed: + dump_pair(prev_m, k, False) + for k in added: + dump_pair(m, k, True) + else: + for k in m.generic_map: + dump_pair(m, k, None) self._dump('
%s%s%s
') - def visit_state(self, s): + def visit_generic_map_in_state(self, selector, title, s, prev_s=None): + m = getattr(s, selector) + prev_m = getattr(prev_s, selector) if prev_s is not None else None + if m is None and prev_m is None: + return + + self._dump('
') self._dump('' - 'Store: ') - if s.store is None: + '%s: ' % title) + if m is None: self._dump(' Nothing!') else: - self._dump('' - '') - self.visit_store(s.store) - - self._dump('
' - '' - 'Environment: ') - if s.environment is None: + if prev_m is not None: + if m.is_different(prev_m): + self._dump('') + self.visit_generic_map(m, prev_m) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_generic_map(m) + + self._dump('') + + def visit_checker_messages(self, m, prev_m=None): + self._dump('') + + def dump_line(l, is_added=None): + self._dump('' + '' + % (self._diff_plus_minus(is_added), l)) + + def dump_chk(chk, is_added=None): + dump_line('%s:' % chk, is_added) + + if prev_m is not None: + removed, added, updated = m.diff_messages(prev_m) + for chk in removed: + dump_chk(chk, False) + for l in prev_m.items[chk].lines: + dump_line(l, False) + for chk in updated: + dump_chk(chk) + for l in m.items[chk].diff_lines(prev_m.items[chk]): + dump_line(l[1:], l.startswith('+')) + for chk in added: + dump_chk(chk, True) + for l in m.items[chk].lines: + dump_line(l, True) + else: + for chk in m.items: + dump_chk(chk) + for l in m.items[chk].lines: + dump_line(l) + + self._dump('
%s%s
') + + def visit_checker_messages_in_state(self, s, prev_s=None): + m = s.checker_messages + prev_m = prev_s.checker_messages if prev_s is not None else None + if m is None and prev_m is None: + return + + self._dump('
') + self._dump('' + 'Checker State: ') + if m is None: self._dump(' Nothing!') else: - self._dump('' - '') - self.visit_environment(s.environment) + if prev_m is not None: + if m.is_different(prev_m): + self._dump('') + self.visit_checker_messages(m, prev_m) + else: + self._dump(' No changes!') + else: + self._dump('') + self.visit_checker_messages(m) self._dump('') + def visit_state(self, s, prev_s): + self.visit_store_in_state(s, prev_s) + self.visit_environment_in_state('environment', 'Expressions', + s, prev_s) + self.visit_generic_map_in_state('constraints', 'Ranges', + s, prev_s) + self.visit_generic_map_in_state('dynamic_types', 'Dynamic Types', + s, prev_s) + self.visit_environment_in_state('constructing_objects', + 'Objects Under Construction', + s, prev_s) + self.visit_checker_messages_in_state(s, prev_s) + def visit_node(self, node): - self._dump('%s [shape=record,label=<' + self._dump('%s [shape=record,' % (node.node_name())) + if self._dark_mode: + self._dump('color="white",fontcolor="gray80",') + self._dump('label=<
') - self._dump('' - % (node.node_id, node.ptr, node.state.state_id + self._dump('' + % ("gray20" if self._dark_mode else "gray70", + node.state.state_id if node.state is not None else 'Unspecified')) - self._dump('') - else: - self._dump('Program point:') + if not self._topo_mode: + self._dump('') + else: + self._dump('Program point:') self._dump('') - if node.state is not None: - self._dump('
') - self.visit_state(node.state) + if node.state is not None and not self._topo_mode: + prev_s = None + # Do diffs only when we have a unique predecessor. + # Don't do diffs on the leaf nodes because they're + # the important ones. + if self._do_diffs and len(node.predecessors) == 1 \ + and len(node.successors) > 0: + prev_s = self._graph.nodes[node.predecessors[0]].state + self.visit_state(node.state, prev_s) self._dump_raw('
Node %d (%s) - ' - 'State %s
State %s
') - if len(node.points) > 1: - self._dump('Program points:
') + if len(node.points) > 1: + self._dump('Program points:
' '') for p in node.points: self.visit_program_point(p) self._dump('
>];\n') def visit_edge(self, pred, succ): - self._dump_raw('%s -> %s;\n' % (pred.node_name(), succ.node_name())) + self._dump_raw('%s -> %s%s;\n' % ( + pred.node_name(), succ.node_name(), + ' [color="white"]' if self._dark_mode else '' + )) def visit_end_of_graph(self): self._dump_raw('}\n') + if not self._dump_dot_only: + import sys + import tempfile + + def write_temp_file(suffix, data): + fd, filename = tempfile.mkstemp(suffix=suffix) + print('Writing "%s"...' % filename) + with os.fdopen(fd, 'w') as fp: + fp.write(data) + print('Done! Please remember to remove the file.') + return filename + + try: + import graphviz + except ImportError: + # The fallback behavior if graphviz is not installed! + print('Python graphviz not found. Please invoke') + print(' $ pip install graphviz') + print('in order to enable automatic conversion to HTML.') + print() + print('You may also convert DOT to SVG manually via') + print(' $ dot -Tsvg input.dot -o output.svg') + print() + write_temp_file('.dot', self.output()) + return + + svg = graphviz.pipe('dot', 'svg', self.output()) + + filename = write_temp_file( + '.html', '%s' % ( + '#1a1a1a' if self._dark_mode else 'white', svg)) + if sys.platform == 'win32': + os.startfile(filename) + elif sys.platform == 'darwin': + os.system('open "%s"' % filename) + else: + os.system('xdg-open "%s"' % filename) + + +#===-----------------------------------------------------------------------===# +# Explorers know how to traverse the ExplodedGraph in a certain order. +# They would invoke a Visitor on every node or edge they encounter. +#===-----------------------------------------------------------------------===# + -# A class that encapsulates traversal of the ExplodedGraph. Different explorer -# kinds could potentially traverse specific sub-graphs. -class Explorer(object): +# BasicExplorer explores the whole graph in no particular order. +class BasicExplorer(object): def __init__(self): - super(Explorer, self).__init__() + super(BasicExplorer, self).__init__() def explore(self, graph, visitor): visitor.visit_begin_graph(graph) @@ -379,16 +907,125 @@ def explore(self, graph, visitor): visitor.visit_end_of_graph() +#===-----------------------------------------------------------------------===# +# Trimmers cut out parts of the ExplodedGraph so that to focus on other parts. +# Trimmers can be combined together by applying them sequentially. +#===-----------------------------------------------------------------------===# + + +# SinglePathTrimmer keeps only a single path - the leftmost path from the root. +# Useful when the trimmed graph is still too large. +class SinglePathTrimmer(object): + def __init__(self): + super(SinglePathTrimmer, self).__init__() + + def trim(self, graph): + visited_nodes = set() + node_id = graph.root_id + while True: + visited_nodes.add(node_id) + node = graph.nodes[node_id] + if len(node.successors) > 0: + succ_id = node.successors[0] + succ = graph.nodes[succ_id] + node.successors = [succ_id] + succ.predecessors = [node_id] + if succ_id in visited_nodes: + break + node_id = succ_id + else: + break + graph.nodes = {node_id: graph.nodes[node_id] + for node_id in visited_nodes} + + +# TargetedTrimmer keeps paths that lead to specific nodes and discards all +# other paths. Useful when you cannot use -trim-egraph (e.g. when debugging +# a crash). +class TargetedTrimmer(object): + def __init__(self, target_nodes): + super(TargetedTrimmer, self).__init__() + self._target_nodes = target_nodes + + @staticmethod + def parse_target_node(node, graph): + if node.startswith('0x'): + ret = 'Node' + node + assert ret in graph.nodes + return ret + else: + for other_id in graph.nodes: + other = graph.nodes[other_id] + if other.node_id == int(node): + return other_id + + @staticmethod + def parse_target_nodes(target_nodes, graph): + return [TargetedTrimmer.parse_target_node(node, graph) + for node in target_nodes.split(',')] + + def trim(self, graph): + queue = self._target_nodes + visited_nodes = set() + + while len(queue) > 0: + node_id = queue.pop() + visited_nodes.add(node_id) + node = graph.nodes[node_id] + for pred_id in node.predecessors: + if pred_id not in visited_nodes: + queue.append(pred_id) + graph.nodes = {node_id: graph.nodes[node_id] + for node_id in visited_nodes} + for node_id in graph.nodes: + node = graph.nodes[node_id] + node.successors = [succ_id for succ_id in node.successors + if succ_id in visited_nodes] + node.predecessors = [succ_id for succ_id in node.predecessors + if succ_id in visited_nodes] + + +#===-----------------------------------------------------------------------===# +# The entry point to the script. +#===-----------------------------------------------------------------------===# + + def main(): - parser = argparse.ArgumentParser() - parser.add_argument('filename', type=str) - parser.add_argument('-d', '--debug', action='store_const', dest='loglevel', - const=logging.DEBUG, default=logging.WARNING, - help='enable debug prints') + parser = argparse.ArgumentParser( + description='Display and manipulate Exploded Graph dumps.') + parser.add_argument('filename', type=str, + help='the .dot file produced by the Static Analyzer') parser.add_argument('-v', '--verbose', action='store_const', - dest='loglevel', const=logging.INFO, + dest='loglevel', const=logging.DEBUG, default=logging.WARNING, help='enable info prints') + parser.add_argument('-d', '--diff', action='store_const', dest='diff', + const=True, default=False, + help='display differences between states') + parser.add_argument('-t', '--topology', action='store_const', + dest='topology', const=True, default=False, + help='only display program points, omit states') + parser.add_argument('-s', '--single-path', action='store_const', + dest='single_path', const=True, default=False, + help='only display the leftmost path in the graph ' + '(useful for trimmed graphs that still ' + 'branch too much)') + parser.add_argument('--to', type=str, default=None, + help='only display execution paths from the root ' + 'to the given comma-separated list of nodes ' + 'identified by a pointer or a stable ID; ' + 'compatible with --single-path') + parser.add_argument('--dark', action='store_const', dest='dark', + const=True, default=False, + help='dark mode') + parser.add_argument('--gray', action='store_const', dest='gray', + const=True, default=False, + help='black-and-white mode') + parser.add_argument('--dump-dot-only', action='store_const', + dest='dump_dot_only', const=True, default=False, + help='instead of writing an HTML file and immediately ' + 'displaying it, dump the rewritten dot file ' + 'to stdout') args = parser.parse_args() logging.basicConfig(level=args.loglevel) @@ -398,8 +1035,21 @@ def main(): raw_line = raw_line.strip() graph.add_raw_line(raw_line) - explorer = Explorer() - visitor = DotDumpVisitor() + trimmers = [] + if args.to is not None: + trimmers.append(TargetedTrimmer( + TargetedTrimmer.parse_target_nodes(args.to, graph))) + if args.single_path: + trimmers.append(SinglePathTrimmer()) + + explorer = BasicExplorer() + + visitor = DotDumpVisitor(args.diff, args.dark, args.gray, args.topology, + args.dump_dot_only) + + for trimmer in trimmers: + trimmer.trim(graph) + explorer.explore(graph, visitor) diff --git a/compiler-rt/test/profile/instrprof-merge.c b/compiler-rt/test/profile/instrprof-merge.c index 8f8d7f458c966..8e8f06874b094 100644 --- a/compiler-rt/test/profile/instrprof-merge.c +++ b/compiler-rt/test/profile/instrprof-merge.c @@ -89,8 +89,8 @@ int main(int argc, const char *argv[]) { // Not profiled // CHECK-LABEL: bar: // CHECK: Counters: 1 -// CHECK-NEXT Function count: 0 -// CHECK-NEXT Block counts: [] +// CHECK-NEXT: Function count: 0 +// CHECK-NEXT: Block counts: [] // Not profiled // CHECK-LABEL: main: diff --git a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h index e7d19d1a815f7..7c826780c3185 100644 --- a/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h +++ b/llvm/include/llvm/Analysis/IteratedDominanceFrontier.h @@ -5,96 +5,85 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// -/// \file -/// Compute iterated dominance frontiers using a linear time algorithm. -/// -/// The algorithm used here is based on: -/// -/// Sreedhar and Gao. A linear time algorithm for placing phi-nodes. -/// In Proceedings of the 22nd ACM SIGPLAN-SIGACT Symposium on Principles of -/// Programming Languages -/// POPL '95. ACM, New York, NY, 62-73. -/// -/// It has been modified to not explicitly use the DJ graph data structure and -/// to directly compute pruned SSA using per-variable liveness information. -// -//===----------------------------------------------------------------------===// #ifndef LLVM_ANALYSIS_IDF_H #define LLVM_ANALYSIS_IDF_H -#include "llvm/ADT/DenseMap.h" -#include "llvm/ADT/SmallPtrSet.h" -#include "llvm/ADT/SmallVector.h" -#include "llvm/IR/BasicBlock.h" #include "llvm/IR/CFGDiff.h" -#include "llvm/IR/Dominators.h" +#include "llvm/Support/GenericIteratedDominanceFrontier.h" namespace llvm { -/// Determine the iterated dominance frontier, given a set of defining -/// blocks, and optionally, a set of live-in blocks. -/// -/// In turn, the results can be used to place phi nodes. -/// -/// This algorithm is a linear time computation of Iterated Dominance Frontiers, -/// pruned using the live-in set. -/// By default, liveness is not used to prune the IDF computation. -/// The template parameters should be either BasicBlock* or Inverse, depending on if you want the forward or reverse IDF. -template -class IDFCalculator { - public: - IDFCalculator(DominatorTreeBase &DT) - : DT(DT), GD(nullptr), useLiveIn(false) {} - - IDFCalculator(DominatorTreeBase &DT, - const GraphDiff *GD) - : DT(DT), GD(GD), useLiveIn(false) {} - - /// Give the IDF calculator the set of blocks in which the value is - /// defined. This is equivalent to the set of starting blocks it should be - /// calculating the IDF for (though later gets pruned based on liveness). - /// - /// Note: This set *must* live for the entire lifetime of the IDF calculator. - void setDefiningBlocks(const SmallPtrSetImpl &Blocks) { - DefBlocks = &Blocks; - } - - /// Give the IDF calculator the set of blocks in which the value is - /// live on entry to the block. This is used to prune the IDF calculation to - /// not include blocks where any phi insertion would be dead. - /// - /// Note: This set *must* live for the entire lifetime of the IDF calculator. - - void setLiveInBlocks(const SmallPtrSetImpl &Blocks) { - LiveInBlocks = &Blocks; - useLiveIn = true; - } +class BasicBlock; + +namespace IDFCalculatorDetail { - /// Reset the live-in block set to be empty, and tell the IDF - /// calculator to not use liveness anymore. - void resetLiveInBlocks() { - LiveInBlocks = nullptr; - useLiveIn = false; +/// Specialization for BasicBlock for the optional use of GraphDiff. +template struct ChildrenGetterTy { + using NodeRef = BasicBlock *; + using ChildrenTy = SmallVector; + + ChildrenGetterTy() = default; + ChildrenGetterTy(const GraphDiff *GD) : GD(GD) { + assert(GD); } - /// Calculate iterated dominance frontiers - /// - /// This uses the linear-time phi algorithm based on DJ-graphs mentioned in - /// the file-level comment. It performs DF->IDF pruning using the live-in - /// set, to avoid computing the IDF for blocks where an inserted PHI node - /// would be dead. - void calculate(SmallVectorImpl &IDFBlocks); - -private: - DominatorTreeBase &DT; - const GraphDiff *GD; - bool useLiveIn; - const SmallPtrSetImpl *LiveInBlocks; - const SmallPtrSetImpl *DefBlocks; + ChildrenTy get(const NodeRef &N); + + const GraphDiff *GD = nullptr; }; -typedef IDFCalculator ForwardIDFCalculator; -typedef IDFCalculator, true> ReverseIDFCalculator; + +} // end of namespace IDFCalculatorDetail + +template +class IDFCalculator final : public IDFCalculatorBase { +public: + using IDFCalculatorBase = + typename llvm::IDFCalculatorBase; + using ChildrenGetterTy = typename IDFCalculatorBase::ChildrenGetterTy; + + IDFCalculator(DominatorTreeBase &DT) + : IDFCalculatorBase(DT) {} + + IDFCalculator(DominatorTreeBase &DT, + const GraphDiff *GD) + : IDFCalculatorBase(DT, ChildrenGetterTy(GD)) { + assert(GD); + } +}; + +using ForwardIDFCalculator = IDFCalculator; +using ReverseIDFCalculator = IDFCalculator; + +//===----------------------------------------------------------------------===// +// Implementation. +//===----------------------------------------------------------------------===// + +namespace IDFCalculatorDetail { + +template +typename ChildrenGetterTy::ChildrenTy +ChildrenGetterTy::get(const NodeRef &N) { + + using OrderedNodeTy = + typename IDFCalculatorBase::OrderedNodeTy; + + if (!GD) { + auto Children = children(N); + return {Children.begin(), Children.end()}; + } + + using SnapShotBBPairTy = + std::pair *, OrderedNodeTy>; + + ChildrenTy Ret; + for (const auto &SnapShotBBPair : children({GD, N})) + Ret.emplace_back(SnapShotBBPair.second); + return Ret; } + +} // end of namespace IDFCalculatorDetail + +} // end of namespace llvm + #endif diff --git a/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h new file mode 100644 index 0000000000000..25eb7cd7b6d57 --- /dev/null +++ b/llvm/include/llvm/Support/GenericIteratedDominanceFrontier.h @@ -0,0 +1,209 @@ +//===- IteratedDominanceFrontier.h - Calculate IDF --------------*- 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 +// +//===----------------------------------------------------------------------===// +/// \file +/// Compute iterated dominance frontiers using a linear time algorithm. +/// +/// The algorithm used here is based on: +/// +/// Sreedhar and Gao. A linear time algorithm for placing phi-nodes. +/// In Proceedings of the 22nd ACM SIGPLAN-SIGACT Symposium on Principles of +/// Programming Languages +/// POPL '95. ACM, New York, NY, 62-73. +/// +/// It has been modified to not explicitly use the DJ graph data structure and +/// to directly compute pruned SSA using per-variable liveness information. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_SUPPORT_GENERIC_IDF_H +#define LLVM_SUPPORT_GENERIC_IDF_H + +#include "llvm/ADT/DenseMap.h" +#include "llvm/ADT/SmallPtrSet.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/GenericDomTree.h" +#include + +namespace llvm { + +namespace IDFCalculatorDetail { + +/// Generic utility class used for getting the children of a basic block. +/// May be specialized if, for example, one wouldn't like to return nullpointer +/// successors. +template struct ChildrenGetterTy { + using NodeRef = typename GraphTraits::NodeRef; + using ChildrenTy = SmallVector; + + ChildrenTy get(const NodeRef &N); +}; + +} // end of namespace IDFCalculatorDetail + +/// Determine the iterated dominance frontier, given a set of defining +/// blocks, and optionally, a set of live-in blocks. +/// +/// In turn, the results can be used to place phi nodes. +/// +/// This algorithm is a linear time computation of Iterated Dominance Frontiers, +/// pruned using the live-in set. +/// By default, liveness is not used to prune the IDF computation. +/// The template parameters should be of a CFG block type. +template class IDFCalculatorBase { +public: + using OrderedNodeTy = + typename std::conditional, NodeTy *>::type; + using ChildrenGetterTy = + IDFCalculatorDetail::ChildrenGetterTy; + + IDFCalculatorBase(DominatorTreeBase &DT) : DT(DT) {} + + IDFCalculatorBase(DominatorTreeBase &DT, + const ChildrenGetterTy &C) + : DT(DT), ChildrenGetter(C) {} + + /// Give the IDF calculator the set of blocks in which the value is + /// defined. This is equivalent to the set of starting blocks it should be + /// calculating the IDF for (though later gets pruned based on liveness). + /// + /// Note: This set *must* live for the entire lifetime of the IDF calculator. + void setDefiningBlocks(const SmallPtrSetImpl &Blocks) { + DefBlocks = &Blocks; + } + + /// Give the IDF calculator the set of blocks in which the value is + /// live on entry to the block. This is used to prune the IDF calculation to + /// not include blocks where any phi insertion would be dead. + /// + /// Note: This set *must* live for the entire lifetime of the IDF calculator. + void setLiveInBlocks(const SmallPtrSetImpl &Blocks) { + LiveInBlocks = &Blocks; + useLiveIn = true; + } + + /// Reset the live-in block set to be empty, and tell the IDF + /// calculator to not use liveness anymore. + void resetLiveInBlocks() { + LiveInBlocks = nullptr; + useLiveIn = false; + } + + /// Calculate iterated dominance frontiers + /// + /// This uses the linear-time phi algorithm based on DJ-graphs mentioned in + /// the file-level comment. It performs DF->IDF pruning using the live-in + /// set, to avoid computing the IDF for blocks where an inserted PHI node + /// would be dead. + void calculate(SmallVectorImpl &IDFBlocks); + +private: + DominatorTreeBase &DT; + ChildrenGetterTy ChildrenGetter; + bool useLiveIn = false; + const SmallPtrSetImpl *LiveInBlocks; + const SmallPtrSetImpl *DefBlocks; +}; + +//===----------------------------------------------------------------------===// +// Implementation. +//===----------------------------------------------------------------------===// + +namespace IDFCalculatorDetail { + +template +typename ChildrenGetterTy::ChildrenTy +ChildrenGetterTy::get(const NodeRef &N) { + using OrderedNodeTy = + typename IDFCalculatorBase::OrderedNodeTy; + + auto Children = children(N); + return {Children.begin(), Children.end()}; +} + +} // end of namespace IDFCalculatorDetail + +template +void IDFCalculatorBase::calculate( + SmallVectorImpl &PHIBlocks) { + // Use a priority queue keyed on dominator tree level so that inserted nodes + // are handled from the bottom of the dominator tree upwards. We also augment + // the level with a DFS number to ensure that the blocks are ordered in a + // deterministic way. + using DomTreeNodePair = + std::pair *, std::pair>; + using IDFPriorityQueue = + std::priority_queue, + less_second>; + + IDFPriorityQueue PQ; + + DT.updateDFSNumbers(); + + for (NodeTy *BB : *DefBlocks) { + if (DomTreeNodeBase *Node = DT.getNode(BB)) + PQ.push({Node, std::make_pair(Node->getLevel(), Node->getDFSNumIn())}); + } + + SmallVector *, 32> Worklist; + SmallPtrSet *, 32> VisitedPQ; + SmallPtrSet *, 32> VisitedWorklist; + + while (!PQ.empty()) { + DomTreeNodePair RootPair = PQ.top(); + PQ.pop(); + DomTreeNodeBase *Root = RootPair.first; + unsigned RootLevel = RootPair.second.first; + + // Walk all dominator tree children of Root, inspecting their CFG edges with + // targets elsewhere on the dominator tree. Only targets whose level is at + // most Root's level are added to the iterated dominance frontier of the + // definition set. + + Worklist.clear(); + Worklist.push_back(Root); + VisitedWorklist.insert(Root); + + while (!Worklist.empty()) { + DomTreeNodeBase *Node = Worklist.pop_back_val(); + NodeTy *BB = Node->getBlock(); + // Succ is the successor in the direction we are calculating IDF, so it is + // successor for IDF, and predecessor for Reverse IDF. + auto DoWork = [&](NodeTy *Succ) { + DomTreeNodeBase *SuccNode = DT.getNode(Succ); + + const unsigned SuccLevel = SuccNode->getLevel(); + if (SuccLevel > RootLevel) + return; + + if (!VisitedPQ.insert(SuccNode).second) + return; + + NodeTy *SuccBB = SuccNode->getBlock(); + if (useLiveIn && !LiveInBlocks->count(SuccBB)) + return; + + PHIBlocks.emplace_back(SuccBB); + if (!DefBlocks->count(SuccBB)) + PQ.push(std::make_pair( + SuccNode, std::make_pair(SuccLevel, SuccNode->getDFSNumIn()))); + }; + + for (auto Succ : ChildrenGetter.get(BB)) + DoWork(Succ); + + for (auto DomChild : *Node) { + if (VisitedWorklist.insert(DomChild).second) + Worklist.push_back(DomChild); + } + } + } +} + +} // end of namespace llvm + +#endif diff --git a/llvm/lib/Analysis/CMakeLists.txt b/llvm/lib/Analysis/CMakeLists.txt index 3cc9fe3c17155..dd5d6251414bf 100644 --- a/llvm/lib/Analysis/CMakeLists.txt +++ b/llvm/lib/Analysis/CMakeLists.txt @@ -41,7 +41,6 @@ add_llvm_library(LLVMAnalysis InstructionSimplify.cpp Interval.cpp IntervalPartition.cpp - IteratedDominanceFrontier.cpp LazyBranchProbabilityInfo.cpp LazyBlockFrequencyInfo.cpp LazyCallGraph.cpp diff --git a/llvm/lib/Analysis/IteratedDominanceFrontier.cpp b/llvm/lib/Analysis/IteratedDominanceFrontier.cpp deleted file mode 100644 index 89257121c6ad7..0000000000000 --- a/llvm/lib/Analysis/IteratedDominanceFrontier.cpp +++ /dev/null @@ -1,104 +0,0 @@ -//===- IteratedDominanceFrontier.cpp - Compute IDF ------------------------===// -// -// 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 -// -//===----------------------------------------------------------------------===// -// -// Compute iterated dominance frontiers using a linear time algorithm. -// -//===----------------------------------------------------------------------===// - -#include "llvm/Analysis/IteratedDominanceFrontier.h" -#include "llvm/IR/CFG.h" -#include "llvm/IR/Dominators.h" -#include - -namespace llvm { - -template -void IDFCalculator::calculate( - SmallVectorImpl &PHIBlocks) { - // Use a priority queue keyed on dominator tree level so that inserted nodes - // are handled from the bottom of the dominator tree upwards. We also augment - // the level with a DFS number to ensure that the blocks are ordered in a - // deterministic way. - typedef std::pair> - DomTreeNodePair; - typedef std::priority_queue, - less_second> IDFPriorityQueue; - IDFPriorityQueue PQ; - - DT.updateDFSNumbers(); - - for (BasicBlock *BB : *DefBlocks) { - if (DomTreeNode *Node = DT.getNode(BB)) - PQ.push({Node, std::make_pair(Node->getLevel(), Node->getDFSNumIn())}); - } - - SmallVector Worklist; - SmallPtrSet VisitedPQ; - SmallPtrSet VisitedWorklist; - - while (!PQ.empty()) { - DomTreeNodePair RootPair = PQ.top(); - PQ.pop(); - DomTreeNode *Root = RootPair.first; - unsigned RootLevel = RootPair.second.first; - - // Walk all dominator tree children of Root, inspecting their CFG edges with - // targets elsewhere on the dominator tree. Only targets whose level is at - // most Root's level are added to the iterated dominance frontier of the - // definition set. - - Worklist.clear(); - Worklist.push_back(Root); - VisitedWorklist.insert(Root); - - while (!Worklist.empty()) { - DomTreeNode *Node = Worklist.pop_back_val(); - BasicBlock *BB = Node->getBlock(); - // Succ is the successor in the direction we are calculating IDF, so it is - // successor for IDF, and predecessor for Reverse IDF. - auto DoWork = [&](BasicBlock *Succ) { - DomTreeNode *SuccNode = DT.getNode(Succ); - - const unsigned SuccLevel = SuccNode->getLevel(); - if (SuccLevel > RootLevel) - return; - - if (!VisitedPQ.insert(SuccNode).second) - return; - - BasicBlock *SuccBB = SuccNode->getBlock(); - if (useLiveIn && !LiveInBlocks->count(SuccBB)) - return; - - PHIBlocks.emplace_back(SuccBB); - if (!DefBlocks->count(SuccBB)) - PQ.push(std::make_pair( - SuccNode, std::make_pair(SuccLevel, SuccNode->getDFSNumIn()))); - }; - - if (GD) { - for (auto Pair : children< - std::pair *, NodeTy>>( - {GD, BB})) - DoWork(Pair.second); - } else { - for (auto *Succ : children(BB)) - DoWork(Succ); - } - - for (auto DomChild : *Node) { - if (VisitedWorklist.insert(DomChild).second) - Worklist.push_back(DomChild); - } - } - } -} - -template class IDFCalculator; -template class IDFCalculator, true>; -} diff --git a/llvm/test/tools/dsymutil/X86/modules.m b/llvm/test/tools/dsymutil/X86/modules.m index 1a5209e42bbd9..9e417861b9726 100644 --- a/llvm/test/tools/dsymutil/X86/modules.m +++ b/llvm/test/tools/dsymutil/X86/modules.m @@ -116,6 +116,7 @@ @interface Foo { // CHECK: DW_AT_type {{.*}}{0x{{0*}}[[PTR:.*]]} // // CHECK: 0x{{0*}}[[PTR]]: DW_TAG_pointer_type +// FIXME: The next line doesn't work. // CHECK-NEXT DW_AT_type [DW_FORM_ref_addr] {0x{{0*}}[[INTERFACE]] extern int odr_violation; diff --git a/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp b/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp index 8803b7c64b609..29b0ae2fb4e09 100644 --- a/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp +++ b/llvm/test/tools/dsymutil/X86/odr-fwd-declaration.cpp @@ -49,7 +49,7 @@ void foo() { // CHECK: AT_name{{.*}} "S" // CHECK-NOT: {{DW_TAG|NULL}} // CHECK: AT_declaration -// CHECK-NOT AT_byte_size +// CHECK-NOT: AT_byte_size #elif defined(FILE2) # 1 "Header.h" 1