Skip to content

Commit 1c976c6

Browse files
fanquaketheuni
andcommitted
tidy: Integrate bicoin-tidy clang-tidy plugin
Enable `bitcoin-unterminated-logprintf`. Co-authored-by: Cory Fields <cory-nospam-@coryfields.com>
1 parent 7de23cc commit 1c976c6

File tree

12 files changed

+268
-5
lines changed

12 files changed

+268
-5
lines changed

ci/test/06_script_b.sh

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,9 +148,13 @@ if [ "$RUN_FUNCTIONAL_TESTS" = "true" ]; then
148148
fi
149149

150150
if [ "${RUN_TIDY}" = "true" ]; then
151+
cmake -B /tidy-build -DLLVM_DIR=/usr/lib/llvm-16/cmake -DCMAKE_BUILD_TYPE=Release -S "${BASE_ROOT_DIR}"/contrib/devtools/bitcoin-tidy
152+
cmake --build /tidy-build "$MAKEJOBS"
153+
cmake --build /tidy-build --target bitcoin-tidy-tests "$MAKEJOBS"
154+
151155
set -eo pipefail
152156
cd "${BASE_BUILD_DIR}/bitcoin-$HOST/src/"
153-
( run-clang-tidy-16 -quiet "${MAKEJOBS}" ) | grep -C5 "error"
157+
( run-clang-tidy-16 -quiet -load="/tidy-build/libbitcoin-tidy.so" "${MAKEJOBS}" ) | grep -C5 "error"
154158
# Filter out files by regex here, because regex may not be
155159
# accepted in src/.bear-tidy-config
156160
# Filter out:
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
cmake_minimum_required(VERSION 3.9)
2+
3+
project(bitcoin-tidy VERSION 1.0.0 DESCRIPTION "clang-tidy checks for Bitcoin Core")
4+
5+
include(GNUInstallDirs)
6+
7+
set(CMAKE_CXX_STANDARD 17)
8+
set(CMAKE_CXX_STANDARD_REQUIRED True)
9+
set(CMAKE_CXX_EXTENSIONS False)
10+
11+
# TODO: Figure out how to avoid the terminfo check
12+
find_package(LLVM REQUIRED CONFIG)
13+
find_program(CLANG_TIDY_EXE NAMES "clang-tidy-${LLVM_VERSION_MAJOR}" "clang-tidy" HINTS ${LLVM_TOOLS_BINARY_DIR})
14+
message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
15+
message(STATUS "Found clang-tidy: ${CLANG_TIDY_EXE}")
16+
17+
add_library(bitcoin-tidy MODULE bitcoin-tidy.cpp logprintf.cpp)
18+
target_include_directories(bitcoin-tidy SYSTEM PRIVATE ${LLVM_INCLUDE_DIRS})
19+
20+
# Disable RTTI and exceptions as necessary
21+
if (MSVC)
22+
target_compile_options(bitcoin-tidy PRIVATE /GR-)
23+
else()
24+
target_compile_options(bitcoin-tidy PRIVATE -fno-rtti)
25+
target_compile_options(bitcoin-tidy PRIVATE -fno-exceptions)
26+
endif()
27+
28+
# Add warnings
29+
if (MSVC)
30+
target_compile_options(bitcoin-tidy PRIVATE /W4)
31+
else()
32+
target_compile_options(bitcoin-tidy PRIVATE -Wall)
33+
target_compile_options(bitcoin-tidy PRIVATE -Wextra)
34+
endif()
35+
36+
set(CLANG_TIDY_COMMAND "${CLANG_TIDY_EXE}" "--load=${CMAKE_BINARY_DIR}/${CMAKE_SHARED_LIBRARY_PREFIX}bitcoin-tidy${CMAKE_SHARED_LIBRARY_SUFFIX}" "-checks=-*,bitcoin-*")
37+
38+
# Create a dummy library that runs clang-tidy tests as a side-effect of building
39+
add_library(bitcoin-tidy-tests OBJECT EXCLUDE_FROM_ALL example_logprintf.cpp)
40+
add_dependencies(bitcoin-tidy-tests bitcoin-tidy)
41+
42+
set_target_properties(bitcoin-tidy-tests PROPERTIES CXX_CLANG_TIDY "${CLANG_TIDY_COMMAND}")
43+
44+
45+
install(TARGETS bitcoin-tidy LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})

contrib/devtools/bitcoin-tidy/README

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Bitcoin Tidy
2+
3+
Example Usage:
4+
5+
```bash
6+
cmake -S . -B build -DLLVM_DIR=/path/to/lib/cmake/llvm -DCMAKE_BUILD_TYPE=Release
7+
make -C build -j$(nproc)
8+
```
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
// Copyright (c) 2023 Bitcoin Developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "logprintf.h"
6+
7+
#include <clang-tidy/ClangTidyModule.h>
8+
#include <clang-tidy/ClangTidyModuleRegistry.h>
9+
10+
class BitcoinModule final : public clang::tidy::ClangTidyModule
11+
{
12+
public:
13+
void addCheckFactories(clang::tidy::ClangTidyCheckFactories& CheckFactories) override
14+
{
15+
CheckFactories.registerCheck<bitcoin::LogPrintfCheck>("bitcoin-unterminated-logprintf");
16+
}
17+
};
18+
19+
static clang::tidy::ClangTidyModuleRegistry::Add<BitcoinModule>
20+
X("bitcoin-module", "Adds bitcoin checks.");
21+
22+
volatile int BitcoinModuleAnchorSource = 0;
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
// Copyright (c) 2023 Bitcoin Developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
// Warn about any use of LogPrintf that does not end with a newline.
6+
#include <string>
7+
8+
enum LogFlags {
9+
NONE
10+
};
11+
12+
enum Level {
13+
None
14+
};
15+
16+
template <typename... Args>
17+
static inline void LogPrintf_(const std::string& logging_function, const std::string& source_file, const int source_line, const LogFlags flag, const Level level, const char* fmt, const Args&... args)
18+
{
19+
}
20+
21+
#define LogPrintLevel_(category, level, ...) LogPrintf_(__func__, __FILE__, __LINE__, category, level, __VA_ARGS__)
22+
#define LogPrintf(...) LogPrintLevel_(LogFlags::NONE, Level::None, __VA_ARGS__)
23+
24+
// Use a macro instead of a function for conditional logging to prevent
25+
// evaluating arguments when logging for the category is not enabled.
26+
#define LogPrint(category, ...) \
27+
do { \
28+
LogPrintf(__VA_ARGS__); \
29+
} while (0)
30+
31+
32+
class CWallet
33+
{
34+
std::string GetDisplayName() const
35+
{
36+
return "default wallet";
37+
}
38+
39+
public:
40+
template <typename... Params>
41+
void WalletLogPrintf(std::string fmt, Params... parameters) const
42+
{
43+
LogPrintf(("%s " + fmt).c_str(), GetDisplayName(), parameters...);
44+
};
45+
};
46+
47+
void good_func()
48+
{
49+
LogPrintf("hello world!\n");
50+
}
51+
void good_func2()
52+
{
53+
CWallet wallet;
54+
wallet.WalletLogPrintf("hi\n");
55+
56+
const CWallet& walletref = wallet;
57+
walletref.WalletLogPrintf("hi\n");
58+
59+
auto* walletptr = new CWallet();
60+
walletptr->WalletLogPrintf("hi\n");
61+
delete walletptr;
62+
}
63+
void bad_func()
64+
{
65+
LogPrintf("hello world!");
66+
}
67+
void bad_func2()
68+
{
69+
LogPrintf("");
70+
}
71+
void bad_func3()
72+
{
73+
// Ending in "..." has no special meaning.
74+
LogPrintf("hello world!...");
75+
}
76+
void bad_func4_ignored()
77+
{
78+
LogPrintf("hello world!"); // NOLINT(bitcoin-unterminated-logprintf)
79+
}
80+
void bad_func5()
81+
{
82+
CWallet wallet;
83+
wallet.WalletLogPrintf("hi");
84+
85+
const CWallet& walletref = wallet;
86+
walletref.WalletLogPrintf("hi");
87+
88+
auto* walletptr = new CWallet();
89+
walletptr->WalletLogPrintf("hi");
90+
delete walletptr;
91+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
// Copyright (c) 2023 Bitcoin Developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include "logprintf.h"
6+
7+
#include <clang/AST/ASTContext.h>
8+
#include <clang/ASTMatchers/ASTMatchFinder.h>
9+
10+
11+
namespace {
12+
AST_MATCHER(clang::StringLiteral, unterminated)
13+
{
14+
size_t len = Node.getLength();
15+
if (len > 0 && Node.getCodeUnit(len - 1) == '\n') {
16+
return false;
17+
}
18+
return true;
19+
}
20+
} // namespace
21+
22+
namespace bitcoin {
23+
24+
void LogPrintfCheck::registerMatchers(clang::ast_matchers::MatchFinder* finder)
25+
{
26+
using namespace clang::ast_matchers;
27+
28+
/*
29+
Logprintf(..., ..., ..., ..., ..., "foo", ...)
30+
*/
31+
32+
finder->addMatcher(
33+
callExpr(
34+
callee(functionDecl(hasName("LogPrintf_"))),
35+
hasArgument(5, stringLiteral(unterminated()).bind("logstring"))),
36+
this);
37+
38+
/*
39+
CWallet wallet;
40+
auto walletptr = &wallet;
41+
wallet.WalletLogPrintf("foo");
42+
wallet->WalletLogPrintf("foo");
43+
*/
44+
finder->addMatcher(
45+
cxxMemberCallExpr(
46+
thisPointerType(qualType(hasDeclaration(cxxRecordDecl(hasName("CWallet"))))),
47+
callee(cxxMethodDecl(hasName("WalletLogPrintf"))),
48+
hasArgument(0, stringLiteral(unterminated()).bind("logstring"))),
49+
this);
50+
}
51+
52+
void LogPrintfCheck::check(const clang::ast_matchers::MatchFinder::MatchResult& Result)
53+
{
54+
if (const clang::StringLiteral* lit = Result.Nodes.getNodeAs<clang::StringLiteral>("logstring")) {
55+
const clang::ASTContext& ctx = *Result.Context;
56+
const auto user_diag = diag(lit->getEndLoc(), "Unterminated format string used with LogPrintf");
57+
const auto& loc = lit->getLocationOfByte(lit->getByteLength(), *Result.SourceManager, ctx.getLangOpts(), ctx.getTargetInfo());
58+
user_diag << clang::FixItHint::CreateInsertion(loc, "\\n");
59+
}
60+
}
61+
62+
} // namespace bitcoin
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Copyright (c) 2023 Bitcoin Developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef LOGPRINTF_CHECK_H
6+
#define LOGPRINTF_CHECK_H
7+
8+
#include <clang-tidy/ClangTidyCheck.h>
9+
10+
namespace bitcoin {
11+
12+
class LogPrintfCheck final : public clang::tidy::ClangTidyCheck
13+
{
14+
public:
15+
LogPrintfCheck(clang::StringRef Name, clang::tidy::ClangTidyContext* Context)
16+
: clang::tidy::ClangTidyCheck(Name, Context) {}
17+
18+
bool isLanguageVersionSupported(const clang::LangOptions& LangOpts) const override
19+
{
20+
return LangOpts.CPlusPlus;
21+
}
22+
void registerMatchers(clang::ast_matchers::MatchFinder* Finder) override;
23+
void check(const clang::ast_matchers::MatchFinder::MatchResult& Result) override;
24+
};
25+
26+
} // namespace bitcoin
27+
28+
#endif // LOGPRINTF_CHECK_H

src/.clang-tidy

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
Checks: '
22
-*,
3+
bitcoin-unterminated-logprintf,
34
bugprone-argument-comment,
45
bugprone-use-after-move,
56
misc-unused-using-decls,

src/dbwrapper.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class CBitcoinLevelDBLogger : public leveldb::Logger {
7676

7777
assert(p <= limit);
7878
base[std::min(bufsize - 1, (int)(p - base))] = '\0';
79-
LogPrintLevel(BCLog::LEVELDB, BCLog::Level::Debug, "%s", base);
79+
LogPrintLevel(BCLog::LEVELDB, BCLog::Level::Debug, "%s", base); // NOLINT(bitcoin-unterminated-logprintf)
8080
if (base != buffer) {
8181
delete[] base;
8282
}

test/lint/lint-format-strings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def main():
7777

7878
matching_files_filtered = []
7979
for matching_file in matching_files:
80-
if not re.search('^src/(leveldb|secp256k1|minisketch|tinyformat|test/fuzz/strprintf.cpp)', matching_file):
80+
if not re.search('^src/(leveldb|secp256k1|minisketch|tinyformat|test/fuzz/strprintf.cpp)|contrib/devtools/bitcoin-tidy/example_logprintf.cpp', matching_file):
8181
matching_files_filtered.append(matching_file)
8282
matching_files_filtered.sort()
8383

0 commit comments

Comments
 (0)