diff options
author | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 13:58:24 +0300 |
---|---|---|
committer | thegeorg <thegeorg@yandex-team.com> | 2024-03-13 14:11:53 +0300 |
commit | 11a895b7e15d1c5a1f52706396b82e3f9db953cb (patch) | |
tree | fabc6d883b0f946151f61ae7865cee9f529a1fdd /contrib/libs/clang16/tools/extra/clang-tidy/misc/NoRecursionCheck.cpp | |
parent | 9685917341315774aad5733b1793b1e533a88bbb (diff) | |
download | ydb-11a895b7e15d1c5a1f52706396b82e3f9db953cb.tar.gz |
Export clang-format16 via ydblib project
6e6be3a95868fde888d801b7590af4044049563f
Diffstat (limited to 'contrib/libs/clang16/tools/extra/clang-tidy/misc/NoRecursionCheck.cpp')
-rw-r--r-- | contrib/libs/clang16/tools/extra/clang-tidy/misc/NoRecursionCheck.cpp | 271 |
1 files changed, 271 insertions, 0 deletions
diff --git a/contrib/libs/clang16/tools/extra/clang-tidy/misc/NoRecursionCheck.cpp b/contrib/libs/clang16/tools/extra/clang-tidy/misc/NoRecursionCheck.cpp new file mode 100644 index 0000000000..2250bba4db --- /dev/null +++ b/contrib/libs/clang16/tools/extra/clang-tidy/misc/NoRecursionCheck.cpp @@ -0,0 +1,271 @@ +//===--- NoRecursionCheck.cpp - clang-tidy --------------------------------===// +// +// 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 "NoRecursionCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Analysis/CallGraph.h" +#include "llvm/ADT/DenseMapInfo.h" +#include "llvm/ADT/SCCIterator.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::misc { + +namespace { + +/// Much like SmallSet, with two differences: +/// 1. It can *only* be constructed from an ArrayRef<>. If the element count +/// is small, there is no copy and said storage *must* outlive us. +/// 2. it is immutable, the way it was constructed it will stay. +template <typename T, unsigned SmallSize> class ImmutableSmallSet { + ArrayRef<T> Vector; + llvm::DenseSet<T> Set; + + static_assert(SmallSize <= 32, "N should be small"); + + bool isSmall() const { return Set.empty(); } + +public: + using size_type = size_t; + + ImmutableSmallSet() = delete; + ImmutableSmallSet(const ImmutableSmallSet &) = delete; + ImmutableSmallSet(ImmutableSmallSet &&) = delete; + T &operator=(const ImmutableSmallSet &) = delete; + T &operator=(ImmutableSmallSet &&) = delete; + + // WARNING: Storage *must* outlive us if we decide that the size is small. + ImmutableSmallSet(ArrayRef<T> Storage) { + // Is size small-enough to just keep using the existing storage? + if (Storage.size() <= SmallSize) { + Vector = Storage; + return; + } + + // We've decided that it isn't performant to keep using vector. + // Let's migrate the data into Set. + Set.reserve(Storage.size()); + Set.insert(Storage.begin(), Storage.end()); + } + + /// count - Return 1 if the element is in the set, 0 otherwise. + size_type count(const T &V) const { + if (isSmall()) { + // Since the collection is small, just do a linear search. + return llvm::is_contained(Vector, V) ? 1 : 0; + } + + return Set.count(V); + } +}; + +/// Much like SmallSetVector, but with one difference: +/// when the size is \p SmallSize or less, when checking whether an element is +/// already in the set or not, we perform linear search over the vector, +/// but if the size is larger than \p SmallSize, we look in set. +/// FIXME: upstream this into SetVector/SmallSetVector itself. +template <typename T, unsigned SmallSize> class SmartSmallSetVector { +public: + using size_type = size_t; + +private: + SmallVector<T, SmallSize> Vector; + llvm::DenseSet<T> Set; + + static_assert(SmallSize <= 32, "N should be small"); + + // Are we still using Vector for uniqness tracking? + bool isSmall() const { return Set.empty(); } + + // Will one more entry cause Vector to switch away from small-size storage? + bool entiretyOfVectorSmallSizeIsOccupied() const { + assert(isSmall() && Vector.size() <= SmallSize && + "Shouldn't ask if we have already [should have] migrated into Set."); + return Vector.size() == SmallSize; + } + + void populateSet() { + assert(Set.empty() && "Should not have already utilized the Set."); + // Magical growth factor prediction - to how many elements do we expect to + // sanely grow after switching away from small-size storage? + const size_t NewMaxElts = 4 * Vector.size(); + Vector.reserve(NewMaxElts); + Set.reserve(NewMaxElts); + Set.insert(Vector.begin(), Vector.end()); + } + + /// count - Return 1 if the element is in the set, 0 otherwise. + size_type count(const T &V) const { + if (isSmall()) { + // Since the collection is small, just do a linear search. + return llvm::is_contained(Vector, V) ? 1 : 0; + } + // Look-up in the Set. + return Set.count(V); + } + + bool setInsert(const T &V) { + if (count(V) != 0) + return false; // Already exists. + // Does not exist, Can/need to record it. + if (isSmall()) { // Are we still using Vector for uniqness tracking? + // Will one more entry fit within small-sized Vector? + if (!entiretyOfVectorSmallSizeIsOccupied()) + return true; // We'll insert into vector right afterwards anyway. + // Time to switch to Set. + populateSet(); + } + // Set time! + // Note that this must be after `populateSet()` might have been called. + bool SetInsertionSucceeded = Set.insert(V).second; + (void)SetInsertionSucceeded; + assert(SetInsertionSucceeded && "We did check that no such value existed"); + return true; + } + +public: + /// Insert a new element into the SmartSmallSetVector. + /// \returns true if the element was inserted into the SmartSmallSetVector. + bool insert(const T &X) { + bool Result = setInsert(X); + if (Result) + Vector.push_back(X); + return Result; + } + + /// Clear the SmartSmallSetVector and return the underlying vector. + decltype(Vector) takeVector() { + Set.clear(); + return std::move(Vector); + } +}; + +constexpr unsigned SmallCallStackSize = 16; +constexpr unsigned SmallSCCSize = 32; + +using CallStackTy = + llvm::SmallVector<CallGraphNode::CallRecord, SmallCallStackSize>; + +// In given SCC, find *some* call stack that will be cyclic. +// This will only find *one* such stack, it might not be the smallest one, +// and there may be other loops. +CallStackTy pathfindSomeCycle(ArrayRef<CallGraphNode *> SCC) { + // We'll need to be able to performantly look up whether some CallGraphNode + // is in SCC or not, so cache all the SCC elements in a set. + const ImmutableSmallSet<CallGraphNode *, SmallSCCSize> SCCElts(SCC); + + // Is node N part if the current SCC? + auto NodeIsPartOfSCC = [&SCCElts](CallGraphNode *N) { + return SCCElts.count(N) != 0; + }; + + // Track the call stack that will cause a cycle. + SmartSmallSetVector<CallGraphNode::CallRecord, SmallCallStackSize> + CallStackSet; + + // Arbitrarily take the first element of SCC as entry point. + CallGraphNode::CallRecord EntryNode(SCC.front(), /*CallExpr=*/nullptr); + // Continue recursing into subsequent callees that are part of this SCC, + // and are thus known to be part of the call graph loop, until loop forms. + CallGraphNode::CallRecord *Node = &EntryNode; + while (true) { + // Did we see this node before? + if (!CallStackSet.insert(*Node)) + break; // Cycle completed! Note that didn't insert the node into stack! + // Else, perform depth-first traversal: out of all callees, pick first one + // that is part of this SCC. This is not guaranteed to yield shortest cycle. + Node = llvm::find_if(Node->Callee->callees(), NodeIsPartOfSCC); + } + + // Note that we failed to insert the last node, that completes the cycle. + // But we really want to have it. So insert it manually into stack only. + CallStackTy CallStack = CallStackSet.takeVector(); + CallStack.emplace_back(*Node); + + return CallStack; +} + +} // namespace + +void NoRecursionCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher(translationUnitDecl().bind("TUDecl"), this); +} + +void NoRecursionCheck::handleSCC(ArrayRef<CallGraphNode *> SCC) { + assert(!SCC.empty() && "Empty SCC does not make sense."); + + // First of all, call out every strongly connected function. + for (CallGraphNode *N : SCC) { + FunctionDecl *D = N->getDefinition(); + diag(D->getLocation(), "function %0 is within a recursive call chain") << D; + } + + // Now, SCC only tells us about strongly connected function declarations in + // the call graph. It doesn't *really* tell us about the cycles they form. + // And there may be more than one cycle in SCC. + // So let's form a call stack that eventually exposes *some* cycle. + const CallStackTy EventuallyCyclicCallStack = pathfindSomeCycle(SCC); + assert(!EventuallyCyclicCallStack.empty() && "We should've found the cycle"); + + // While last node of the call stack does cause a loop, due to the way we + // pathfind the cycle, the loop does not necessarily begin at the first node + // of the call stack, so drop front nodes of the call stack until it does. + const auto CyclicCallStack = + ArrayRef<CallGraphNode::CallRecord>(EventuallyCyclicCallStack) + .drop_until([LastNode = EventuallyCyclicCallStack.back()]( + CallGraphNode::CallRecord FrontNode) { + return FrontNode == LastNode; + }); + assert(CyclicCallStack.size() >= 2 && "Cycle requires at least 2 frames"); + + // Which function we decided to be the entry point that lead to the recursion? + FunctionDecl *CycleEntryFn = CyclicCallStack.front().Callee->getDefinition(); + // And now, for ease of understanding, let's print the call sequence that + // forms the cycle in question. + diag(CycleEntryFn->getLocation(), + "example recursive call chain, starting from function %0", + DiagnosticIDs::Note) + << CycleEntryFn; + for (int CurFrame = 1, NumFrames = CyclicCallStack.size(); + CurFrame != NumFrames; ++CurFrame) { + CallGraphNode::CallRecord PrevNode = CyclicCallStack[CurFrame - 1]; + CallGraphNode::CallRecord CurrNode = CyclicCallStack[CurFrame]; + + Decl *PrevDecl = PrevNode.Callee->getDecl(); + Decl *CurrDecl = CurrNode.Callee->getDecl(); + + diag(CurrNode.CallExpr->getBeginLoc(), + "Frame #%0: function %1 calls function %2 here:", DiagnosticIDs::Note) + << CurFrame << cast<NamedDecl>(PrevDecl) << cast<NamedDecl>(CurrDecl); + } + + diag(CyclicCallStack.back().CallExpr->getBeginLoc(), + "... which was the starting point of the recursive call chain; there " + "may be other cycles", + DiagnosticIDs::Note); +} + +void NoRecursionCheck::check(const MatchFinder::MatchResult &Result) { + // Build call graph for the entire translation unit. + const auto *TU = Result.Nodes.getNodeAs<TranslationUnitDecl>("TUDecl"); + CallGraph CG; + CG.addToCallGraph(const_cast<TranslationUnitDecl *>(TU)); + + // Look for cycles in call graph, + // by looking for Strongly Connected Components (SCC's) + for (llvm::scc_iterator<CallGraph *> SCCI = llvm::scc_begin(&CG), + SCCE = llvm::scc_end(&CG); + SCCI != SCCE; ++SCCI) { + if (!SCCI.hasCycle()) // We only care about cycles, not standalone nodes. + continue; + handleSCC(*SCCI); + } +} + +} // namespace clang::tidy::misc |