Index: clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp =================================================================== --- clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp +++ clang-tools-extra/clang-tidy/bugprone/BugproneTidyModule.cpp @@ -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" @@ -72,6 +73,8 @@ "bugprone-copy-constructor-init"); CheckFactories.registerCheck( "bugprone-dangling-handle"); + CheckFactories.registerCheck( + "bugprone-dynamic-static-initializers"); CheckFactories.registerCheck( "bugprone-exception-escape"); CheckFactories.registerCheck( Index: clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt =================================================================== --- clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt +++ clang-tools-extra/clang-tidy/bugprone/CMakeLists.txt @@ -8,6 +8,7 @@ BugproneTidyModule.cpp CopyConstructorInitCheck.cpp DanglingHandleCheck.cpp + DynamicStaticInitializersCheck.cpp ExceptionEscapeCheck.cpp FoldInitTypeCheck.cpp ForwardDeclarationNamespaceCheck.cpp Index: clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.h =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.h @@ -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 Index: clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.cpp =================================================================== --- /dev/null +++ clang-tools-extra/clang-tidy/bugprone/DynamicStaticInitializersCheck.cpp @@ -0,0 +1,70 @@ +//===--- 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 { + +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) { + // FIXME: Add matchers. + if (!getLangOpts().CPlusPlus) + return; + Finder->addMatcher(varDecl().bind("var"), this); +} + +static bool hasConstantDeclaration(const VarDecl *Var) { + const Expr *Init = Var->getInit(); + if (Init && !Init->isValueDependent()) { + if (Var->isConstexpr()) + return true; + return Var->checkInitIsICE(); + } + return false; +} + +void DynamicStaticInitializersCheck::check(const MatchFinder::MatchResult &Result) { + // FIXME: Add callback implementation. + auto *Var = Result.Nodes.getNodeAs("var"); + SourceLocation Loc = Var->getLocation(); + if (!Loc.isValid() || !utils::isPresumedLocInHeaderFile(Loc, *Result.SourceManager, + HeaderFileExtensions)) + return; + if (Var->hasGlobalStorage()) + // If the initializer is a constant expression, then the compiler + // doesn't have to dynamically initialize it. + if (!hasConstantDeclaration(Var)) + diag(Loc, "static variable %0 may be dynamically initialized in this header file") + << Var; +} + +} // namespace bugprone +} // namespace tidy +} // namespace clang Index: clang-tools-extra/docs/ReleaseNotes.rst =================================================================== --- clang-tools-extra/docs/ReleaseNotes.rst +++ clang-tools-extra/docs/ReleaseNotes.rst @@ -101,6 +101,12 @@ Finds and fixes ``absl::Time`` subtraction expressions to do subtraction in the Time domain instead of the numeric domain. +- New :doc:`bugprone-dynamic-static-initializers + ` check. + + Finds instances where variables with static storage are initialized + dynamically in header files. + - New :doc:`bugprone-unhandled-self-assignment ` check. Index: clang-tools-extra/docs/clang-tidy/checks/bugprone-dynamic-static-initializers.rst =================================================================== --- /dev/null +++ clang-tools-extra/docs/clang-tidy/checks/bugprone-dynamic-static-initializers.rst @@ -0,0 +1,9 @@ +.. 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. Index: clang-tools-extra/docs/clang-tidy/checks/list.rst =================================================================== --- clang-tools-extra/docs/clang-tidy/checks/list.rst +++ clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -41,6 +41,7 @@ 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 Index: clang-tools-extra/test/clang-tidy/bugprone-dynamic-static-initializers.hpp =================================================================== --- /dev/null +++ clang-tools-extra/test/clang-tidy/bugprone-dynamic-static-initializers.hpp @@ -0,0 +1,44 @@ +// RUN: %check_clang_tidy %s bugprone-dynamic-static-initializers %t + +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; +}