[clang-tools-extra] Add llvm-mlir-use-after-erase check (PR #210727)
via cfe-commits
cfe-commits at lists.llvm.org
Fri Jul 24 05:35:38 PDT 2026
llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-tidy
Author: Harald-R
<details>
<summary>Changes</summary>
As proposed in [this thread](https://discourse.llvm.org/t/clang-tidy-checks-for-common-mlir-anti-patterns/91179), a clang-tidy check could be introduced which detects cases where an operation is erased, but it still has uses after. The check targets the following calls for erasing an operation:
- `mlir::Operation::erase`
- `mlir::Operation::destroy`
- `mlir::RewriterBase::eraseOp`
- `mlir::RewriterBase::replaceOp`
- through derived operations of `mlir::OpState` that are wrappers around `mlir::Operation` (e.g. `mlir::func::FuncOp` and all others that inherit from `mlir::Op`)
Cases where an operation's declaration variable is reinitialized are also taken into account. Additionally, cases where an operation is erased in a loop's iteration are covered as well.
The implementation of this check is based on the existing [bugprone-use-after-move](https://clang.llvm.org/extra/clang-tidy/checks/bugprone/use-after-move.html).
The findings and fixes for this check over the LLVM project can be found in https://github.com/llvm/llvm-project/pull/210733.
---
Patch is 37.13 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/210727.diff
8 Files Affected:
- (modified) clang-tools-extra/clang-tidy/llvm/CMakeLists.txt (+1)
- (modified) clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp (+3)
- (added) clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.cpp (+365)
- (added) clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.h (+37)
- (modified) clang-tools-extra/docs/ReleaseNotes.rst (+9)
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
- (added) clang-tools-extra/docs/clang-tidy/checks/llvm/mlir-use-after-erase.rst (+39)
- (added) clang-tools-extra/test/clang-tidy/checkers/llvm/mlir-use-after-erase.cpp (+476)
``````````diff
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index bec3ba50c81c5..847695c853cf5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -8,6 +8,7 @@ add_clang_library(clangTidyLLVMModule STATIC
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
+ MlirUseAfterEraseCheck.cpp
PreferIsaOrDynCastInConditionalsCheck.cpp
PreferRegisterOverUnsignedCheck.cpp
PreferStaticOverAnonymousNamespaceCheck.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
index 918af88c979e0..1f42cce26a0b8 100644
--- a/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
+++ b/clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp
@@ -14,6 +14,7 @@
#include "FormatvStringCheck.h"
#include "HeaderGuardCheck.h"
#include "IncludeOrderCheck.h"
+#include "MlirUseAfterEraseCheck.h"
#include "PreferIsaOrDynCastInConditionalsCheck.h"
#include "PreferRegisterOverUnsignedCheck.h"
#include "PreferStaticOverAnonymousNamespaceCheck.h"
@@ -36,6 +37,8 @@ class LLVMModule : public ClangTidyModule {
CheckFactories.registerCheck<FormatvStringCheck>("llvm-formatv-string");
CheckFactories.registerCheck<LLVMHeaderGuardCheck>("llvm-header-guard");
CheckFactories.registerCheck<IncludeOrderCheck>("llvm-include-order");
+ CheckFactories.registerCheck<MlirUseAfterEraseCheck>(
+ "llvm-mlir-use-after-erase");
CheckFactories.registerCheck<readability::NamespaceCommentCheck>(
"llvm-namespace-comment");
CheckFactories.registerCheck<PreferIsaOrDynCastInConditionalsCheck>(
diff --git a/clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.cpp b/clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.cpp
new file mode 100644
index 0000000000000..5b805cef34b40
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.cpp
@@ -0,0 +1,365 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 "MlirUseAfterEraseCheck.h"
+#include "../utils/ExprSequence.h"
+#include "clang/AST/Expr.h"
+#include "clang/AST/ExprCXX.h"
+#include "clang/ASTMatchers/ASTMatchFinder.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "clang/Analysis/Analyses/CFGReachabilityAnalysis.h"
+#include "clang/Analysis/CFG.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallPtrSet.h"
+#include <optional>
+
+using namespace clang::ast_matchers;
+using namespace clang::tidy::utils;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+/// Contains information about a use-after-erase.
+struct UseAfterErase {
+ // The DeclRefExpr that constituted the use of the operation.
+ const DeclRefExpr *DeclRef;
+
+ // Is the order in which the erase and the use are evaluated undefined?
+ bool EvaluationOrderUndefined = false;
+
+ // Does the use happen in a later loop iteration than the erase?
+ //
+ // We default to false and change it to true if required in find().
+ bool UseHappensInLaterLoopIteration = false;
+};
+
+/// Finds uses of an `mlir::Operation` variable that are reachable after the
+/// operation was erased (and maintains state required by the various internal
+/// helper functions).
+class UseAfterEraseFinder {
+public:
+ UseAfterEraseFinder(ASTContext *TheContext);
+
+ // Within the given code block, finds the first use of 'ErasedVariable' that
+ // occurs after 'EraseCall' (the expression that erases the operation). If a
+ // use-after-erase is found, returns information about it.
+ std::optional<UseAfterErase> find(Stmt *CodeBlock, const Expr *EraseCall,
+ const DeclRefExpr *ErasedVariable);
+
+private:
+ std::optional<UseAfterErase> findInternal(const CFGBlock *Block,
+ const Expr *EraseCall,
+ const ValueDecl *ErasedVariable);
+ void getUsesAndReinits(const CFGBlock *Block, const ValueDecl *ErasedVariable,
+ SmallVectorImpl<const DeclRefExpr *> *Uses,
+ llvm::SmallPtrSetImpl<const Stmt *> *Reinits);
+ void getDeclRefs(const CFGBlock *Block, const Decl *ErasedVariable,
+ llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
+ void getReinits(const CFGBlock *Block, const Decl *ErasedVariable,
+ llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
+ llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs);
+
+ ASTContext *Context;
+ std::unique_ptr<ExprSequence> Sequence;
+ std::unique_ptr<StmtToBlockMap> BlockMap;
+ llvm::SmallPtrSet<const CFGBlock *, 8> Visited;
+};
+
+} // namespace
+
+UseAfterEraseFinder::UseAfterEraseFinder(ASTContext *TheContext)
+ : Context(TheContext) {}
+
+std::optional<UseAfterErase>
+UseAfterEraseFinder::find(Stmt *CodeBlock, const Expr *EraseCall,
+ const DeclRefExpr *ErasedVariable) {
+ // We include implicit and temporary destructors in the CFG so that
+ // destructors marked [[noreturn]] are handled correctly in the control flow
+ // analysis. (These are used in some styles of assertion macros.)
+ CFG::BuildOptions Options;
+ Options.AddImplicitDtors = true;
+ Options.AddTemporaryDtors = true;
+ const auto TheCFG = CFG::buildCFG(nullptr, CodeBlock, Context, Options);
+ if (!TheCFG)
+ return std::nullopt;
+
+ Sequence = std::make_unique<ExprSequence>(TheCFG.get(), CodeBlock, Context);
+ BlockMap = std::make_unique<StmtToBlockMap>(TheCFG.get(), Context);
+ Visited.clear();
+
+ const CFGBlock *EraseBlock = BlockMap->blockContainingStmt(EraseCall);
+ if (!EraseBlock)
+ EraseBlock = &TheCFG->getEntry();
+
+ auto TheUseAfterErase =
+ findInternal(EraseBlock, EraseCall, ErasedVariable->getDecl());
+
+ if (TheUseAfterErase) {
+ if (const CFGBlock *UseBlock =
+ BlockMap->blockContainingStmt(TheUseAfterErase->DeclRef)) {
+ // Does the use happen in a later loop iteration than the erase?
+ // - If they are in the same CFG block, we know the use happened in a
+ // later iteration if we visited that block a second time.
+ // - Otherwise, we know the use happened in a later iteration if the
+ // erase is reachable from the use.
+ CFGReverseBlockReachabilityAnalysis CFA(*TheCFG);
+ TheUseAfterErase->UseHappensInLaterLoopIteration =
+ UseBlock == EraseBlock ? Visited.contains(UseBlock)
+ : CFA.isReachable(UseBlock, EraseBlock);
+ }
+ }
+ return TheUseAfterErase;
+}
+
+std::optional<UseAfterErase>
+UseAfterEraseFinder::findInternal(const CFGBlock *Block, const Expr *EraseCall,
+ const ValueDecl *ErasedVariable) {
+ if (Visited.contains(Block))
+ return std::nullopt;
+
+ // Mark the block as visited (except if this is the block containing the erase
+ // call and it's being visited the first time).
+ if (!EraseCall)
+ Visited.insert(Block);
+
+ // Get all uses and reinits in the block.
+ SmallVector<const DeclRefExpr *, 1> Uses;
+ llvm::SmallPtrSet<const Stmt *, 1> Reinits;
+ getUsesAndReinits(Block, ErasedVariable, &Uses, &Reinits);
+
+ // Ignore all reinitializations where the erase potentially comes after the
+ // reinit.
+ SmallVector<const Stmt *, 1> ReinitsToDelete;
+ for (const auto *Reinit : Reinits)
+ if (EraseCall && Sequence->potentiallyAfter(EraseCall, Reinit))
+ ReinitsToDelete.push_back(Reinit);
+ for (const auto *Reinit : ReinitsToDelete)
+ Reinits.erase(Reinit);
+
+ // Find all uses that potentially come after the erase.
+ for (const auto *Use : Uses) {
+ if (!EraseCall || Sequence->potentiallyAfter(Use, EraseCall)) {
+ // Does the use have a saving reinit? A reinit is saving if it definitely
+ // comes before the use, i.e. if there's no potential that the reinit is
+ // after the use.
+ bool HaveSavingReinit = false;
+ for (const auto *Reinit : Reinits)
+ if (!Sequence->potentiallyAfter(Reinit, Use))
+ HaveSavingReinit = true;
+
+ if (!HaveSavingReinit) {
+ UseAfterErase TheUseAfterErase;
+ TheUseAfterErase.DeclRef = Use;
+
+ // Is this a use-after-erase that depends on order of evaluation?
+ // This is the case if the erase potentially comes after the use (and we
+ // already know that the use potentially comes after the erase, which
+ // taken together tells us that the ordering is unclear).
+ TheUseAfterErase.EvaluationOrderUndefined =
+ EraseCall != nullptr && Sequence->potentiallyAfter(EraseCall, Use);
+
+ return TheUseAfterErase;
+ }
+ }
+ }
+
+ // If the operation wasn't reinitialized, call ourselves recursively on all
+ // successors.
+ if (Reinits.empty()) {
+ for (const auto &Succ : Block->succs())
+ if (Succ)
+ if (auto Found = findInternal(Succ, nullptr, ErasedVariable))
+ return Found;
+ }
+
+ return std::nullopt;
+}
+
+void UseAfterEraseFinder::getUsesAndReinits(
+ const CFGBlock *Block, const ValueDecl *ErasedVariable,
+ SmallVectorImpl<const DeclRefExpr *> *Uses,
+ llvm::SmallPtrSetImpl<const Stmt *> *Reinits) {
+ llvm::SmallPtrSet<const DeclRefExpr *, 1> DeclRefs;
+ llvm::SmallPtrSet<const DeclRefExpr *, 1> ReinitDeclRefs;
+
+ getDeclRefs(Block, ErasedVariable, &DeclRefs);
+ getReinits(Block, ErasedVariable, Reinits, &ReinitDeclRefs);
+
+ // All references to the variable that aren't reinitializations are uses.
+ Uses->clear();
+ for (const DeclRefExpr *DeclRef : DeclRefs)
+ if (!ReinitDeclRefs.contains(DeclRef))
+ Uses->push_back(DeclRef);
+
+ // Sort the uses by their occurrence in the source code.
+ llvm::sort(*Uses, [](const DeclRefExpr *D1, const DeclRefExpr *D2) {
+ return D1->getExprLoc() < D2->getExprLoc();
+ });
+}
+
+void UseAfterEraseFinder::getDeclRefs(
+ const CFGBlock *Block, const Decl *ErasedVariable,
+ llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
+ DeclRefs->clear();
+ const auto DeclRefMatcher =
+ declRefExpr(to(equalsNode(ErasedVariable))).bind("declref");
+
+ for (const auto &Elem : *Block) {
+ const auto S = Elem.getAs<CFGStmt>();
+ if (!S)
+ continue;
+
+ for (const auto &Match :
+ match(findAll(DeclRefMatcher), *S->getStmt(), *Context)) {
+ const auto *DeclRef = Match.getNodeAs<DeclRefExpr>("declref");
+ if (DeclRef && BlockMap->blockContainingStmt(DeclRef) == Block)
+ DeclRefs->insert(DeclRef);
+ }
+ }
+}
+
+void UseAfterEraseFinder::getReinits(
+ const CFGBlock *Block, const Decl *ErasedVariable,
+ llvm::SmallPtrSetImpl<const Stmt *> *Stmts,
+ llvm::SmallPtrSetImpl<const DeclRefExpr *> *DeclRefs) {
+ const auto DeclRefMatcher =
+ declRefExpr(to(equalsNode(ErasedVariable))).bind("declref");
+
+ // A reinitialization gives the variable a new, valid operation, which makes
+ // subsequent uses safe again. We treat plain assignments and
+ // (re-)declarations as reinitializations. For "derived" ops (e.g.
+ // `mlir::func::FuncOp`), the assignment is an overloaded `operator=`, which
+ // is represented as a `CXXOperatorCallExpr` rather than a `BinaryOperator`.
+ const auto ReinitMatcher =
+ stmt(anyOf(binaryOperator(hasOperatorName("="),
+ hasLHS(ignoringParenImpCasts(DeclRefMatcher))),
+ cxxOperatorCallExpr(
+ hasOverloadedOperatorName("="),
+ hasArgument(0, ignoringParenImpCasts(DeclRefMatcher))),
+ declStmt(hasDescendant(equalsNode(ErasedVariable)))))
+ .bind("reinit");
+
+ Stmts->clear();
+ DeclRefs->clear();
+ for (const CFGElement &Elem : *Block) {
+ const auto S = Elem.getAs<CFGStmt>();
+ if (!S)
+ continue;
+
+ for (const auto &Match :
+ match(findAll(ReinitMatcher), *S->getStmt(), *Context)) {
+ const auto *TheStmt = Match.getNodeAs<Stmt>("reinit");
+ const auto *DeclRef = Match.getNodeAs<DeclRefExpr>("declref");
+ if (TheStmt && BlockMap->blockContainingStmt(TheStmt) == Block) {
+ Stmts->insert(TheStmt);
+
+ // We count DeclStmts as reinitializations, but they don't have a
+ // DeclRefExpr associated with them -- so we need to check 'DeclRef'
+ // before adding it to the set.
+ if (DeclRef)
+ DeclRefs->insert(DeclRef);
+ }
+ }
+ }
+}
+
+static void emitDiagnostic(const Expr *EraseCall, const DeclRefExpr *EraseArg,
+ const UseAfterErase &Use, ClangTidyCheck *Check) {
+ const auto UseLoc = Use.DeclRef->getExprLoc();
+ const auto EraseLoc = EraseCall->getExprLoc();
+
+ Check->diag(UseLoc, "operation %0 is used after it was erased")
+ << EraseArg->getDecl();
+ Check->diag(EraseLoc, "operation erased here", DiagnosticIDs::Note);
+ if (Use.EvaluationOrderUndefined) {
+ Check->diag(UseLoc,
+ "the use and erase are unsequenced, i.e. there is no guarantee "
+ "about the order in which they are evaluated",
+ DiagnosticIDs::Note);
+ } else if (Use.UseHappensInLaterLoopIteration) {
+ Check->diag(UseLoc,
+ "the use happens in a later loop iteration than the erase",
+ DiagnosticIDs::Note);
+ }
+}
+
+void MlirUseAfterEraseCheck::registerMatchers(MatchFinder *Finder) {
+ // The reference to the operation that is being erased.
+ const auto Arg = declRefExpr(to(varDecl())).bind("arg");
+
+ // A "derived" op is a value of a subclass of `mlir::OpState` (e.g.
+ // `mlir::func::FuncOp`) that wraps an `mlir::Operation *`. The underlying
+ // operation can be obtained via the overloaded `operator->` or via
+ // `getOperation()`, both of which are declared in `mlir::OpState`.
+ const auto OfOpState = ofClass(isSameOrDerivedFrom("::mlir::OpState"));
+ const auto UnwrapDerivedOp =
+ expr(anyOf(cxxMemberCallExpr(
+ on(ignoringParenImpCasts(Arg)),
+ callee(cxxMethodDecl(hasName("getOperation"), OfOpState))),
+ cxxOperatorCallExpr(hasOverloadedOperatorName("->"),
+ hasArgument(0, ignoringParenImpCasts(Arg)),
+ callee(cxxMethodDecl(OfOpState)))));
+
+ // A reference to the operation, either directly (a variable of type
+ // `mlir::Operation *`) or through a derived op that is unwrapped to the
+ // underlying `mlir::Operation *`.
+ const auto OpRef = expr(ignoringParenImpCasts(anyOf(Arg, UnwrapDerivedOp)));
+
+ // Erasure through `mlir::Operation::erase` and `mlir::Operation::destroy`
+ // member functions.
+ const auto MemberErase = cxxMemberCallExpr(
+ on(OpRef), callee(cxxMethodDecl(hasAnyName("erase", "destroy"),
+ ofClass(hasName("::mlir::Operation")))));
+
+ // Erasure through `mlir::RewriterBase`. The operation that gets invalidated
+ // is always the first argument of these methods.
+ const auto RewriterErase = cxxMemberCallExpr(
+ callee(
+ cxxMethodDecl(hasAnyName("eraseOp", "eraseOpResults", "replaceOp",
+ "replaceOpWithNewOp"),
+ ofClass(isSameOrDerivedFrom("::mlir::RewriterBase")))),
+ hasArgument(0, OpRef));
+
+ Finder->addMatcher(
+ traverse(TK_AsIs,
+ expr(anyOf(MemberErase, RewriterErase),
+ anyOf(hasAncestor(compoundStmt(hasParent(
+ lambdaExpr().bind("containing-lambda")))),
+ hasAncestor(functionDecl().bind("containing-func"))))
+ .bind("erase-call")),
+ this);
+}
+
+void MlirUseAfterEraseCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *EraseCall = Result.Nodes.getNodeAs<Expr>("erase-call");
+ const auto *Arg = Result.Nodes.getNodeAs<DeclRefExpr>("arg");
+ const auto *ContainingLambda =
+ Result.Nodes.getNodeAs<LambdaExpr>("containing-lambda");
+ const auto *ContainingFunc =
+ Result.Nodes.getNodeAs<FunctionDecl>("containing-func");
+
+ // Only track operations that are stored in a local variable.
+ if (!Arg->getDecl()->getDeclContext()->isFunctionOrMethod())
+ return;
+
+ // Collect the code block that could use the operation after it was erased.
+ SmallVector<Stmt *> CodeBlocks;
+ if (ContainingLambda)
+ CodeBlocks.push_back(ContainingLambda->getBody());
+ else if (ContainingFunc)
+ CodeBlocks.push_back(ContainingFunc->getBody());
+
+ for (Stmt *CodeBlock : CodeBlocks) {
+ UseAfterEraseFinder Finder(Result.Context);
+ if (auto Use = Finder.find(CodeBlock, EraseCall, Arg))
+ emitDiagnostic(EraseCall, Arg, *Use, this);
+ }
+}
+
+} // namespace clang::tidy::llvm_check
diff --git a/clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.h b/clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.h
new file mode 100644
index 0000000000000..52574ac76fdf9
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/MlirUseAfterEraseCheck.h
@@ -0,0 +1,37 @@
+//===----------------------------------------------------------------------===//
+//
+// 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_TOOLS_EXTRA_CLANG_TIDY_LLVM_MLIRUSEAFTERERASECHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_MLIRUSEAFTERERASECHECK_H
+
+#include "../ClangTidyCheck.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Detects uses of an ``mlir::Operation`` after it has been erased, either
+/// directly through ``Operation::erase``/``Operation::destroy``, through
+/// ``mlir::RewriterBase`` helpers such as ``eraseOp`` and ``replaceOp``,
+/// or through derived ops of ``mlir::OpState`` that provide are wrappers
+/// around an ``mlir::Operation``.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/mlir-use-after-erase.html
+class MlirUseAfterEraseCheck : public ClangTidyCheck {
+public:
+ MlirUseAfterEraseCheck(StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context) {}
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+ return LangOpts.CPlusPlus;
+ }
+};
+
+} // namespace clang::tidy::llvm_check
+
+#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_MLIRUSEAFTERERASECHECK_H
diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst
index dd397b574b6f4..f7bd617eb1919 100644
--- a/clang-tools-extra/docs/ReleaseNotes.rst
+++ b/clang-tools-extra/docs/ReleaseNotes.rst
@@ -97,6 +97,15 @@ Improvements to clang-tidy
New checks
^^^^^^^^^^
+- New :doc:`llvm-mlir-use-after-erase
+ <clang-tidy/checks/llvm/mlir-use-after-erase>` check.
+
+ Detects uses of an ``mlir::Operation`` after it has been erased, either
+ directly through ``Operation::erase``/``Operation::destroy``, through
+ ``mlir::RewriterBase`` helpers such as ``eraseOp`` and ``replaceOp``,
+ or through derived ops of ``mlir::OpState`` that are wrappers around
+ an ``mlir::Operation``.
+
- New :doc:`performance-expensive-value-or
<clang-tidy/checks/performance/expensive-value-or>` check.
diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst
index 8758efe14080c..d20cf004afcda 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/list.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst
@@ -246,6 +246,7 @@ Clang-Tidy Checks
:doc:`llvm-formatv-string <llvm/formatv-string>`,
:doc:`llvm-header-guard <llvm/header-guard>`,
:doc:`llvm-include-order <llvm/include-order>`, "Yes"
+ :doc:`llvm-mlir-use-after-erase <llvm/mlir-use-after-erase>`,
:doc:`llvm-namesp...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/210727
More information about the cfe-commits
mailing list