This is an archive of the discontinued LLVM Phabricator instance.

[PATCH] New diagnostic for non-idiomatic copy or move operations (v2)
AbandonedPublic

Authored by aaron.ballman on Dec 11 2015, 10:12 AM.

Details

Reviewers
dblaikie
rsmith
Summary

All copy operations were created equal (as far as the core language is concerned), but some copy operations are more equal than others (as far as the library is concerned). The same is true of move operations. This patch diagnoses copy and move operations (constructors and assignment operators) that are non-idiomatic so that users are not surprised when their copyable type cannot be placed in a standard library container or used with a standard library algorithm.

Idiomatic copy and move operations are (where T is the class type for the object):
Copy Constructor: takes a const T&
Move constructor: takes a T&&
Copy Assignment: takes a const T&, returns a T&, and has no ref-qualifiers
Move Assignment: takes a T&&, returns a T&, and has no ref-qualifiers

This patch does not diagnose *all* non-idiomatic uses because of false positives in practice. Specifically, it does not diagnose when a non-idiomatic operation is overloaded with an idiomatic one (T(const T&); T(T&);) or when there are multiple non-idiomatic operation overloads (T(volatile T&); T(T&);). In both cases, it is reasonable to assume that the user knows they have non-idiomatic signatures. This means that the diagnostic can always be suppressed by either converting the operation into an idiomatic one, or by adding an overload that is deleted (or is private, as in pre-C++11 code bases), depending on the behavior they desire.

One thing this patch does could have are fix-it hints to correct the types involved. However, I cannot find a way to get the full source range for the type, including qualifiers. For a parameter of type 'const volatile T&', the TypeLoc appears to only track the location of T&, but not the qualifiers. Assistance in understanding how to do this would be appreciated.

Diff Detail

Event Timeline

aaron.ballman retitled this revision from to [PATCH] New diagnostic for non-idiomatic copy or move operations (v2).
aaron.ballman updated this object.
aaron.ballman added reviewers: rsmith, dblaikie.
aaron.ballman added a subscriber: cfe-commits.

I should note that the majority of the changes in this patch are to deal with the diagnostic triggering on test cases. I tried to correct the test cases to be idiomatic where it appears the signature of the operation was not the purpose of the test. The other cases either got an expected-warning, or had the flag disabled.

I ran the diagnostic over several code bases, and the outcome was:

LLVM: 0 warnings
Qt: 7206 warnings (most of these are duplicate diagnostics because the signatures are in a header file)
rethinkdb: 34 warnings (same here)
cocos2d: 0 warnings
opencv: 0 warnings

Ping now that the holidays are somewhat over-ish.

rsmith edited edge metadata.Jan 6 2016, 3:21 PM

I'm unconvinced this meets the bar for an on-by-default warning. Your suggested workaround for correct-but-diagnosed code doesn't seem to work: if I have

struct X {
  X(const volatile X&);
};

... adding

X(const X&) = delete;

or a private "normal" copy constructor will break clients of my code that happen to have a non-volatile X object. This will also diagnose the pre-C++11 idiom of:

struct noncopyable {
  noncopyable(noncopyable&);
  void operator=(noncopyable&);
};

... where the references are deliberately references to non-const in order to allow more bugs to be caught at compile time.

I'd also expect that if the user wrote more tokens than would be present in the idiomatic declaration, they probably know what they're doing (so don't reject extra cv-qualifiers and ref-qualifiers).

Can you provide a list of the things this found in Qt and rethinkdb? Is there some pattern in them? Are they bugs?

lib/Sema/SemaDeclCXX.cpp
4924–4929

I don't think it makes sense to warn on this when the qualifier is &. In that case, the user's code is *better* than the normal idiom. And if they used the ref-qualifier &&, perhaps we should just assume they know what they're doing?

I'm unconvinced this meets the bar for an on-by-default warning. Your suggested workaround for correct-but-diagnosed code doesn't seem to work: if I have

struct X {
  X(const volatile X&);
};

... adding

X(const X&) = delete;

or a private "normal" copy constructor will break clients of my code that happen to have a non-volatile X object.

That's a good point. You could use delegating constructors to silence the warning, but that is a bit intrusive (and non-functional for coding targeting older versions of the standard).

This will also diagnose the pre-C++11 idiom of:

struct noncopyable {
  noncopyable(noncopyable&);
  void operator=(noncopyable&);
};

... where the references are deliberately references to non-const in order to allow more bugs to be caught at compile time.

This was intentional, based on discussion. The thought was that (1) these are still copy constructible/assignable (within the context of the class type itself), and (2) they're still not idiomatic. However, I do think that's a bit of a weak argument and would be fine allowing those cases.

I'd also expect that if the user wrote more tokens than would be present in the idiomatic declaration, they probably know what they're doing (so don't reject extra cv-qualifiers and ref-qualifiers).

It would be oddly inconsistent to warn on some non-idiomatic operations, but then fail to warn on other non-idiomatic operations though. However, it may make sense

Can you provide a list of the things this found in Qt and rethinkdb? Is there some pattern in them? Are they bugs?

The Qt issues are things like: http://code.qt.io/cgit/qt/qtbase.git/tree/src/tools/uic/ui4.h?h=dev#n379 However, there are also plenty like:

dom/QualifiedName.h:70:11: warning: non-idiomatic copy assignment operator declaration; consider returning 'T&' instead [-Wnon-idiomatic-copy-move]

const QualifiedName& operator=(const QualifiedName& other) { other.ref(); deref(); m_impl = other.m_impl; return *this; }

Rethink's look more like:

(Sorry, fat fingered the original response.)

I'm unconvinced this meets the bar for an on-by-default warning. Your suggested workaround for correct-but-diagnosed code doesn't seem to work: if I have

struct X {
  X(const volatile X&);
};

... adding

X(const X&) = delete;

or a private "normal" copy constructor will break clients of my code that happen to have a non-volatile X object.

That's a good point. You could use delegating constructors to silence the warning, but that is a bit intrusive (and non-functional for coding targeting older versions of the standard).

This will also diagnose the pre-C++11 idiom of:

struct noncopyable {
  noncopyable(noncopyable&);
  void operator=(noncopyable&);
};

... where the references are deliberately references to non-const in order to allow more bugs to be caught at compile time.

This was intentional, based on discussion. The thought was that (1) these are still copy constructible/assignable (within the context of the class type itself), and (2) they're still not idiomatic. However, I do think that's a bit of a weak argument and would be fine allowing those cases.

I'd also expect that if the user wrote more tokens than would be present in the idiomatic declaration, they probably know what they're doing (so don't reject extra cv-qualifiers and ref-qualifiers).

It would be oddly inconsistent to warn on some non-idiomatic operations, but then fail to warn on other non-idiomatic operations though. However, it may make sense

Can you provide a list of the things this found in Qt and rethinkdb? Is there some pattern in them? Are they bugs?

The Qt issues are things like: http://code.qt.io/cgit/qt/qtbase.git/tree/src/tools/uic/ui4.h?h=dev#n379 However, there are also plenty like:

dom/QualifiedName.h:70:11: warning: non-idiomatic copy assignment operator declaration; consider returning 'T&' instead [-Wnon-idiomatic-copy-move]

const QualifiedName& operator=(const QualifiedName& other) { other.ref(); deref(); m_impl = other.m_impl; return *this; }

Rethink's look more like:
./src/rapidjson/document.h:329:5: error: non-idiomatic copy assignment operator declaration; consider returning 'T&' instead [-Werror,-Wnon-idiomatic-copy-move]

GenericStringRef operator=(const GenericStringRef&);
^

./src/rapidjson/document.h:595:43: error: non-idiomatic copy assignment operator declaration; consider 'const T&' instead [-Werror,-Wnon-idiomatic-copy-move]

GenericValue& operator=(GenericValue& rhs) RAPIDJSON_NOEXCEPT {
                                      ^

Whether the issues pointed out are bugs or not is a bit more subjective as it depends on the usage. Some seems like they aren't (such as Qt's stuff in ui4.h which are old-style "deleted" functions). Others seem like they definitely are, such as what I pointed out from rethinkdb (though rethink also had some old-style deleted functions as well). And others seem questionable, such as Qt's QualifiedName, where it could go either way.

aaron.ballman abandoned this revision.Jun 1 2017, 10:50 AM