2

How can I match against variables in a lambda that are defined outside of the lambda and captured by reference?

The problem I'm trying to solve: I have a database transaction system whose code looks something like this:

std::set<int> values;
auto f = [&](TransactionOp* op) -> Status {
  for (auto v : readColumn("values")) 
     values.insert(v);
  return Ok();
}
Status s = TransactionRunner::Run(f);

The code above has a subtle bug because f doesn't clear values. TransactionRunner::Run can call f multiple times until the transaction succeeds. If f doesn't clear values then values will have garbage values from previous attempts.

I'm writing a clang-tidy check to find bugs like this and stop new ones from being written.

So far I have something like:

cxxRecordDecl(isLambda(), hasDescendant(cxxMethodDecl(returns(hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(hasName("Status")))))), parameterCountIs(1), hasParameter(0, hasType(pointsTo(cxxRecordDecl(hasName("TransactionOp"))))), hasBody(compoundStmt(allOf(hasDescendant(cxxMemberCallExpr(on(declRefExpr(to(varDecl().bind("insertee")))), thisPointerType(cxxRecordDecl(hasName("set"))), callee(cxxMethodDecl(hasName("insert"))))), unless(hasDescendant(cxxMemberCallExpr(on(declRefExpr(to(equalsBoundNode("insertee")))), thisPointerType(cxxRecordDecl(hasName("set"))), callee(cxxMethodDecl(hasName("clear"))))))))))))

The above will find a lambda with the right signature that has a set insertion inside it but no clear call to the same set.

I want it to not trigger on sets declared inside the lambda. So I'd like the matcher to only match if the set is captured by the lambda.

Hesky Fisher
  • 1,145
  • 7
  • 14

1 Answers1

0

I found a solution.

I use a negative matcher (unless) to say that the declaration of the variable is not a descendant of the lambda's body. This doesn't exactly do what I asked for (determine that the variable is a capture) but it will only match captures and globals so it works for my use case.

Here is my whole matcher:

cxxRecordDecl(isLambda(), hasDescendant(cxxMethodDecl(returns(hasUnqualifiedDesugaredType(recordType(hasDeclaration(cxxRecordDecl(hasName("Status")))))), parameterCountIs(1), hasParameter(0, hasType(pointsTo(cxxRecordDecl(hasName("TransactionOp"))))), hasBody(compoundStmt(allOf(hasDescendant(cxxMemberCallExpr(on(declRefExpr(to(varDecl().bind("insertee")))), thisPointerType(cxxRecordDecl(hasName("set"))), callee(cxxMethodDecl(hasName("insert"))))), unless(hasDescendant(cxxMemberCallExpr(on(declRefExpr(to(equalsBoundNode("insertee")))), thisPointerType(cxxRecordDecl(hasName("set"))), callee(cxxMethodDecl(hasName("clear")))))), unless(hasDescendant(decl(equalsBoundNode("insertee"))))))))))

The interesting parts are where I bind the declaration of the set being inserted inside the cxxMethodDecl:

cxxMethodDecl(on(declRefExpr(to(varDecl().bind("insertee")))), ...)

And then say that the declaration is not a descendant of the body (so it must be outside):

unless(hasDescendant(decl(equalsBoundNode("insertee")))))))

Hopefully, this solution can save someone else some time.

Hesky Fisher
  • 1,145
  • 7
  • 14