Skip to content

Commit

Permalink
[clang-tidy] Add performance-avoid-endl check
Browse files Browse the repository at this point in the history
This check flags uses of `std::endl` on streams and suggests using the newline character `'\n'` instead. `std::endl` performs two operations: it writes a newline character to the output stream and then flushes the stream buffer, which can be less efficient than writing a single newline character using `'\n'`.

This fixes #35321

Reviewed By: PiotrZSL

Differential Revision: https://reviews.llvm.org/D148318
  • Loading branch information
AMS21 authored and PiotrZSL committed Apr 22, 2023
1 parent a79a6ea commit cd89330
Show file tree
Hide file tree
Showing 8 changed files with 416 additions and 0 deletions.
84 changes: 84 additions & 0 deletions clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.cpp
@@ -0,0 +1,84 @@
//===--- AvoidEndlCheck.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 "AvoidEndlCheck.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclCXX.h"
#include "clang/AST/Expr.h"
#include "clang/AST/ExprCXX.h"
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/ASTMatchers/ASTMatchers.h"
#include "clang/Lex/Lexer.h"

using namespace clang::ast_matchers;

namespace clang::tidy::performance {

void AvoidEndlCheck::registerMatchers(MatchFinder *Finder) {
Finder->addMatcher(
callExpr(
unless(isExpansionInSystemHeader()),
anyOf(cxxOperatorCallExpr(
hasOverloadedOperatorName("<<"),
hasRHS(declRefExpr(to(namedDecl(hasName("::std::endl"))))
.bind("expr"))),
callExpr(argumentCountIs(1),
callee(functionDecl(hasName("::std::endl"))))
.bind("expr"))),
this);
}

void AvoidEndlCheck::check(const MatchFinder::MatchResult &Result) {
const auto *Expression = Result.Nodes.getNodeAs<Expr>("expr");
assert(Expression);
assert(isa<DeclRefExpr>(Expression) || isa<CallExpr>(Expression));

// FIXME: It would be great if we could transform
// 'std::cout << "Hi" << std::endl;' into
// 'std::cout << "Hi\n"';

if (llvm::isa<DeclRefExpr>(Expression)) {
// Handle the more common streaming '... << std::endl' case
const CharSourceRange TokenRange =
CharSourceRange::getTokenRange(Expression->getSourceRange());
const StringRef SourceText = Lexer::getSourceText(
TokenRange, *Result.SourceManager, Result.Context->getLangOpts());

auto Diag = diag(Expression->getBeginLoc(),
"do not use '%0' with streams; use '\\n' instead")
<< SourceText;

Diag << FixItHint::CreateReplacement(TokenRange, "'\\n'");
} else {
// Handle the less common function call 'std::endl(...)' case
const auto *CallExpression = llvm::cast<CallExpr>(Expression);
assert(CallExpression->getNumArgs() == 1);

const StringRef SourceText = Lexer::getSourceText(
CharSourceRange::getTokenRange(
CallExpression->getCallee()->getSourceRange()),
*Result.SourceManager, Result.Context->getLangOpts());

const CharSourceRange ArgTokenRange = CharSourceRange::getTokenRange(
CallExpression->getArg(0)->getSourceRange());
const StringRef ArgSourceText = Lexer::getSourceText(
ArgTokenRange, *Result.SourceManager, Result.Context->getLangOpts());

const std::string ReplacementString =
std::string(ArgSourceText) + " << '\\n'";

diag(CallExpression->getBeginLoc(),
"do not use '%0' with streams; use '\\n' instead")
<< SourceText
<< FixItHint::CreateReplacement(
CharSourceRange::getTokenRange(CallExpression->getSourceRange()),
ReplacementString);
}
}

} // namespace clang::tidy::performance
40 changes: 40 additions & 0 deletions clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.h
@@ -0,0 +1,40 @@
//===--- AvoidEndlCheck.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_PERFORMANCE_AVOIDENDLCHECK_H
#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_AVOIDENDLCHECK_H

#include "../ClangTidyCheck.h"

namespace clang::tidy::performance {

/// ClangTidyCheck Checks to flag for uses of 'std::endl' on streams and
/// suggests using the newline character '"\n"' instead.
///
/// For the user-facing documentation see:
/// https://clang.llvm.org/extra/clang-tidy/checks/performance/avoid-endl.html
class AvoidEndlCheck : public ClangTidyCheck {
public:
AvoidEndlCheck(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;

std::optional<TraversalKind> getCheckTraversalKind() const override {
return TK_IgnoreUnlessSpelledInSource;
}
};

} // namespace clang::tidy::performance

#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_PERFORMANCE_AVOIDENDLCHECK_H
1 change: 1 addition & 0 deletions clang-tools-extra/clang-tidy/performance/CMakeLists.txt
Expand Up @@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)

add_clang_library(clangTidyPerformanceModule
AvoidEndlCheck.cpp
FasterStringFindCheck.cpp
ForRangeCopyCheck.cpp
ImplicitConversionInLoopCheck.cpp
Expand Down
Expand Up @@ -9,6 +9,7 @@
#include "../ClangTidy.h"
#include "../ClangTidyModule.h"
#include "../ClangTidyModuleRegistry.h"
#include "AvoidEndlCheck.h"
#include "FasterStringFindCheck.h"
#include "ForRangeCopyCheck.h"
#include "ImplicitConversionInLoopCheck.h"
Expand All @@ -31,6 +32,7 @@ namespace performance {
class PerformanceModule : public ClangTidyModule {
public:
void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override {
CheckFactories.registerCheck<AvoidEndlCheck>("performance-avoid-endl");
CheckFactories.registerCheck<FasterStringFindCheck>(
"performance-faster-string-find");
CheckFactories.registerCheck<ForRangeCopyCheck>(
Expand Down
5 changes: 5 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Expand Up @@ -150,6 +150,11 @@ New checks
Converts standard library type traits of the form ``traits<...>::type`` and
``traits<...>::value`` into ``traits_t<...>`` and ``traits_v<...>`` respectively.

- New :doc:`performance-avoid-endl
<clang-tidy/checks/performance/avoid-endl>` check.

Finds uses of ``std::endl`` on streams and replaces them with ``'\n'``.

- New :doc:`readability-avoid-unconditional-preprocessor-if
<clang-tidy/checks/readability/avoid-unconditional-preprocessor-if>` check.

Expand Down
1 change: 1 addition & 0 deletions clang-tools-extra/docs/clang-tidy/checks/list.rst
Expand Up @@ -314,6 +314,7 @@ Clang-Tidy Checks
`objc-super-self <objc/super-self.html>`_, "Yes"
`openmp-exception-escape <openmp/exception-escape.html>`_,
`openmp-use-default-none <openmp/use-default-none.html>`_,
`performance-avoid-endl <performance/avoid-endl.html>`_, "Yes"
`performance-faster-string-find <performance/faster-string-find.html>`_, "Yes"
`performance-for-range-copy <performance/for-range-copy.html>`_, "Yes"
`performance-implicit-conversion-in-loop <performance/implicit-conversion-in-loop.html>`_,
Expand Down
@@ -0,0 +1,56 @@
.. title:: clang-tidy - performance-avoid-endl

performance-avoid-endl
============================

Checks for uses of ``std::endl`` on streams and suggests using the newline
character ``'\n'`` instead.

Rationale:
Using ``std::endl`` on streams can be less efficient than using the newline
character ``'\n'`` because ``std::endl`` performs two operations: it writes a
newline character to the output stream and then flushes the stream buffer.
Writing a single newline character using ``'\n'`` does not trigger a flush,
which can improve performance. In addition, flushing the stream buffer can
cause additional overhead when working with streams that are buffered.

Example:

Consider the following code:

.. code-block:: c++
#include <iostream>

int main() {
std::cout << "Hello" << std::endl;
}

Which gets transformed into:

.. code-block:: c++
#include <iostream>

int main() {
std::cout << "Hello" << '\n';
}

This code writes a single newline character to the ``std::cout`` stream without
flushing the stream buffer.

Additionally, it is important to note that the standard C++ streams (like
``std::cerr``, ``std::wcerr``, ``std::clog`` and ``std::wclog``)
always flush after a write operation, unless ``std::ios_base::sync_with_stdio``
is set to ``false``. regardless of whether ``std::endl`` or ``'\n'`` is used.
Therefore, using ``'\n'`` with these streams will not
result in any performance gain, but it is still recommended to use
``'\n'`` for consistency and readability.

If you do need to flush the stream buffer, you can use ``std::flush``
explicitly like this:

.. code-block:: c++
#include <iostream>

int main() {
std::cout << "Hello\n" << std::flush;
}

0 comments on commit cd89330

Please sign in to comment.