SERVER-71741 Move macro definition leaks check to clang-tidy

This commit is contained in:
Juan Gu 2023-06-05 16:37:19 +00:00 committed by Evergreen Agent
parent 24af688c58
commit d981535c05
10 changed files with 195 additions and 23 deletions

View File

@ -39,6 +39,7 @@ Checks: '-*,
mongo-cxx20-banned-includes-check,
mongo-header-bracket-check,
mongo-std-atomic-check,
mongo-macro-definition-leaks-check,
mongo-mutex-check,
mongo-assert-check,
mongo-std-optional-check,

View File

@ -141,9 +141,6 @@ class Linter:
self._check_newlines()
self._check_and_strip_comments()
# File-level checks
self._check_macro_definition_leaks()
# Line-level checks
for linenum in range(start_line, len(self.clean_lines)):
if not self.clean_lines[linenum]:
@ -209,26 +206,6 @@ class Linter:
self.clean_lines.append(clean_line)
def _check_macro_definition_leaks(self):
"""Some header macros should appear in define/undef pairs."""
if not _RE_HEADER.search(self.file_name):
return
# Naive check: doesn't consider `#if` scoping.
# Assumes an #undef matches the nearest #define.
for macro in ['MONGO_LOGV2_DEFAULT_COMPONENT']:
re_define = re.compile(fr"^\s*#\s*define\s+{macro}\b")
re_undef = re.compile(fr"^\s*#\s*undef\s+{macro}\b")
def_line = None
for idx, line in enumerate(self.clean_lines):
if def_line is None:
if re_define.match(line):
def_line = idx
else:
if re_undef.match(line):
def_line = None
if def_line is not None:
self._error(def_line, 'mongodb/undefmacro', f'Missing "#undef {macro}"')
def _check_for_mongo_polyfill(self, linenum):
line = self.clean_lines[linenum]
match = _RE_PATTERN_MONGO_POLYFILL.search(line)

View File

@ -0,0 +1,114 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#include "MongoMacroDefinitionLeaksCheck.h"
#include <clang/Lex/PPCallbacks.h>
#include <clang/Lex/Preprocessor.h>
namespace mongo::tidy {
using namespace clang;
using namespace clang::ast_matchers;
// Callbacks for handling preprocessor events
class MongoMacroPPCallbacks : public clang::PPCallbacks {
public:
explicit MongoMacroPPCallbacks(MongoMacroDefinitionLeaksCheck& Check,
clang::LangOptions LangOpts,
const clang::SourceManager& SM)
: Check(Check), LangOpts(LangOpts), SM(SM) {}
// Callback for when a macro is defined
void MacroDefined(const clang::Token& MacroNameTok, const clang::MacroDirective* MD) override {
llvm::StringRef macroName = MacroNameTok.getIdentifierInfo()->getName();
if (macroName == "MONGO_LOGV2_DEFAULT_COMPONENT") {
defineUndefDiff += 1;
lastMacroLocation = MD->getLocation();
}
}
// Callback for when a macro is undefined
void MacroUndefined(const clang::Token& MacroNameTok,
const clang::MacroDefinition& MD,
const clang::MacroDirective* Undef) override {
llvm::StringRef macroName = MacroNameTok.getIdentifierInfo()->getName();
if (macroName == "MONGO_LOGV2_DEFAULT_COMPONENT") {
defineUndefDiff -= 1;
}
}
// Callback for when a file is included or excluded
void FileChanged(SourceLocation Loc,
FileChangeReason Reason,
SrcMgr::CharacteristicKind FileType,
FileID PrevFID) override {
if (Reason != EnterFile && Reason != ExitFile)
return;
const FileEntry* CurrentFile = SM.getFileEntryForID(SM.getFileID(Loc));
if (!CurrentFile)
return;
if (Reason == EnterFile) {
// Push the file to the stack
fileStack.push(CurrentFile->getName().str());
defineUndefDiff = 0;
} else if (Reason == ExitFile && !fileStack.empty()) {
// Get the top file from the stack
std::string currentFileName = fileStack.top();
fileStack.pop();
if (defineUndefDiff != 0) {
Check.diag(lastMacroLocation, "Missing #undef 'MONGO_LOGV2_DEFAULT_COMPONENT'");
}
}
}
private:
MongoMacroDefinitionLeaksCheck& Check;
clang::LangOptions LangOpts;
const clang::SourceManager& SM;
int defineUndefDiff = 0;
clang::SourceLocation lastMacroLocation;
std::stack<std::string> fileStack;
};
MongoMacroDefinitionLeaksCheck::MongoMacroDefinitionLeaksCheck(
llvm::StringRef Name, clang::tidy::ClangTidyContext* Context)
: ClangTidyCheck(Name, Context) {}
void MongoMacroDefinitionLeaksCheck::registerPPCallbacks(const clang::SourceManager& SM,
clang::Preprocessor* PP,
clang::Preprocessor* ModuleExpanderPP) {
PP->addPPCallbacks(::std::make_unique<MongoMacroPPCallbacks>(*this, getLangOpts(), SM));
}
} // namespace mongo::tidy

View File

@ -0,0 +1,58 @@
/**
* Copyright (C) 2023-present MongoDB, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the Server Side Public License, version 1,
* as published by MongoDB, Inc.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* Server Side Public License for more details.
*
* You should have received a copy of the Server Side Public License
* along with this program. If not, see
* <http://www.mongodb.com/licensing/server-side-public-license>.
*
* As a special exception, the copyright holders give permission to link the
* code of portions of this program with the OpenSSL library under certain
* conditions as described in each individual source file and distribute
* linked combinations including the program with the OpenSSL library. You
* must comply with the Server Side Public License in all respects for
* all of the code used other than as permitted herein. If you modify file(s)
* with this exception, you may extend this exception to your version of the
* file(s), but you are not obligated to do so. If you do not wish to do so,
* delete this exception statement from your version. If you delete this
* exception statement from all source files in the program, then also delete
* it in the license file.
*/
#pragma once
#include <clang-tidy/ClangTidy.h>
#include <clang-tidy/ClangTidyCheck.h>
namespace mongo::tidy {
/**
* MongoMacroDefinitionLeaksCheck is a custom clang-tidy check for detecting
* the imbalance between the definitions and undefinitions of the macro
* "MONGO_LOGV2_DEFAULT_COMPONENT" in the same file.
*
* It extends ClangTidyCheck and overrides the registerPPCallbacks function. The registerPPCallbacks
* function adds a custom Preprocessor callback class (MongoMacroPPCallbacks) to handle
* preprocessor events and detect an imbalance in the definitions and undefinitions of the
* "MONGO_LOGV2_DEFAULT_COMPONENT" macro within each file.
*
* If a .h or .hpp file is found to have a non-zero difference between definitions and undefinitions
* of the "MONGO_LOGV2_DEFAULT_COMPONENT" macro, it's considered a leak and the check raises a
* diagnostic message pointing out the location of the last macro definition.
*/
class MongoMacroDefinitionLeaksCheck : public clang::tidy::ClangTidyCheck {
public:
MongoMacroDefinitionLeaksCheck(clang::StringRef Name, clang::tidy::ClangTidyContext* Context);
void registerPPCallbacks(const clang::SourceManager& SM,
clang::Preprocessor* PP,
clang::Preprocessor* ModuleExpanderPP) override;
};
} // namespace mongo::tidy

View File

@ -34,6 +34,7 @@
#include "MongoCxx20BannedIncludesCheck.h"
#include "MongoFCVConstantCheck.h"
#include "MongoHeaderBracketCheck.h"
#include "MongoMacroDefinitionLeaksCheck.h"
#include "MongoMutexCheck.h"
#include "MongoStdAtomicCheck.h"
#include "MongoStdOptionalCheck.h"
@ -70,6 +71,8 @@ public:
CheckFactories.registerCheck<MongoUnstructuredLogCheck>("mongo-unstructured-log-check");
CheckFactories.registerCheck<MongoCollectionShardingRuntimeCheck>(
"mongo-collection-sharding-runtime-check");
CheckFactories.registerCheck<MongoMacroDefinitionLeaksCheck>(
"mongo-macro-definition-leaks-check");
}
};

View File

@ -136,6 +136,7 @@ mongo_custom_check = env.SharedLibrary(
"MongoFCVConstantCheck.cpp",
"MongoUnstructuredLogCheck.cpp",
"MongoCollectionShardingRuntimeCheck.cpp",
"MongoMacroDefinitionLeaksCheck.cpp",
],
LIBDEPS_NO_INHERIT=[
'$BUILD_DIR/third_party/shim_allocator',

View File

@ -312,6 +312,21 @@ class MongoTidyTests(unittest.TestCase):
self.run_clang_tidy()
def test_MongoMacroDefinitionLeaksCheck(self):
self.write_config(
textwrap.dedent("""\
Checks: '-*,mongo-macro-definition-leaks-check'
WarningsAsErrors: '*'
HeaderFilterRegex: '(mongo/.*)'
"""))
self.expected_output = [
"Missing #undef 'MONGO_LOGV2_DEFAULT_COMPONENT'",
]
self.run_clang_tidy()
if __name__ == '__main__':
parser = argparse.ArgumentParser()

View File

@ -39,6 +39,7 @@ if env.GetOption('ninja') == 'disabled':
'test_MongoFCVConstantCheck.cpp',
'test_MongoUnstructuredLogCheck.cpp',
'test_MongoCollectionShardingRuntimeCheck.cpp',
'test_MongoMacroDefinitionLeaksCheck.cpp',
]
# So that we can do fast runs, we will generate a separate compilation database file for each

View File

@ -0,0 +1 @@
#include "test_MongoMacroDefinitionLeaksCheck.h"

View File

@ -0,0 +1 @@
#define MONGO_LOGV2_DEFAULT_COMPONENT 1