CodeQL / Analyze (actions) (push) Canceled after 0s
CodeQL / Analyze (python) (push) Canceled after 0s
pre-commit / pre-commit (push) Canceled after 0s
Multi-Arch CI / setup (push) Canceled after 0s
Publish build_manylinux_x86_64 images / publish_build_manylinux_x86_64 (push) Canceled after 0s
Setup (Multi-Arch) / setup (push) Canceled after 0s
Multi-Arch CI / Linux::${{ fromJSON(needs.setup.outputs.linux_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubi10 image / publish_no_rocm_image_ubi10 (push) Canceled after 0s
Multi-Arch CI / Windows::${{ fromJSON(needs.setup.outputs.windows_build_config || '{}').build_variant_label || 'skip' }} (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Multi-Arch CI / Manifest Diff (push) Canceled after 0s
TheRock Manifest Diff Report / Generate Manifest Diff Report (push) Canceled after 0s
Multi-Arch CI / CI Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_ocl_rt images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04_rocgdb images / publish_no_rocm_image_ubuntu24_04_rocgdb (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_ocl_rt (push) Canceled after 0s
Publish TheRock Docker image / build-and-push-image (push) Canceled after 0s
Unit Tests / Unit Tests :: ubuntu-24.04 (push) Canceled after 0s
Unit Tests / Unit Tests :: windows-2022 (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_media (push) Canceled after 0s
Unit Tests / Unit Tests Summary (push) Canceled after 0s
Publish no_rocm_image_ubuntu24_04 images / publish_no_rocm_image_ubuntu24_04_openmpi (push) Canceled after 0s
141 lines
5.8 KiB
C++
141 lines
5.8 KiB
C++
//===----------------------------------------------------------------------===//
|
|
//
|
|
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
|
|
// See https://llvm.org/LICENSE.txt for license information.
|
|
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#include "NoRecursionCheck.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Analysis/CallGraph.h"
|
|
#include "llvm/ADT/SCCIterator.h"
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang::tidy::misc {
|
|
|
|
static constexpr unsigned SmallCallStackSize = 16;
|
|
static constexpr unsigned SmallSCCSize = 32;
|
|
|
|
using CallStackTy = 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.
|
|
static 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 llvm::SmallPtrSet<CallGraphNode *, SmallSCCSize> SCCElts(
|
|
llvm::from_range, SCC);
|
|
|
|
// Is node N part if the current SCC?
|
|
auto NodeIsPartOfSCC = [&SCCElts](CallGraphNode *N) {
|
|
return SCCElts.contains(N);
|
|
};
|
|
|
|
// Track the call stack that will cause a cycle.
|
|
llvm::SmallSetVector<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;
|
|
}
|
|
|
|
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 (const CallGraphNode *N : SCC) {
|
|
const 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?
|
|
const 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) {
|
|
const CallGraphNode::CallRecord PrevNode = CyclicCallStack[CurFrame - 1];
|
|
const 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
|