This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] Add `performance-avoid-endl` check
ClosedPublic

Authored by AMS21 on Apr 14 2023, 3:14 AM.

Details

Summary

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 llvm#35321

Diff Detail

Event Timeline

AMS21 created this revision.Apr 14 2023, 3:14 AM
AMS21 requested review of this revision.Apr 14 2023, 3:14 AM
Herald added a project: Restricted Project. · View Herald TranscriptApr 14 2023, 3:14 AM
Herald added a subscriber: cfe-commits. · View Herald Transcript
AMS21 added a comment.Apr 14 2023, 3:19 AM

This check is mostly working. Added notes about problems I know about.

clang-tools-extra/clang-tidy/performance/DontUseEndlCheck.cpp
44 ↗(On Diff #513513)

This doesn't quite work and I'm not sure why or what would work. Any help would be appreciated.

Report for this like

cpp
std::cout << std::endl;

looks like this:

std::cout << std::endl;
             ^~~~~
             '\n'

So the start location is correct but the end is not.

clang-tools-extra/test/clang-tidy/checkers/performance/dont-use-endl.cpp
45–71 ↗(On Diff #513513)

All these test are missing checks for the fixit. See comment above why they are missing.

For consistency with other checks, please rename it to performance-avoid-endl. You can use the rename_check.py to easily do this :)

AMS21 updated this revision to Diff 513582.Apr 14 2023, 7:18 AM

Rename to performance-avoid-endl

AMS21 retitled this revision from [clang-tidy] Add `performance-dont-use-endl` check to [clang-tidy] Add `performance-avoid-endl` check.Apr 14 2023, 7:18 AM

Thank you for the contribution! Looks good in general, have minor comments.

clang-tools-extra/docs/clang-tidy/checks/performance/avoid-endl.rst
2

Please wrap file to 80 chars

10

Nit: maybe document the rationale for using '\n' instead of "\n"? Readability-wise it's more consistent to always use double-quotes.

24

The rendering does not display lines numbers so this can be confusing. I believe we can just omit it - it's clear what it's referring too.

24

This is repetition of the Rationale on line 8, I would just remove it and say "this gets transformed into:"

clang-tools-extra/test/clang-tidy/checkers/performance/avoid-endl.cpp
48

Use CHECK-FIXES to also test the fixes.

carlosgalvezp added inline comments.Apr 14 2023, 7:30 AM
clang-tools-extra/test/clang-tidy/checkers/performance/avoid-endl.cpp
48

Please add the complete error message, which includes the check name in brackets (see similar tests as reference)

Consider extending this check to suport also std::ends, maybe name it performance-avoid-endl-ends.

clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.cpp
2

rename script got problem with this comment, you may need to align it manually

clang-tools-extra/clang-tidy/performance/DontUseEndlCheck.cpp
26 ↗(On Diff #513513)

unnecessary limit... (in my project we use custom stream class for logging).

Finder->addMatcher(cxxOperatorCallExpr(unless(isExpansionInSystemHeader()),
                                       hasOverloadedOperatorName("<<"),
                                       unless(isMacroExpansion()),
                                       hasRHS(ignoringImplicit(declRefExpr(to(namedDecl(hasAnyName("endl", "ends")).bind("decl"))).bind("expr")))

something like this should be sufficient...
If you do not plan to remove restriction for basic_ostream, make it configurable.

And other problem is that some << operators does not need to be methods, they can be functions, in such case you may run into issues, but you could just read of type from expr... instead processing argument, or class.

Like `cxxOperatorCallExpr(hasType(references(cxxRecordDecl(....`

44 ↗(On Diff #513513)

Use getTokenRange

For what its worth, there typically isn't any performance gain using \n over std::endl when writing to std::cerr or std::clog (or their wide string counterparts) as every write operation on those implicitly calls flush.
However If the fix was changed to convert std::cerr << "Hello" << std::endl into std::cerr << "Hello\n"; That would have a noticeable difference on performance(as it would only be 1 flush rather than 2).

clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.cpp
37

Assert on this, all paths should have this node bound.

41

Wrap the std::endl in single quotes.

Also it may be wise to spell this how the user has it spelt

std::cout << std::endl; // do not use 'std::endl'
using namespace std;
cout << endl; // do not use 'endl'
clang-tools-extra/test/clang-tidy/checkers/performance/avoid-endl.cpp
9–12

These definitions can be elided. Same goes below

25–26

Can you also add definitions and tests for std::wostream with std::wcout/std::wcerr

28–29

cout and cerr aren't iostreams, they are just ostreams.

47

I know it's asking a lot, but it would be amazing if the fix here could be changed to

std::cout << "World\n";

To do this would require checking if the first argument to the endl operator<< call was another operator<< call whose second argument was a string literal.

48

That's not necessary. A lot of test files elide this as there is nothing to gain from it.

AMS21 updated this revision to Diff 513878.Apr 15 2023, 3:10 AM
AMS21 marked 12 inline comments as done.

Implement suggested fixes

clang-tools-extra/test/clang-tidy/checkers/performance/avoid-endl.cpp
47

I agree this would be even cleaner

njames93 added inline comments.Apr 15 2023, 7:04 AM
clang-tools-extra/clang-tidy/performance/AvoidEndlEndsCheck.cpp
28 ↗(On Diff #513878)

Why are you matching on ends.
That manipulator just inserts a null character to the stream(akin to doing std::cout << '\0';)
More to the point changing replacing this call with std::cout << '\n' changes behaviour and will likely cause UB as this modifier is meant for null-terminating character buffers to pass to C functions.

Double side note the point I made before about how you can turn << "World" << endl into << "World\n" also wouldn't work here as << "World\0" does the same thing as << "World" due to "World" already being a null terminated string.

PiotrZSL added inline comments.Apr 15 2023, 7:11 AM
clang-tools-extra/clang-tidy/performance/AvoidEndlEndsCheck.cpp
28 ↗(On Diff #513878)

Good point, so maybe lets get rid std::ends, probably that could be covered in different check. I personalty for my project got ends and endl on forbidd list, but thats more to avoid control characters in logs, we can probably create new check for that case, and don't change this one.

Sorry for confusion...

AMS21 marked 2 inline comments as done.Apr 17 2023, 12:20 AM
AMS21 updated this revision to Diff 514118.Apr 17 2023, 12:20 AM

Remove std::ends from the check

I've also notices that we don't handle this case

std::endl(std::cout);

Although a rather unusual thing to use, its still valid and has the same problems.

AMS21 edited the summary of this revision. (Show Details)Apr 17 2023, 12:35 AM
AMS21 updated this revision to Diff 514366.Apr 17 2023, 12:41 PM

Handle the function calling case

AMS21 updated this revision to Diff 514367.Apr 17 2023, 12:42 PM

Fix docs

AMS21 updated this revision to Diff 514908.Apr 19 2023, 4:51 AM

Minor code cleanup

Overall looks good, didn't found any bugs, just some potential improvements.

clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.cpp
24

Consider using TK_IgnoreUnlessSpelledInSource like in other checks, this should allow you to remove ignoringImplicit.

25

cxxOperatorCallExpr inherits from callExpr.
Instead of using here expr, use callExpr, that should allow to filter more ast elements.

29

consider ::std::endl unless you plan to support any endl function

31

unless(isExpansionInSystemHeader()) is not needed here, you have it in line 24, thats sufficient.

32

same here consider ::std::endl

33

put this argumentCountIs first, it will be cheaper to check than string compare.

49–52

entire reading of text is redundant, in log you can put simply:
"do not use 'std::endl' with streams; use '\\n' instead"

62

this shouldn't be needed, you already checked that you have 1 argument

64–67

this again looks redundant..., just use std::endl in diag

69–71

move this to line 80, you won't need to assign this to variable

clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.h
17

sync this text with a description in release notes, and first sentence in documentation

clang-tools-extra/docs/clang-tidy/checks/performance/avoid-endl.rst
8

should be '\n', same in documentation, and check comment

41

and std::cout, std::cerr, std::clog, std::wcout, std::wcerr and std::wclog.
simply use wording like "standard C++ streams" and then put some examples in ().

42

Unless std::ios_base::sync_with_stdio is set to false.

54

use 2 spaces instead of 4

AMS21 updated this revision to Diff 515283.Apr 20 2023, 4:43 AM
AMS21 marked 12 inline comments as done.

Implement suggested changes.

AMS21 added inline comments.Apr 20 2023, 4:44 AM
clang-tools-extra/clang-tidy/performance/AvoidEndlCheck.cpp
49–52

I did this before and it was suggested that we match the users spelling. See njames93 comment above.
But honestly I'm fine either way. Hard coding the std::endl definitely makes the code simpler.

Wrap the std::endl in single quotes.

Also it may be wise to spell this how the user has it spelt

std::cout << std::endl; // do not use 'std::endl'
using namespace std;
cout << endl; // do not use 'endl'
62

That's true. I still like to write asserts like this, because the code in this code block requires the argument count to 1. So if something would be changed in the future here, you would hopefully trip the assert and see that the code below needs to be adjusted.

And since this is only checked for debug builds, I don't see the harm here.

PiotrZSL accepted this revision.Apr 20 2023, 5:02 AM

More or less looks good.

This revision is now accepted and ready to land.Apr 20 2023, 5:02 AM

If someone could be so kind as to push this on my behalf, it would be greatly appreciated

This revision was automatically updated to reflect the committed changes.