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 @@ -6,6 +6,7 @@ add_clang_library(clangTidyLinuxKernelModule ExtraSemiCheck.cpp LinuxKernelTidyModule.cpp + LogFunctionsCheck.cpp MustCheckErrsCheck.cpp LINK_LIBS 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 @@ -10,6 +10,7 @@ #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" #include "ExtraSemiCheck.h" +#include "LogFunctionsCheck.h" #include "MustCheckErrsCheck.h" namespace clang { @@ -21,6 +22,8 @@ public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { CheckFactories.registerCheck("linuxkernel-extra-semi"); + CheckFactories.registerCheck( + "linuxkernel-log-functions"); CheckFactories.registerCheck( "linuxkernel-must-check-errs"); } diff --git a/clang-tools-extra/clang-tidy/linuxkernel/LogFunctionsCheck.h b/clang-tools-extra/clang-tidy/linuxkernel/LogFunctionsCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/linuxkernel/LogFunctionsCheck.h @@ -0,0 +1,39 @@ +//===--- LogFunctionsCheck.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_LOGFUNCTIONSCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LINUXKERNEL_LOGFUNCTIONSCHECK_H + +#include "../ClangTidyCheck.h" +#include "clang/Lex/MacroInfo.h" +#include + +namespace clang { +namespace tidy { +namespace linuxkernel { + +class LogFunctionsCheck : public ClangTidyCheck { + + enum LogFunctionsFixerKind { LFFK_None, LFFK_H }; + +public: + LogFunctionsCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + enum LogFunctionsFixerKind FixerKind; + const std::string LogFunctionsFixerKindName; +}; + +} // namespace linuxkernel +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LINUXKERNEL_LOGFUNCTIONSCHECK_H diff --git a/clang-tools-extra/clang-tidy/linuxkernel/LogFunctionsCheck.cpp b/clang-tools-extra/clang-tidy/linuxkernel/LogFunctionsCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/linuxkernel/LogFunctionsCheck.cpp @@ -0,0 +1,95 @@ +//===--- LogFunctionsCheck.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 "LogFunctionsCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace linuxkernel { + +LogFunctionsCheck::LogFunctionsCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), FixerKind(LFFK_H) {} + +void LogFunctionsCheck::registerMatchers(MatchFinder *Finder) { + if (FixerKind == LFFK_H) { + // From the reproducer + // extern int printk(const char *, ...) __attribute__((format(printf, 1, 2))); + // void f(unsigned short a) { printk("%hx\n", a); } + // + // The AST + // |-FunctionDecl printk 'int (const char *, ...)' extern + // | |-ParmVarDecl 'const char *' + // | `-FormatAttr printf 1 2 + // ... + // + // `-CompoundStmt + // `-CallExpr + // |-ImplicitCastExpr + // | `-DeclRefExpr Function 'printk' 'int (const char *, ...)' + // |-ImplicitCastExpr + // | `-ImplicitCastExpr + // | `-StringLiteral + Finder->addMatcher( + compoundStmt(has( + callExpr(has(ignoringParenImpCasts(stringLiteral().bind("lit"))), + callee(functionDecl(hasAttr(attr::Format)).bind("func"))) + .bind("call"))), + this); + } +} + +void LogFunctionsCheck::check(const MatchFinder::MatchResult &Result) { + if (FixerKind == LFFK_H) { + const auto *Call = Result.Nodes.getNodeAs("call"); + const auto *Function = Result.Nodes.getNodeAs("func"); + const auto *Literal = Result.Nodes.getNodeAs("lit"); + FormatAttr *Format = Function->getAttr(); + unsigned ArgNum = Format->getFormatIdx() - 1; + if (Format->getType()->getName() == "printf" && + ArgNum < Call->getNumArgs() && + Literal == Call->getArg(ArgNum)->IgnoreCasts() && Literal->isAscii() && + Literal->getCharByteWidth() == 1) { + StringRef LiteralString = Literal->getString(); + StringRef LookForH[] = {"%hi", "%hd", "%hu", "%hx", + "%hhi", "%hhd", "%hhu", "%hhx"}; + for (auto FoundH : LookForH) { + if (LiteralString.contains(FoundH)) { + size_t HBegin = LiteralString.find(FoundH) + 1; + size_t HEnd = HBegin + 1; + StringRef ErrorString = "h"; + if (FoundH.contains("hh")) { + HEnd++; + ErrorString = "hh"; + } + SourceLocation BeginLoc = Literal->getLocationOfByte( + HBegin, Result.Context->getSourceManager(), + Result.Context->getLangOpts(), Result.Context->getTargetInfo()); + SourceLocation EndLoc = Literal->getLocationOfByte( + HEnd, Result.Context->getSourceManager(), + Result.Context->getLangOpts(), Result.Context->getTargetInfo()); + diag(EndLoc, "Integer promotion: Using '%0' in '%1' is unnecessay") + << ErrorString << FoundH + << FixItHint::CreateRemoval( + CharSourceRange::getCharRange(BeginLoc, EndLoc)); + } + } + } + } +} + +void LogFunctionsCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "Fixer", ""); +} + +} // namespace linuxkernel +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/test/clang-tidy/checkers/linuxkernel-logfunctions-h.c b/clang-tools-extra/test/clang-tidy/checkers/linuxkernel-logfunctions-h.c new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/linuxkernel-logfunctions-h.c @@ -0,0 +1,20 @@ +// RUN: %check_clang_tidy %s linuxkernel-log-functions %t + +extern printk(const char *, ...) __attribute__((format(printf, 1, 2))); + +// scripts/checkpatch.pl produces +// WARNING: Integer promotion: Using 'h' in '%hx' is unnecessary +// ... linuxkernel-logfunctions-h.c:10: +// + printk("%hx\n", a); +// +// with --fix-inplace, the change is +// void f(unsigned char a) +// { +// - printk("%hx\n", a); +// + printk("%x\n", a); +// } +void f(unsigned char a) { + printk("%hx\n", a); + // CHECK-MESSAGES: :[[@LINE-1]]:13: warning: Integer promotion: Using 'h' in '%hx' is unnecessay + // CHECK-FIXES: printk("%x\n", a);{{$}} +}