Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[clang-tidy] Add support for lambdas in cppcoreguidelines-owning-memory #77246

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,17 @@ using namespace clang::ast_matchers::internal;

namespace clang::tidy::cppcoreguidelines {

namespace {
AST_MATCHER_P(LambdaExpr, hasCallOperator, Matcher<CXXMethodDecl>,
InnerMatcher) {
return InnerMatcher.matches(*Node.getCallOperator(), Finder, Builder);
}

AST_MATCHER_P(LambdaExpr, hasLambdaBody, Matcher<Stmt>, InnerMatcher) {
return InnerMatcher.matches(*Node.getBody(), Finder, Builder);
}
} // namespace

void OwningMemoryCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
Options.store(Opts, "LegacyResourceProducers", LegacyResourceProducers);
Options.store(Opts, "LegacyResourceConsumers", LegacyResourceConsumers);
Expand Down Expand Up @@ -55,6 +66,8 @@ void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) {
CreatesLegacyOwner, LegacyOwnerCast);

const auto ConsideredOwner = eachOf(IsOwnerType, CreatesOwner);
const auto ScopeDeclaration = anyOf(translationUnitDecl(), namespaceDecl(),
recordDecl(), functionDecl());

// Find delete expressions that delete non-owners.
Finder->addMatcher(
Expand Down Expand Up @@ -144,13 +157,51 @@ void OwningMemoryCheck::registerMatchers(MatchFinder *Finder) {
.bind("bad_owner_creation_parameter"))),
this);

auto IsNotInSubLambda = stmt(
hasAncestor(
stmt(anyOf(equalsBoundNode("body"), lambdaExpr())).bind("scope")),
hasAncestor(stmt(equalsBoundNode("scope"), equalsBoundNode("body"))));

// Matching on functions, that return an owner/resource, but don't declare
// their return type as owner.
Finder->addMatcher(
functionDecl(hasDescendant(returnStmt(hasReturnValue(ConsideredOwner))
.bind("bad_owner_return")),
unless(returns(qualType(hasDeclaration(OwnerDecl)))))
.bind("function_decl"),
functionDecl(
decl().bind("function_decl"),
hasBody(
stmt(stmt().bind("body"),
hasDescendant(
returnStmt(hasReturnValue(ConsideredOwner),
// Ignore sub-lambda expressions
IsNotInSubLambda,
// Ignore sub-functions
hasAncestor(functionDecl().bind("context")),
hasAncestor(functionDecl(
equalsBoundNode("context"),
equalsBoundNode("function_decl"))))
.bind("bad_owner_return")))),
returns(qualType(unless(hasDeclaration(OwnerDecl))).bind("result"))),
this);

// Matching on lambdas, that return an owner/resource, but don't declare
// their return type as owner.
Finder->addMatcher(
lambdaExpr(
hasAncestor(decl(ScopeDeclaration).bind("scope-decl")),
hasLambdaBody(
stmt(stmt().bind("body"),
hasDescendant(
returnStmt(
hasReturnValue(ConsideredOwner),
// Ignore sub-lambdas
IsNotInSubLambda,
// Ignore sub-functions
hasAncestor(decl(ScopeDeclaration).bind("context")),
hasAncestor(decl(equalsBoundNode("context"),
equalsBoundNode("scope-decl"))))
.bind("bad_owner_return")))),
hasCallOperator(returns(
qualType(unless(hasDeclaration(OwnerDecl))).bind("result"))))
.bind("lambda"),
this);

// Match on classes that have an owner as member, but don't declare a
Expand Down Expand Up @@ -329,7 +380,7 @@ bool OwningMemoryCheck::handleReturnValues(const BoundNodes &Nodes) {
// Function return statements, that are owners/resources, but the function
// declaration does not declare its return value as owner.
const auto *BadReturnType = Nodes.getNodeAs<ReturnStmt>("bad_owner_return");
const auto *Function = Nodes.getNodeAs<FunctionDecl>("function_decl");
const auto *ResultType = Nodes.getNodeAs<QualType>("result");

// Function return values, that should be owners but aren't.
if (BadReturnType) {
Expand All @@ -338,8 +389,9 @@ bool OwningMemoryCheck::handleReturnValues(const BoundNodes &Nodes) {
diag(BadReturnType->getBeginLoc(),
"returning a newly created resource of "
"type %0 or 'gsl::owner<>' from a "
"function whose return type is not 'gsl::owner<>'")
<< Function->getReturnType() << BadReturnType->getSourceRange();
"%select{function|lambda}1 whose return type is not 'gsl::owner<>'")
<< *ResultType << (Nodes.getNodeAs<Expr>("lambda") != nullptr)
<< BadReturnType->getSourceRange();

// FIXME: Rewrite the return type as 'gsl::owner<OriginalType>'
return true;
Expand Down
4 changes: 4 additions & 0 deletions clang-tools-extra/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ Changes in existing checks
<clang-tidy/checks/cppcoreguidelines/missing-std-forward>` check by no longer
giving false positives for deleted functions.

- Improved :doc:`cppcoreguidelines-owning-memory
<clang-tidy/checks/cppcoreguidelines/owning-memory>` check to properly handle
return type in lambdas and in nested functions.

- Cleaned up :doc:`cppcoreguidelines-prefer-member-initializer
<clang-tidy/checks/cppcoreguidelines/prefer-member-initializer>`
by removing enforcement of rule `C.48
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -395,3 +395,109 @@ namespace PR63994 {
// CHECK-NOTES: [[@LINE-1]]:5: warning: returning a newly created resource of type 'A *' or 'gsl::owner<>' from a function whose return type is not 'gsl::owner<>'
}
}

namespace PR59389 {
struct S {
S();
S(int);

int value = 1;
};

void testLambdaInFunctionNegative() {
const auto MakeS = []() -> ::gsl::owner<S*> {
return ::gsl::owner<S*>{new S{}};
};
}

void testLambdaInFunctionPositive() {
const auto MakeS = []() -> S* {
return ::gsl::owner<S*>{new S{}};
// CHECK-NOTES: [[@LINE-1]]:7: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a lambda whose return type is not 'gsl::owner<>'
};
}

void testFunctionInFunctionNegative() {
struct C {
::gsl::owner<S*> test() {
return ::gsl::owner<S*>{new S{}};
}
};
}

void testFunctionInFunctionPositive() {
struct C {
S* test() {
return ::gsl::owner<S*>{new S{}};
// CHECK-NOTES: [[@LINE-1]]:9: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a function whose return type is not 'gsl::owner<>'
}
};
}

::gsl::owner<S*> testReverseLambdaNegative() {
const auto MakeI = [] -> int { return 5; };
return ::gsl::owner<S*>{new S(MakeI())};
}

S* testReverseLambdaPositive() {
const auto MakeI = [] -> int { return 5; };
return ::gsl::owner<S*>{new S(MakeI())};
// CHECK-NOTES: [[@LINE-1]]:5: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a function whose return type is not 'gsl::owner<>'
}

::gsl::owner<S*> testReverseFunctionNegative() {
struct C {
int test() { return 5; }
};
return ::gsl::owner<S*>{new S(C().test())};
}

S* testReverseFunctionPositive() {
struct C {
int test() { return 5; }
};
return ::gsl::owner<S*>{new S(C().test())};
// CHECK-NOTES: [[@LINE-1]]:5: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a function whose return type is not 'gsl::owner<>'
}

void testLambdaInLambdaNegative() {
const auto MakeS = []() -> ::gsl::owner<S*> {
const auto MakeI = []() -> int { return 5; };
return ::gsl::owner<S*>{new S(MakeI())};
};
}

void testLambdaInLambdaPositive() {
const auto MakeS = []() -> S* {
const auto MakeI = []() -> int { return 5; };
return ::gsl::owner<S*>{new S(MakeI())};
// CHECK-NOTES: [[@LINE-1]]:7: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a lambda whose return type is not 'gsl::owner<>'
};
}

void testLambdaInLambdaWithDoubleReturns() {
const auto MakeS = []() -> S* {
const auto MakeS2 = []() -> S* {
return ::gsl::owner<S*>{new S(1)};
// CHECK-NOTES: [[@LINE-1]]:9: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a lambda whose return type is not 'gsl::owner<>' [cppcoreguidelines-owning-memory]
};
return ::gsl::owner<S*>{new S(2)};
// CHECK-NOTES: [[@LINE-1]]:7: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a lambda whose return type is not 'gsl::owner<>'
};
}

void testReverseLambdaInLambdaNegative() {
const auto MakeI = []() -> int {
const auto MakeS = []() -> ::gsl::owner<S*> { return new S(); };
return 5;
};
}

void testReverseLambdaInLambdaPositive() {
const auto MakeI = []() -> int {
const auto MakeS = []() -> S* { return new S(); };
// CHECK-NOTES: [[@LINE-1]]:39: warning: returning a newly created resource of type 'S *' or 'gsl::owner<>' from a lambda whose return type is not 'gsl::owner<>'
return 5;
};
}
}
Loading