diff --git a/clang-tools-extra/clang-tidy/misc/AvoidStdIoOutsideMainCheck.h b/clang-tools-extra/clang-tidy/misc/AvoidStdIoOutsideMainCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/AvoidStdIoOutsideMainCheck.h @@ -0,0 +1,49 @@ +//===--- AvoidStdIoOutsideMainCheck.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_MISC_AVOIDSTDIOOUTSIDEMAINCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_AVOIDSTDIOOUTSIDEMAINCHECK_H + +#include "../ClangTidyCheck.h" +#include + +namespace clang { +namespace tidy { +namespace misc { + +/// Finds predefined standard stream objects like ``cin``, ``wcin``, ``cout``, +/// ``wcout``, ``cerr`` or ``wcerr``, that are used outside the ``main`` +/// function. It also finds uses of ``cstdio`` and ``stdio.h`` functions like +/// ``printf()`` outside the ``main`` function. +/// +/// For the user-facing documentation and examples see: +/// http://clang.llvm.org/extra/clang-tidy/checks/misc-avoid-std-io-outside-main.html +class AvoidStdIoOutsideMainCheck : public ClangTidyCheck { +public: + AvoidStdIoOutsideMainCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + + bool isLanguageVersionSupported(const LangOptions &LangOpts) const override { + return LangOpts.CPlusPlus; + } + + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + +private: + const std::vector StdIOStreams = {"cin", "wcin", "cout", + "wcout", "cerr", "wcerr"}; + const std::vector CLikeIOFunctions = { + "printf", "vprintf", "puts", "putchar", "scanf", "getchar", "gets"}; +}; + +} // namespace misc +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_MISC_AVOIDSTDIOOUTSIDEMAINCHECK_H diff --git a/clang-tools-extra/clang-tidy/misc/AvoidStdIoOutsideMainCheck.cpp b/clang-tools-extra/clang-tidy/misc/AvoidStdIoOutsideMainCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/misc/AvoidStdIoOutsideMainCheck.cpp @@ -0,0 +1,70 @@ +//===--- AvoidStdIoOutsideMainCheck.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 "AvoidStdIoOutsideMainCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace misc { + +void AvoidStdIoOutsideMainCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + declRefExpr(to(varDecl(hasAnyName(SmallVector( + StdIOStreams.begin(), StdIOStreams.end())), + isInStdNamespace())), + unless(forFunction(isMain()))) + .bind("StdStreamObject"), + this); + + Finder->addMatcher( + declRefExpr(hasDeclaration(functionDecl(hasAnyName(SmallVector( + CLikeIOFunctions.begin(), CLikeIOFunctions.end())))), + unless(forFunction(isMain()))) + .bind("CLibFunction"), + this); + + /// Matcher for indirect stdio uses: + /// \code + /// auto Print = &puts; + /// Print("This is using stdio"); + /// \endcode + Finder->addMatcher( + declRefExpr( + hasDeclaration(varDecl(hasDescendant(declRefExpr( + hasDeclaration(functionDecl(hasAnyName(SmallVector( + CLikeIOFunctions.begin(), CLikeIOFunctions.end())))))))), + unless(forFunction(isMain()))) + .bind("CLibFunction"), + this); +} + +void AvoidStdIoOutsideMainCheck::check(const MatchFinder::MatchResult &Result) { + + if (const auto *MatchedStreamObj = + Result.Nodes.getNodeAs("StdStreamObject")) { + diag(MatchedStreamObj->getLocation(), + "predefined standard stream objects should " + "not be used outside the main function"); + return; + } + + if (const auto *MatchedCLibFunc = + Result.Nodes.getNodeAs("CLibFunction")) { + diag(MatchedCLibFunc->getLocation(), + "cstdio functions should not be used outside the main function"); + return; + } +} + +} // namespace misc +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/misc/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/misc/CMakeLists.txt @@ -4,6 +4,7 @@ ) add_clang_library(clangTidyMiscModule + AvoidStdIoOutsideMainCheck.cpp DefinitionsInHeadersCheck.cpp MiscTidyModule.cpp MisplacedConstCheck.cpp diff --git a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp --- a/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/misc/MiscTidyModule.cpp @@ -9,6 +9,7 @@ #include "../ClangTidy.h" #include "../ClangTidyModule.h" #include "../ClangTidyModuleRegistry.h" +#include "AvoidStdIoOutsideMainCheck.h" #include "DefinitionsInHeadersCheck.h" #include "MisplacedConstCheck.h" #include "NewDeleteOverloadsCheck.h" @@ -31,6 +32,8 @@ class MiscModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "misc-avoid-std-io-outside-main"); CheckFactories.registerCheck( "misc-definitions-in-headers"); CheckFactories.registerCheck("misc-misplaced-const"); diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -119,6 +119,14 @@ Finds calls to ``new`` with missing exception handler for ``std::bad_alloc``. +- New :doc:`misc-avoid-std-io-outside-main + ` check. + + Finds predefined standard stream objects like ``cin``, ``wcin``, ``cout``, + ``wcout``, ``cerr`` or ``wcerr``, that are used outside the ``main`` + function. It also finds uses of ``cstdio`` and ``stdio.h`` functions like + ``printf()`` outside the ``main`` function. + New check aliases ^^^^^^^^^^^^^^^^^ diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -205,6 +205,7 @@ `llvmlibc-callee-namespace `_, `llvmlibc-implementation-in-namespace `_, `llvmlibc-restrict-system-libc-headers `_, "Yes" + `misc-misc-avoid-std-io-outside-main `_, `misc-definitions-in-headers `_, "Yes" `misc-misplaced-const `_, `misc-new-delete-overloads `_, diff --git a/clang-tools-extra/docs/clang-tidy/checks/misc-avoid-std-io-outside-main.rst b/clang-tools-extra/docs/clang-tidy/checks/misc-avoid-std-io-outside-main.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/misc-avoid-std-io-outside-main.rst @@ -0,0 +1,52 @@ +.. title:: clang-tidy - misc-avoid-std-io-outside-main + +misc-avoid-std-io-outside-main +============= + +Finds predefined standard stream objects like ``cin``, ``wcin``, ``cout``, +``wcout``, ``cerr`` or ``wcerr``, that are used outside the ``main`` +function. It also finds uses of ``cstdio`` and ``stdio.h`` functions like +``printf()`` outside the ``main`` function. + +For instance, in the following code, the use of ``std::cout`` and ``printf()`` +outside of ``main()`` would get flagged whereas the use of them inside +``main()`` is not flagged: + +.. code-block:: c++ + + #include + #include + + void some_function() { + std::cout << "This triggers the check."; // NOK + std::printf("This triggers the check."); // NOK + } + + int main() { + std::cout << "This does not trigger the check."; // OK + std::printf("This does not trigger the check."); // OK + } + +Since the predefined standard stream objects are global objects, their use +outside of ``main()`` worsens a program's testability and is thus discouraged. +Instead, those objects should only be used inside ``main()``. They can then be +passed as arguments to other functions like so: + +.. code-block:: c++ + + #include + + void some_function(std::istream & in, std::ostream & out) { + out << "This does not trigger the check."; + + int i{0}; + in >> i; + } + + int main() { + some_function(std::cin, std::cout); + } + +In contrast to using ``std::cin`` and ``std::cout`` directly, in the above +example, it is possible to inject mocked stream objects into +``some_function()`` during testing.