This is an archive of the discontinued LLVM Phabricator instance.

[ASTMatchers] Fix `hasBody` for the descendants of `FunctionDecl`
ClosedPublic

Authored by baloghadamsoftware on Sep 11 2020, 10:02 AM.

Details

Summary

AST Matcher hasBody is a polymorphic matcher that behaves differently for loop statements and function declarations. The main difference is the for functions declarations it does not only call FunctionDecl::getBody() but first checks whether the declaration in question is that specific declaration which has the body by calling FunctionDecl::doesThisDeclarationHaveABody(). This is achieved by specialization of the template GetBodyMatcher. Unfortunately template specializations do not catch the descendants of the class for which the template is specialized. Therefore it does not work correcly for the descendants of FunctionDecl, such as CXXMethodDecl, CXXConstructorDecl, CXXDestructorDecl etc. This patch fixes this issue by using a template metaprogram.

Diff Detail

Event Timeline

baloghadamsoftware requested review of this revision.Sep 11 2020, 10:02 AM

That is part of "etc.". It works for all current and future descendants of FunctionDecl now.

Thank you for looking into it.

martong accepted this revision.Sep 14 2020, 2:14 AM

Unfortunately template specializations do not catch the descendants of the class for which the template is specialized. Therefore it does not work correcly for the descendants of FunctionDecl, such as CXXMethodDecl, CXXConstructorDecl, CXXDestructorDecl etc. This patch fixes this issue by using a template metaprogram.

Yes, implicit type conversions are not considered during template argument deduction.

First I was thinking about why don't we just simply specialize for all subclasses of FunctionDecl (CXXMethodDecl, CXXConstructorDecl, CXXDestructorDecl, etc) ?
But then I realized, your solution with enable_if is more generic and will work even if a new subclass is added. Looks good to me.

clang/unittests/ASTMatchers/ASTMatchersTraversalTest.cpp
1626

FindsBodyOfFunctionDeclSubclasses ?

1629

"met" -> "method" ?

This revision is now accepted and ready to land.Sep 14 2020, 2:14 AM

Can you expand on what is wrong currently for FunctionDecl descendants? Would the new test FindsBodyOfFunctionChildren fail with the current implementation?

Can you expand on what is wrong currently for FunctionDecl descendants? Would the new test FindsBodyOfFunctionChildren fail with the current implementation?

Yes, exactly. They return 2 findings instead of 1 because the matcher matches both the forward declaration and the definition. The cause for this is that template specializations do not work for the descendants of the class for which is template specialized. Instead, the generic version is used. Thus for the children of FunctionDecl the original GetBodyMatcher is instantiated which does not check whether doesThisDeclarationHaveABody() but returns a body instead which may be bound to another declaration of the same function in the declaration chain. This is obviously wrong.

Can you expand on what is wrong currently for FunctionDecl descendants? Would the new test FindsBodyOfFunctionChildren fail with the current implementation?

Yes, exactly. They return 2 findings instead of 1 because the matcher matches both the forward declaration and the definition. The cause for this is that template specializations do not work for the descendants of the class for which is template specialized. Instead, the generic version is used. Thus for the children of FunctionDecl the original GetBodyMatcher is instantiated which does not check whether doesThisDeclarationHaveABody() but returns a body instead which may be bound to another declaration of the same function in the declaration chain. This is obviously wrong.

Thank you for clarifying. I might nitpick that it's not "obviously wrong": I'm actually writing code now that depends on exactly that behavior -- I don't care where the method decl body is, as long as it's visible in the current TU. That said, I think you're right in wanting FunctionDecl's behavior to align with that of its derived classes and I think it better to behave that way than how it currently behaves. But, we should have an alternative available in case this change breaks anyone else depending on the current behavior. Is there a different matcher with the semantics I've described?

Also, please clarify the comments in the header for this matcher. The current description is quite unclear for functions: "Matches a 'for', 'while', 'do while' statement or a function
definition that has a given body."

Thank you for clarifying. I might nitpick that it's not "obviously wrong": I'm actually writing code now that depends on exactly that behavior -- I don't care where the method decl body is, as long as it's visible in the current TU. That said, I think you're right in wanting FunctionDecl's behavior to align with that of its derived classes and I think it better to behave that way than how it currently behaves. But, we should have an alternative available in case this change breaks anyone else depending on the current behavior. Is there a different matcher with the semantics I've described?

Also, please clarify the comments in the header for this matcher. The current description is quite unclear for functions: "Matches a 'for', 'while', 'do while' statement or a function
definition that has a given body."

We must decide about the namings. If we want to be in sync with the methods in FunctionDecl, then we keep hasBody() as is, but remove the template specialization, and create a new doesThisDeclarationHaveABody(). However, this involves changing existing checks. The other solution is to fix hasBody() like in this patch, and find a new name for the other behavior, such as e.g. hasBodySomewhere() or something similar.

The comments in the header are really poorly written, but there is the word definition which means that the matcher matches function definitions and not declarations. This is surely to be rephrased.

We must decide about the namings. If we want to be in sync with the methods in FunctionDecl, then we keep hasBody() as is, but remove the template specialization, and create a new doesThisDeclarationHaveABody(). However, this involves changing existing checks. The other solution is to fix hasBody() like in this patch, and find a new name for the other behavior, such as e.g. hasBodySomewhere() or something similar.

Agreed. I think we should stick with your approach, since it involves fewer changes and is consistent with the AST API itself. As for naming, hasBodySomewhere() works, but other options: hasSomeBody, hasAnyBody, hasBodyForSomeDecl.

FWIW, I checked our codebase and we definitely have a handful of uses that rely on current behavior. So, I think its important to add the new matcher before we commit this change.

The comments in the header are really poorly written, but there is the word definition which means that the matcher matches function definitions and not declarations. This is surely to be rephrased.

+1

Thanks!

Created another matcher hasAnyBody, and updated the documentation of the matchers.

ymandel accepted this revision.Sep 15 2020, 9:44 AM

Thanks, this looks great! But, can you also please update https://github.com/llvm/llvm-project/blob/master/clang-tools-extra/clang-tidy/modernize/UseEqualsDeleteCheck.cpp#L39, since it depends on the current semantics?

Checker modernize-use-equals-delete adjusted.