Skip to content

Commit

Permalink
[clang-tidy] Add support for lambdas in cppcoreguidelines-owning-memo…
Browse files Browse the repository at this point in the history
…ry (#77246)

Implement proper support for lambdas and sub-functions/classes.
Moved from https://reviews.llvm.org/D157285

Fixes: #59389
  • Loading branch information
PiotrZSL committed Mar 19, 2024
1 parent 3ff67d8 commit 0a40f5d
Show file tree
Hide file tree
Showing 3 changed files with 169 additions and 7 deletions.
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
Expand Up @@ -165,6 +165,10 @@ Changes in existing checks
giving false positives for deleted functions and fix false negative when some
parameters are forwarded, but other aren't.

- 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
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;
};
}
}

0 comments on commit 0a40f5d

Please sign in to comment.