This is an archive of the discontinued LLVM Phabricator instance.

Add a readability-deleted-default clang-tidy check.
ClosedPublic

Authored by pilki on Apr 11 2016, 5:52 AM.

Diff Detail

Repository
rL LLVM

Event Timeline

pilki updated this revision to Diff 53229.Apr 11 2016, 5:52 AM
pilki retitled this revision from to Add a readability-deleted-default clang-tidy check..
pilki updated this object.
pilki added a reviewer: alexfh.
pilki added a subscriber: cfe-commits.
alexfh added inline comments.Apr 11 2016, 7:12 AM
clang-tidy/readability/DeletedDefaultCheck.cpp
26 ↗(On Diff #53229)

unless(isInTemplateInstantiation()) is the canonical way to do this.

30 ↗(On Diff #53229)

I think, we need at most two matchers here: one for constructors and one for assignment operators. We could also cram these into a single matcher. I also suggest to combine the diag() calls to a single one using %N or %select{}N format to parametrize the message. Something along the lines of:

Finder->addMatcher(cxxMethodDecl(isDefaulted(), isDeleted(), unless(isImplicit()), unless(isInTemplateInstantiation()),
    anyOf(isCopyAssignmentOperator(), isMoveAssignmentOperator(), cxxConstructorDecl().bind("ctor"))).bind("method"));

...
const auto *Method = Result.Nodes.getNodeAs<CXXMethodDecl>("method");
assert(Method != nullptr);
diag(Method->getLocStart(),
       "this %select{method|constructor}0 is marked '= default' but is actually "
       "implicitly deleted, probably because an instance variable or a base "
       "class is not copyable nor movable; this definition should either be removed "
       "or explicitly marked as '= delete'") << (Result.Nodes.getNodeAs<Decl>("ctor") ? 1 : 0);

If you think that adding "copy", "move" or "default" makes the message any better, this could also be accommodated in this approach.

31 ↗(On Diff #53229)

Alternatively, you can use isDefaulted(), unless(isImplicit()).

test/clang-tidy/readability-deleted-default.cpp
13 ↗(On Diff #53229)

Please specify the message completely once.

alexfh requested changes to this revision.Apr 11 2016, 7:13 AM
alexfh edited edge metadata.
This revision now requires changes to proceed.Apr 11 2016, 7:13 AM
alexfh added inline comments.Apr 11 2016, 8:29 AM
clang-tidy/readability/DeletedDefaultCheck.cpp
82 ↗(On Diff #53229)

nit: CopyAssignment

pilki updated this revision to Diff 53257.Apr 11 2016, 9:14 AM
pilki edited edge metadata.
pilki marked 3 inline comments as done.
pilki added inline comments.
clang-tidy/readability/DeletedDefaultCheck.cpp
30 ↗(On Diff #53229)

I reduced to two matchers and used a common template for the error message. Tell me if it's ok.

31 ↗(On Diff #53229)

What is the advantage? Not writing my own matcher?

pilki marked an inline comment as done.Apr 11 2016, 9:15 AM

I'd consider just making this a compiler warning, perhaps?

I'd consider just making this a compiler warning, perhaps?

I agree that it might be a good idea. However, it doesn't hurt to have this in clang-tidy (at least as a prototype - to figure out details and see how it works on real code) until we have it in Clang. Also, making it a Clang diagnostic might be more difficult, especially without a prior experience.

pilki added a comment.Apr 11 2016, 9:44 AM

I'd consider just making this a compiler warning, perhaps?

I honestly don't feel competent right now to write compiler warnings: clang-tidy has a nice, well defined interface. A compiler warning would force me to dig in the internals of clang. But if you feel strongly about it and give me a couple of pointers (like a diff that adds a similar warning, and maybe a pointer to the general area in the code where I would add it), I can maybe give it a try in the next few days/weeks.

Thank you for addressing the comments!

clang-tidy/readability/DeletedDefaultCheck.cpp
27 ↗(On Diff #53257)

What about this comment?

32 ↗(On Diff #53257)

Yes, using standard tools instead of introducing a new one.

44 ↗(On Diff #53257)

A few suggestions to improve the wording:

  • remove "this"
  • replace the period in the end of the first sentence with a semicolon and use a lower-case letter to start the second part.
  • "You should do X" doesn't sound right in warning messages. I'd use a softer option, for example in passive voice: "this definition should either be removed or explicitly marked as '= delete'"
49 ↗(On Diff #53257)

If the check() method issues a diagnostic in any case, I'd prefer a slightly different pattern to reduce code duplication a bit more:

auto Diag = diag(Constructor->getLocStart(), ".....");
if (x) {
  Diag << "qqq" << "eee";
}
...
70 ↗(On Diff #53257)

nit: No need for this variable.

72 ↗(On Diff #53257)

"marked 'const'" sounds better to me than "marked as const".

I don't feel sufficiently strongly to insist - clang-tidy's mostly outside
my wheelhouse anyway.

As for how to go about it - my rough approach would be to write a small
test case that calls an implicitly-deleted-but-explicitly-defaulted move
op, run it, check the diagnostic text, find that in DiagnosticSemaKinds,
find where the diagnostic identifier is used in the code, set a breakpoint,
break there - then trace back to when/where it got marked as deleted (find
the member function of the AST node in question that is used to mark it as
deleted - break in there, with the condition that 'this ==' the specific
instance you're looking at, etc). Then see if, at the point at which the
thing is marked as deleted, you can check that it's not dependent (not a
template, etc) and emit the appropriate warning.

  • Dave

Please mention this check in docs/ReleaseNotes.rst (in alphabetical order).

pilki updated this revision to Diff 53289.Apr 11 2016, 11:23 AM
pilki edited edge metadata.
pilki marked 5 inline comments as done.

I hope I answered all comments (sorry if I missed some, I'm not yet used to this UI).

I have an open question about isInTemplateInstantiation, and added a test (since I missed templated function)

clang-tidy/readability/DeletedDefaultCheck.cpp
27 ↗(On Diff #53257)

isInTemplateInstantiation() is a matcher on Stmt only (when hasAncestor is a polymorphic matcher). When using it, it does not compile. So I copied the body here. I don't know how bad that is.

alexfh added inline comments.Apr 11 2016, 4:46 PM
clang-tidy/readability/DeletedDefaultCheck.cpp
28 ↗(On Diff #53289)

For decls there is isInstantiated(), which is defined as:

auto IsInstantiation = decl(anyOf(cxxRecordDecl(isTemplateInstantiation()),
                                  functionDecl(isTemplateInstantiation())));
return decl(anyOf(IsInstantiation, hasAncestor(IsInstantiation)));

and should be just what you need.

35 ↗(On Diff #53289)

It's not overly important, but you can further reduce the code by folding the first matcher into the second one (move cxxConstructorDecl() inside anyOf here, since CXXConstructorDecl is a CXXMethodDecl). You can also use just a single id to bind the node and distinguish the constructor using dyn_cast. Then you'll be able to use a single diag() call below and remove the unneeded variables Message and isBadlyDefaulted.

alexfh requested changes to this revision.Apr 11 2016, 4:47 PM
alexfh edited edge metadata.
This revision now requires changes to proceed.Apr 11 2016, 4:47 PM
pilki updated this revision to Diff 53371.Apr 12 2016, 1:28 AM
pilki edited edge metadata.
pilki marked an inline comment as done.
pilki added inline comments.
clang-tidy/readability/DeletedDefaultCheck.cpp
35 ↗(On Diff #53289)

Mmh, I'm not convinced it really helps with readability here. I can do it if you insist though.

alexfh accepted this revision.Apr 12 2016, 3:46 AM
alexfh edited edge metadata.

Looks good. A couple of comments.

I'm happy to commit the patch for you once you answer the comments.

Thank you for the work!

clang-tidy/readability/DeletedDefaultCheck.cpp
36 ↗(On Diff #53371)

I would at least join matchers, since, I believe, it might be more effective that way.

cxxMethodDecl(anyOf(cxxConstructorDecl().bind("constructor"),
                                     isCopyAssignmentOperator(),
                                     isMoveAssignmentOperator()),
                               isBadlyDefaulted)
                     .bind("assignment")

Optionally, you might want to inline the isBadlyDefaulted matcher (without the external allOf matcher).

As for restructuring the check() method, I don't insist.

49 ↗(On Diff #53371)

If you don't feel strongly, I'd mildly suggest to turn if { ... return } to a chain of if/else (also the top-level ones). The code will become denser, but it will be easier to see the whole logic in a glance.

This revision is now accepted and ready to land.Apr 12 2016, 3:46 AM
pilki updated this revision to Diff 53381.Apr 12 2016, 4:41 AM
pilki edited edge metadata.
pilki marked an inline comment as done.
pilki added inline comments.
clang-tidy/readability/DeletedDefaultCheck.cpp
36 ↗(On Diff #53371)

I'm a bit confused by this suggestion. It will bind to "assignment" even when it is a constructor. It works because we get "constructor" first in check(), but I find the resulting contract between the check() and registerMatchers awkward.

49 ↗(On Diff #53371)

I thought that in the clang codebase, return were preferred to 'else'. Done.

I share David's consideration for making this a compiler warning instead of a clang-tidy check. Perhaps it would make sense to gather some data using this check over some large code bases to see how often it triggers. If it's overly chatty (or we discover interesting usage scenarios), then leaving it as a clang-tidy check may make more sense.

clang-tidy/readability/DeletedDefaultCheck.cpp
38 ↗(On Diff #53381)

This is a bit verbose. How about:

"%0 is explicitly defaulted but implicitly deleted, probably because %1; definition can either be removed or explicitly deleted"

?

47 ↗(On Diff #53381)

"non-static data member" instead of "instance variable"

55 ↗(On Diff #53381)

s/not/neither

pilki updated this revision to Diff 53404.Apr 12 2016, 8:26 AM
pilki marked 3 inline comments as done.
pilki added a comment.Apr 12 2016, 8:29 AM

Fixed all the comment but the one about merging the two matchers.

alexfh added inline comments.Apr 12 2016, 9:08 AM
clang-tidy/readability/DeletedDefaultCheck.cpp
37 ↗(On Diff #53404)

Will it be less confusing to you, if you change "assignment" to something more generic, e.g. "method" or "decl"?

Also, if you find it less readable that way, we can leave it as is for now.

50 ↗(On Diff #53404)

I usually prefer early returns, however, in this case I find it better to make the code compact. This way it's also immediately clear that the code does nothing but mapping certain properties of matched objects to a message.

pilki updated this revision to Diff 53525.Apr 13 2016, 2:59 AM
pilki marked 2 inline comments as done.
pilki added inline comments.
clang-tidy/readability/DeletedDefaultCheck.cpp
37 ↗(On Diff #53404)

I implemented the requested changes

Thank you for addressing the comments!

I'll commit the patch for you.

The patch doesn't apply cleanly. Please rebase it against current HEAD.

pilki updated this revision to Diff 53529.Apr 13 2016, 3:48 AM

I did svn up and reuploaded the diff. I did not see any changes on files I touched, so I'm not sure it worked.

Something is weird with your patch: it has names starting with a space (note two spaces between "Index:" and "clang-tidy/..."):

Index:  clang-tidy/readability/CMakeLists.txt
===================================================================
---  clang-tidy/readability/CMakeLists.txt
+++  clang-tidy/readability/CMakeLists.txt
@@ -4,6 +4,7 @@
   AvoidConstParamsInDecls.cpp
   BracesAroundStatementsCheck.cpp
   ContainerSizeEmptyCheck.cpp
+  DeletedDefaultCheck.cpp
   ElseAfterReturnCheck.cpp
   FunctionSizeCheck.cpp
   IdentifierNamingCheck.cpp
Index:  clang-tidy/readability/DeletedDefaultCheck.h
===================================================================

For comparison, a normal diff:
http://reviews.llvm.org/file/data/ldmp5jqxuvect5cwpyer/PHID-FILE-3rnivuajuco4mxhb6nzh/D18962.diff

Index: llvm/trunk/lib/Target/SystemZ/README.txt
===================================================================
--- llvm/trunk/lib/Target/SystemZ/README.txt
+++ llvm/trunk/lib/Target/SystemZ/README.txt
@@ -43,11 +43,6 @@
pilki updated this revision to Diff 53532.Apr 13 2016, 4:05 AM
pilki updated this revision to Diff 53533.Apr 13 2016, 4:14 AM

Sorry, both my 'diff' and my 'svn' command added some magical coloring... It should be good now.

This revision was automatically updated to reflect the committed changes.

Now that there is a stable warning in clang for this check that is enabled without specifying any warning flags, Is there reason to think about retiring this check?

Now that there is a stable warning in clang for this check that is enabled without specifying any warning flags, Is there reason to think about retiring this check?

I'd be fine with that. We may even have the time to get a deprecation note into Clang 12 so that we can remove this code in Clang 13 if we felt so inclined.