Clang AST Matchers: как сопоставить захваченные лямбда-переменные?

Как я могу сопоставить переменные в лямбда-выражении, которые определены вне лямбда-выражения и захвачены по ссылке?

Проблема, которую я пытаюсь решить: у меня есть система транзакций базы данных, код которой выглядит примерно так:

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

В приведенном выше коде есть небольшая ошибка, потому что f не очищает значения. TransactionRunner::Run может вызывать f несколько раз, пока транзакция не завершится успешно. Если f не очищает значения, тогда значения будут иметь мусорные значения из предыдущих попыток.

Я пишу аккуратную проверку, чтобы найти такие ошибки и предотвратить появление новых.

Пока у меня есть что-то вроде:

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"))))))))))))

В приведенном выше примере будет найдена лямбда с правильной подписью, внутри которой есть вставка набора, но нет явного вызова того же набора.

Я хочу, чтобы он не запускался для наборов, объявленных внутри лямбды. Поэтому я бы хотел, чтобы сопоставитель совпадал только в том случае, если набор захвачен лямбдой.


person Hesky Fisher    schedule 10.04.2019    source источник


Ответы (1)


Я нашел решение.

Я использую отрицательный сопоставитель (если), чтобы сказать, что объявление переменной не является потомком тела лямбды. Это не совсем то, о чем я просил (определить, что переменная является захватом), но он будет соответствовать только захватам и глобальным переменным, поэтому он работает для моего варианта использования.

Вот весь мой матчер:

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"))))))))))

Интересные части — это то, где я привязываю объявление набора, вставляемого внутрь cxxMethodDecl:

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

А затем сказать, что объявление не является потомком тела (поэтому оно должно быть снаружи):

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

Надеюсь, это решение может сэкономить кому-то время.

person Hesky Fisher    schedule 10.04.2019