This is an archive of the discontinued LLVM Phabricator instance.

[clang-tidy] misc-move-const-arg: Detect if result of std::move() is being passed as a const ref argument
ClosedPublic

Authored by mboehme on Jun 10 2016, 2:56 AM.

Details

Summary

Conceptually, this is very close to the existing functionality of misc-move-const-arg, which is why I'm adding it here and not creating a new check. For example, for a type A that is both movable and copyable, this

const A a1;
A a2(std::move(a1));

is not only a case where a const argument is being passed to std::move(), but the result of std::move() is also being passed as a const reference (due to overload resolution).

The new check typically triggers (exclusively) in cases where people think they're dealing with a movable type, but in fact the type is not movable.

Diff Detail

Repository
rL LLVM

Event Timeline

mboehme updated this revision to Diff 60327.Jun 10 2016, 2:56 AM
mboehme retitled this revision from to [clang-tidy] misc-move-const-arg: Detect if result of std::move() is being passed as a const ref argument.
mboehme updated this object.
mboehme added reviewers: alexfh, hokein.
mboehme added a subscriber: cfe-commits.
aaron.ballman added a subscriber: aaron.ballman.
aaron.ballman added inline comments.
clang-tidy/misc/MoveConstantArgumentCheck.cpp
20 ↗(On Diff #60327)

Instead of an unnamed namespace, you should use static functions per our coding standards (http://llvm.org/docs/CodingStandards.html#anonymous-namespaces).

22 ↗(On Diff #60327)

Should pass Diag by reference rather than by pointer.

26 ↗(On Diff #60327)

Should only use auto when the type is explicitly spelled out in the initialization (which this is not). Same elsewhere.

alexfh edited edge metadata.Jun 13 2016, 4:59 AM

Thank you for working on this! A few more comments in addition to what Aaron has written.

clang-tidy/misc/MoveConstantArgumentCheck.cpp
22 ↗(On Diff #60327)

I'd prefer the parameter to be named Call instead of TheCallExpr. Would make it more consistent with the rest of clang-tidy code.

37 ↗(On Diff #60327)

Alternatively, this function could return a llvm::SmallVector<FixItHint, 2> (either empty or with the fixes) and the caller could just feed it to the result of diag(). Not much difference though, just pointing to this possibility.

42 ↗(On Diff #60327)

With two lines of implementation and just one caller, this function doesn't seem to add any value.

116 ↗(On Diff #60327)

Is this equivalent to just ignoring all cases where CallMove->getLocStart().isMacroID()? If not, what's the subset of the cases that would be ignored by isMacroID(), but not with the current condition?

mboehme updated this revision to Diff 60506.Jun 13 2016, 5:26 AM
mboehme edited edge metadata.
mboehme updated this revision to Diff 60507.Jun 13 2016, 5:28 AM
mboehme updated this revision to Diff 60523.Jun 13 2016, 7:36 AM
mboehme marked 4 inline comments as done.
mboehme added inline comments.Jun 13 2016, 7:40 AM
clang-tidy/misc/MoveConstantArgumentCheck.cpp
37 ↗(On Diff #60327)

Thanks for the suggestion! If it's OK with you, though, I'd like to keep this as-is -- I feel it makes the call-site clearer.

42 ↗(On Diff #60327)

I've inlined the function. (I think I originally had two places where it got called, so I factored it out, but then one of those places went away.)

116 ↗(On Diff #60327)

Actually, having looked into this some more, it seems that the macro cases are already handled by the check "if (!FileMoveRange.isValid())" above -- so I'm deleting this entire block of code.

(I think I added the isMacroBodyExpansion() and isMacroArgExpansion() checks first before I moved the FileMoveRange.isValid() check, which then made them unnecessary.)

aaron.ballman added inline comments.Jun 13 2016, 11:10 AM
test/clang-tidy/misc-move-const-arg.cpp
75–76 ↗(On Diff #60523)

This type isn't non-moveable. For that, you need to explicitly delete the move constructor. Perhaps a better name is NonMoveConstructible?

Also, no need for the public access specifier.

116 ↗(On Diff #60523)

No need for the public access specifier.

mboehme updated this revision to Diff 60671.Jun 14 2016, 6:11 AM
mboehme added inline comments.Jun 14 2016, 6:21 AM
test/clang-tidy/misc-move-const-arg.cpp
75–76 ↗(On Diff #60523)

This type isn't non-moveable. For that, you need to explicitly delete the move constructor.

Can you expand on this?

The standard says: "If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if - X does not have a user-declared copy constructor [...]" (12.8/9).

Because I'm declaring a copy constructor, I would thus have expected not to get an implicitly-declared move constructor. Where am I going wrong here?

Perhaps a better name is NonMoveConstructible?

But the type _is_ move-constructible (in the sense of std::is_move_constructible<>).

Also, no need for the public access specifier.

Oops -- thanks for catching this.

Actually, I meant to make this a class -- not sure how the "struct" slipped in there.

116 ↗(On Diff #60523)

Thanks for cathching this -- as above, I meant to make this a class.

aaron.ballman added inline comments.Jun 14 2016, 6:54 AM
test/clang-tidy/misc-move-const-arg.cpp
75–76 ↗(On Diff #60671)

Can you expand on this?

The standard says: "If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if - X does not have a user-declared copy constructor [...]" (12.8/9).

Because I'm declaring a copy constructor, I would thus have expected not to get an implicitly-declared move constructor. Where am I going wrong here?

Move operations are optimized versions of a copy operation, so a failed move operation can fall back to perform a copy operation instead and achieve the same operational semantics but with slightly worse performance. Because you have a copy constructor, you do not get an implicitly-declared move constructor (so that's correct), but that doesn't mean the type cannot be used in a context where the user expects a move (i.e., calling std::move() on it) -- the operation just falls back on the copy constructor.

Basically, there's an operational difference between not having the implicitly-declared move constructor and having a deleted move constructor. In the former, fallback to copy happens and in the latter you get a diagnostic. So when I hear "non-moveable type", my brain assumes you mean "get a diagnostic when you try to move."

mboehme updated this revision to Diff 60686.Jun 14 2016, 7:35 AM
mboehme added inline comments.Jun 14 2016, 7:40 AM
test/clang-tidy/misc-move-const-arg.cpp
75–76 ↗(On Diff #60671)

Ah, now I see what you mean.

I've renamed "NonMoveable" to "NoMoveSemantics" (and "Moveable" to "MoveSemantics" for consistency). What do you think?

(I don't like NonMoveConstructible because it's prone to the same ambiguity -- or even more so, because std::is_move_constructible<> uses exactly the same term and the type is move constructible in the sense of that trait.)

aaron.ballman accepted this revision.Jun 16 2016, 7:08 AM
aaron.ballman edited edge metadata.

LGTM, but you should wait for @alexfh to sign off as well since he had some comments.

This revision is now accepted and ready to land.Jun 16 2016, 7:08 AM
alexfh accepted this revision.Jun 16 2016, 7:22 AM
alexfh edited edge metadata.

LG. Thanks!

BTW, I'll commit the patch for you.

This revision was automatically updated to reflect the committed changes.