Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[clang-tidy] Add
performance-avoid-endl
check
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
Showing
8 changed files
with
416 additions
and
0 deletions.
There are no files selected for viewing
84 changes: 84 additions & 0 deletions
84
clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.cpp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
clang-tools-extra/docs/clang-tidy/checks/performance/avoid-endl.rst
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} |
Oops, something went wrong.