[clang-tidy] Check for dynamically initialized statics in headers.

Finds instances where variables with static storage are initialized dynamically in header files.

Reviewed By: aaron.ballman, alexfh

Patch by Charles Zhang!

Differential Revision: https://reviews.llvm.org/D62829

llvm-svn: 369568
This commit is contained in:
Yuanfang Chen 2019-08-21 20:00:01 +00:00
parent ed18e70c86
commit f24c1e6b51
8 changed files with 193 additions and 1 deletions

View File

@ -16,6 +16,7 @@
#include "BranchCloneCheck.h"
#include "CopyConstructorInitCheck.h"
#include "DanglingHandleCheck.h"
#include "DynamicStaticInitializersCheck.h"
#include "ExceptionEscapeCheck.h"
#include "FoldInitTypeCheck.h"
#include "ForwardDeclarationNamespaceCheck.h"
@ -73,6 +74,8 @@ public:
"bugprone-copy-constructor-init");
CheckFactories.registerCheck<DanglingHandleCheck>(
"bugprone-dangling-handle");
CheckFactories.registerCheck<DynamicStaticInitializersCheck>(
"bugprone-dynamic-static-initializers");
CheckFactories.registerCheck<ExceptionEscapeCheck>(
"bugprone-exception-escape");
CheckFactories.registerCheck<FoldInitTypeCheck>(

View File

@ -8,6 +8,7 @@ add_clang_library(clangTidyBugproneModule
BugproneTidyModule.cpp
CopyConstructorInitCheck.cpp
DanglingHandleCheck.cpp
DynamicStaticInitializersCheck.cpp
ExceptionEscapeCheck.cpp
FoldInitTypeCheck.cpp
ForwardDeclarationNamespaceCheck.cpp

View File

@ -0,0 +1,68 @@
//===--- DynamicStaticInitializersCheck.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 "DynamicStaticInitializersCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
using namespace clang::ast_matchers;
namespace clang {
namespace tidy {
namespace bugprone {
AST_MATCHER(clang::VarDecl, hasConstantDeclaration) {
const Expr *Init = Node.getInit();
if (Init && !Init->isValueDependent()) {
if (Node.isConstexpr())
return true;
return Node.checkInitIsICE();
}
return false;
}
DynamicStaticInitializersCheck::DynamicStaticInitializersCheck(StringRef Name,
ClangTidyContext *Context)
: ClangTidyCheck(Name, Context),
RawStringHeaderFileExtensions(Options.getLocalOrGlobal(
"HeaderFileExtensions", utils::defaultHeaderFileExtensions())) {
if (!utils::parseHeaderFileExtensions(RawStringHeaderFileExtensions,
HeaderFileExtensions, ',')) {
llvm::errs() << "Invalid header file extension: "
<< RawStringHeaderFileExtensions << "\n";
}
}
void DynamicStaticInitializersCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "HeaderFileExtensions", RawStringHeaderFileExtensions);
}
void DynamicStaticInitializersCheck::registerMatchers(MatchFinder *Finder) {
if (!getLangOpts().CPlusPlus || getLangOpts().ThreadsafeStatics)
return;
Finder->addMatcher(
varDecl(hasGlobalStorage(), unless(hasConstantDeclaration())).bind("var"),
this);
}
void DynamicStaticInitializersCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Var = Result.Nodes.getNodeAs<VarDecl>("var");
SourceLocation Loc = Var->getLocation();
if (!Loc.isValid() || !utils::isPresumedLocInHeaderFile(Loc, *Result.SourceManager,
HeaderFileExtensions))
return;
// If the initializer is a constant expression, then the compiler
// doesn't have to dynamically initialize it.
diag(Loc, "static variable %0 may be dynamically initialized in this header file")
<< Var;
}
} // namespace bugprone
} // namespace tidy
} // namespace clang

View File

@ -0,0 +1,43 @@
//===--- DynamicStaticInitializersCheck.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_BUGPRONE_DYNAMIC_STATIC_INITIALIZERS_CHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DYNAMIC_STATIC_INITIALIZERS_CHECK_H
#include "../ClangTidyCheck.h"
#include "../utils/HeaderFileExtensionsUtils.h"
namespace clang {
namespace tidy {
namespace bugprone {
/// Finds dynamically initialized static variables in header files.
///
/// The check supports these options:
/// - `HeaderFileExtensions`: a comma-separated list of filename extensions of
/// header files (The filename extensions should not contain "." prefix).
/// "h,hh,hpp,hxx" by default.
/// For extension-less header files, using an empty string or leaving an
/// empty string between "," if there are other filename extensions.
class DynamicStaticInitializersCheck : public ClangTidyCheck {
public:
DynamicStaticInitializersCheck(StringRef Name, ClangTidyContext *Context);
void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
void registerMatchers(ast_matchers::MatchFinder *Finder) override;
void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
private:
const std::string RawStringHeaderFileExtensions;
utils::HeaderFileExtensionsSet HeaderFileExtensions;
};
} // namespace bugprone
} // namespace tidy
} // namespace clang
#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_BUGPRONE_DYNAMIC_STATIC_INITIALIZERS_CHECK_H

View File

@ -67,6 +67,12 @@ The improvements are...
Improvements to clang-tidy
--------------------------
- New :doc:`bugprone-dynamic-static-initializers
<clang-tidy/checks/bugprone-dynamic-static-initializers>` check.
Finds instances where variables with static storage are initialized
dynamically in header files.
- New :doc:`linuxkernel-must-use-errs
<clang-tidy/checks/linuxkernel-must-use-errs>` check.
@ -79,7 +85,6 @@ Improvements to clang-tidy
Finds uses of deprecated Googletest APIs with names containing ``case`` and
replaces them with equivalent APIs with ``suite``.
Improvements to include-fixer
-----------------------------

View File

@ -0,0 +1,27 @@
.. title:: clang-tidy - bugprone-dynamic-static-initializers
bugprone-dynamic-static-initializers
====================================
Finds instances of static variables that are dynamically initialized
in header files.
This can pose problems in certain multithreaded contexts. For example,
when disabling compiler generated synchronization instructions for
static variables initialized at runtime (e.g. by ``-fno-threadsafe-statics``), even if a particular project
takes the necessary precautions to prevent race conditions during
initialization by providing their own synchronization, header files included from other projects may
not. Therefore, such a check is helpful for ensuring that disabling
compiler generated synchronization for static variable initialization will not cause
problems.
Consider the following code:
-- code-block:: c
int foo() {
static int k = bar();
return k;
}
When synchronization of static initialization is disabled, if two threads both call `foo` for the first time, there is the possibility that `k` will be double initialized, creating a race condition.

View File

@ -44,6 +44,7 @@ Clang-Tidy Checks
bugprone-branch-clone
bugprone-copy-constructor-init
bugprone-dangling-handle
bugprone-dynamic-static-initializers
bugprone-exception-escape
bugprone-fold-init-type
bugprone-forward-declaration-namespace

View File

@ -0,0 +1,44 @@
// RUN: %check_clang_tidy %s bugprone-dynamic-static-initializers %t -- -- -fno-threadsafe-statics
int fact(int n) {
return (n == 0) ? 1 : n * fact(n - 1);
}
int static_thing = fact(5);
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: static variable 'static_thing' may be dynamically initialized in this header file [bugprone-dynamic-static-initializers]
int sample() {
int x;
return x;
}
int dynamic_thing = sample();
// CHECK-MESSAGES: :[[@LINE-1]]:5: warning: static variable 'dynamic_thing' may be dynamically initialized in this header file [bugprone-dynamic-static-initializers]
int not_so_bad = 12 + 4942; // no warning
extern int bar();
int foo() {
static int k = bar();
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: static variable 'k' may be dynamically initialized in this header file [bugprone-dynamic-static-initializers]
return k;
}
int bar2() {
return 7;
}
int foo2() {
// This may work fine when optimization is enabled because bar() can
// be turned into a constant 7. But without optimization, it can
// cause problems. Therefore, we must err on the side of conservatism.
static int x = bar2();
// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: static variable 'x' may be dynamically initialized in this header file [bugprone-dynamic-static-initializers]
return x;
}
int foo3() {
static int p = 7 + 83; // no warning
return p;
}