diff --git a/clang-tools-extra/clang-tidy/linuxkernel/CMakeLists.txt b/clang-tools-extra/clang-tidy/linuxkernel/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/linuxkernel/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/linuxkernel/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS support) add_clang_library(clangTidyLinuxKernelModule + IrqUnbalancedCheck.cpp LinuxKernelTidyModule.cpp MustCheckErrsCheck.cpp diff --git a/clang-tools-extra/clang-tidy/linuxkernel/IrqUnbalancedCheck.h b/clang-tools-extra/clang-tidy/linuxkernel/IrqUnbalancedCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/linuxkernel/IrqUnbalancedCheck.h @@ -0,0 +1,34 @@ +//===--- IrqUnbalancedCheck.h - clang-tidy ----------------------*- 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_TOOLS_EXTRA_CLANG_TIDY_LINUXKERNEL_IRQUNBALANCEDCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LINUXKERNEL_IRQUNBALANCEDCHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace linuxkernel { + +/// FIXME: Write a short description. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/linuxkernel-irq-unbalanced.html +class IrqUnbalancedCheck : public ClangTidyCheck { +public: + IrqUnbalancedCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace linuxkernel +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LINUXKERNEL_IRQUNBALANCEDCHECK_H diff --git a/clang-tools-extra/clang-tidy/linuxkernel/IrqUnbalancedCheck.cpp b/clang-tools-extra/clang-tidy/linuxkernel/IrqUnbalancedCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/linuxkernel/IrqUnbalancedCheck.cpp @@ -0,0 +1,70 @@ +//===--- IrqUnbalancedCheck.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 "IrqUnbalancedCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +std::string annotation = "ignore_irq_balancing"; + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace linuxkernel { + +void IrqUnbalancedCheck::registerMatchers(MatchFinder *Finder) { + // FIXME: Add matchers. + auto disable = forEachDescendant( + callExpr(hasDeclaration(namedDecl(hasName("arch_local_irq_disable")))) + .bind("disable")); + auto enable = forEachDescendant( + callExpr(hasDeclaration(namedDecl(hasName("arch_local_irq_enable")))) + .bind("enable")); + auto matcher = functionDecl( + anyOf(allOf(disable, unless(enable)), allOf(enable, unless(disable)))); + + Finder->addMatcher(matcher.bind("func"), this); +} + +void IrqUnbalancedCheck::check(const MatchFinder::MatchResult &Result) { + // FIXME: Add callback implementation. + const auto *MatchedDecl = Result.Nodes.getNodeAs("func"); + const auto *DisableCall = Result.Nodes.getNodeAs("disable"); + const auto *EnableCall = Result.Nodes.getNodeAs("enable"); + + for (const auto *Attr : MatchedDecl->attrs()) { + if (Attr->getKind() == clang::attr::Annotate) { + if (dyn_cast(Attr)->getAnnotation().str() == annotation) { + return; + } + } + } + + if (EnableCall) { + diag(MatchedDecl->getLocation(), + "call to 'enable_local_irq' without 'disable_local_irq' in %0 ") + << MatchedDecl; + diag(EnableCall->getBeginLoc(), "call to 'enable_local_irq'", + DiagnosticIDs::Note) + << MatchedDecl; + } + + if (DisableCall) { + diag(MatchedDecl->getLocation(), + "call to 'disable_local_irq' without 'enable_local_irq' in %0 ") + << MatchedDecl; + diag(DisableCall->getBeginLoc(), "call to 'disable_local_irq'", + DiagnosticIDs::Note) + << MatchedDecl; + } +} + +} // namespace linuxkernel +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp b/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp --- a/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/linuxkernel/LinuxKernelTidyModule.cpp @@ -9,6 +9,7 @@ #include "../ClangTidy.h" #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" +#include "IrqUnbalancedCheck.h" #include "MustCheckErrsCheck.h" namespace clang { @@ -19,6 +20,8 @@ class LinuxKernelModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "linuxkernel-irq-unbalanced"); CheckFactories.registerCheck( "linuxkernel-must-check-errs"); } diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -73,6 +73,11 @@ Checks Linux kernel code to see if it uses the results from the functions in ``linux/err.h``. +- New :doc:`linuxkernel-irq-unbalanced + ` check. + + Checks Linux kernel for dangerous uses of ``local_irq_disable`` and ``local_irq_enable`` + - New :doc:`google-upgrade-googletest-case ` check. @@ -80,6 +85,7 @@ replaces them with equivalent APIs with ``suite``. + Improvements to include-fixer ----------------------------- diff --git a/clang-tools-extra/docs/clang-tidy/checks/linuxkernel-irq-unbalanced.rst b/clang-tools-extra/docs/clang-tidy/checks/linuxkernel-irq-unbalanced.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/linuxkernel-irq-unbalanced.rst @@ -0,0 +1,18 @@ +.. title:: clang-tidy - linuxkernel-irq-unbalanced + +linuxkernel-irq-unbalanced +========================== + +Checks for calls to ``local_irq_disable`` without matching calls to ``local_irq_enable`` +and vice-versa. In most cases these functions must be called in pairs to avoid indefinite +hanging in the kernel. Functions marked with ``__attribute__((annotate("ignore_irq_balancing")))`` +are ignored for analysis. This is common when the machine is powering down. + +Example: + +.. code-block:: c + + void bar() { + local_irq_disable(); + // No call to local_irq_enable(); + } diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -269,6 +269,7 @@ hicpp-use-nullptr (redirects to modernize-use-nullptr) hicpp-use-override (redirects to modernize-use-override) hicpp-vararg (redirects to cppcoreguidelines-pro-type-vararg) + linuxkernel-irq-unbalanced linuxkernel-must-use-errs llvm-header-guard llvm-include-order diff --git a/clang-tools-extra/test/clang-tidy/linuxkernel-irq-unbalanced.cpp b/clang-tools-extra/test/clang-tidy/linuxkernel-irq-unbalanced.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/linuxkernel-irq-unbalanced.cpp @@ -0,0 +1,49 @@ +// RUN: %check_clang_tidy %s linuxkernel-irq-unbalanced %t + +extern void arch_local_irq_disable(); +extern void arch_local_irq_enable(); + +#define raw_local_irq_disable() arch_local_irq_disable() +#define raw_local_irq_enable() arch_local_irq_enable() + +#define local_irq_enable() \ + do { \ + raw_local_irq_enable(); \ + } while (0) +#define local_irq_disable() \ + do { \ + raw_local_irq_disable(); \ + } while (0) + +void foo() { + local_irq_disable(); + local_irq_enable(); +} + +// CHECK-MESSAGES: :[[@LINE+1]]:6: warning: call to 'enable_local_irq' without 'disable_local_irq' in 'bar' [linuxkernel-irq-unbalanced] +void bar() { + local_irq_enable(); +} + +// CHECK-MESSAGES: :[[@LINE+1]]:6: warning: call to 'disable_local_irq' without 'enable_local_irq' in 'baz' [linuxkernel-irq-unbalanced] +void baz() { + local_irq_disable(); +} + +void f() { + if (1) { + local_irq_disable(); + local_irq_enable(); + } +} + +// CHECK-MESSAGES: :[[@LINE+1]]:6: warning: call to 'disable_local_irq' without 'enable_local_irq' in 'f2' [linuxkernel-irq-unbalanced] +void f2() { + if (1) { + local_irq_disable(); + } +} + +__attribute__((annotate("ignore_irq_balancing"))) void g() { + local_irq_enable(); +}