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 { + +/// Diagnoses if a predefined standard stream object (`cin`, `wcin`, +/// `cout`, `wcout`, `cerr` or `wcerr`) is used outside the `main` function. +/// It also flags uses of uses of `cstdio`/`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,71 @@ +//===--- 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("printf", "vprintf", "puts", "putchar", + "scanf", "getchar", "gets"))))))), + 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 @@ -95,6 +95,14 @@ Finds member initializations in the constructor body which can be placed into the initialization list instead. +- New :doc:`misc-avoid-std-io-outside-main + ` check. + + Diagnoses if a predefined standard stream object (``cin``, ``wcin``, + ``cout``, ``wcout``, ``cerr`` or ``wcerr``) is used outside the ``main`` function. + It also flags uses of uses of ``cstdio``/``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 @@ -202,6 +202,7 @@ `llvmlibc-callee-namespace `_, `llvmlibc-implementation-in-namespace `_, `llvmlibc-restrict-system-libc-headers `_, "Yes" + `misc-misc-avoid-std-io-outside-main `_, "Yes" `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,50 @@ +.. title:: clang-tidy - misc-avoid-std-io-outside-main + +misc-avoid-std-io-outside-main +============= + +Diagnoses if a predefined standard stream object (``cin``, ``wcin``, +``cout``, ``wcout``, ``cerr`` or ``wcerr``) or a ``cstdio``/``stdio.h`` function is used +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."; + ~~~~ + std::printf("This triggers the check."); + ~~~~~ + } + + int main() { + std::cout << "This does not trigger the check."; + std::printf("This does not trigger the check."); + } + +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. diff --git a/clang-tools-extra/test/clang-tidy/checkers/misc-avoid-std-io-outside-main.cpp b/clang-tools-extra/test/clang-tidy/checkers/misc-avoid-std-io-outside-main.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/checkers/misc-avoid-std-io-outside-main.cpp @@ -0,0 +1,138 @@ +// RUN: %check_clang_tidy %s misc-avoid-std-io-outside-main %t + +namespace std { +struct string { + string(const char *); + ~string(); +}; + +struct Ostream { + Ostream &operator<<(string Message); +}; + +struct Istream { + Istream &operator>>(string Message); +}; + +Ostream cout{}, wcout{}, cerr{}, wcerr{}; +Istream cin{}, wcin{}; + +int printf(const char *Format, ...); +int vprintf(const char *const, ...); +int puts(const char *Str); +int putchar(int Character); +int scanf(const char *Format, ...); +int getchar(void); +char *gets(char *Str); +} // namespace std + +int printf(const char *Format, ...); +int vprintf(const char *const, ...); +int puts(const char *Str); +int putchar(int Character); +int scanf(const char *Format, ...); +int getchar(void); +char *gets(char *Str); + +namespace arbitrary_namespace { +std::Ostream cout{}; +std::Istream cin{}; +} // namespace arbitrary_namespace + +void anyNonMainFunction() { + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: predefined standard stream objects should not be used outside the main function [misc-avoid-std-io-outside-main] + std::cout << "This should trigger the check"; + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: predefined standard stream objects should not be used outside the main function [misc-avoid-std-io-outside-main] + std::wcout << "This should trigger the check"; + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: predefined standard stream objects should not be used outside the main function [misc-avoid-std-io-outside-main] + std::cerr << "This should trigger the check"; + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: predefined standard stream objects should not be used outside the main function [misc-avoid-std-io-outside-main] + std::wcerr << "This should trigger the check"; + + arbitrary_namespace::cout << "This should not trigger the check"; // OK + + std::string Foo{"bar"}; + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: predefined standard stream objects should not be used outside the main function [misc-avoid-std-io-outside-main] + std::cin >> Foo; + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: predefined standard stream objects should not be used outside the main function [misc-avoid-std-io-outside-main] + std::wcin >> Foo; + + arbitrary_namespace::cin >> Foo; // OK + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + std::printf("This should trigger the check"); + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + printf("This should trigger the check"); + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + std::puts("This should trigger the check"); + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + puts("This should trigger the check"); + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + std::putchar('m'); + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + putchar('m'); + + char Input[5]; + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + std::scanf("%s", Input); + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + scanf("%s", Input); + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + std::getchar(); + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + getchar(); + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + std::gets(Input); + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + gets(Input); + + // CHECK-MESSAGES: :[[@LINE+1]]:8: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + std::vprintf("This should trigger the check %d", 1); + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + vprintf("This should trigger the check %d", 1); + + // CHECK-MESSAGES: :[[@LINE+1]]:17: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + auto Print = &puts; + // CHECK-MESSAGES: :[[@LINE+1]]:3: warning: cstdio functions should not be used outside the main function [misc-avoid-std-io-outside-main] + Print("This should trigger the check"); +} + +int main() { + std::cout << "This should not trigger the check"; // OK + std::wcout << "This should not trigger the check"; // OK + std::cerr << "This should not trigger the check"; // OK + std::wcerr << "This should not trigger the check"; // OK + arbitrary_namespace::cout << "This should not trigger the check"; // OK + + std::string Foo{"bar"}; + std::cin >> Foo; // OK + std::wcin >> Foo; // OK + arbitrary_namespace::cin >> Foo; // OK + + char Input[5]; + std::printf("This should not trigger the check"); // OK + std::puts("This should not trigger the check"); // OK + std::putchar('m'); // OK + std::scanf("%s", Input); // OK + std::getchar(); // OK + std::gets(Input); // OK + std::vprintf("This should not trigger the check %d", 1); // OK + printf("This should not trigger the check"); // OK + puts("This should not trigger the check"); // OK + putchar('m'); // OK + scanf("%s", Input); // OK + getchar(); // OK + gets(Input); // OK + vprintf("This should not trigger the check %d", 1); // OK + auto Print = &puts; // OK + Print("This should not trigger the check"); // OK +}