================
@@ -21,25 +22,117 @@ AST_MATCHER(LambdaExpr, hasCoroutineBody) {
}
AST_MATCHER(LambdaExpr, hasCaptures) { return Node.capture_size() != 0U; }
+
+AST_MATCHER(LambdaExpr, hasDeducingThis) {
+ return Node.getCallOperator()->isExplicitObjectMemberFunction();
+}
} // namespace
+AvoidCapturingLambdaCoroutinesCheck::AvoidCapturingLambdaCoroutinesCheck(
+ StringRef Name, ClangTidyContext *Context)
+ : ClangTidyCheck(Name, Context),
+ AllowExplicitObjectParameters(
+ Options.get("AllowExplicitObjectParameters", false)) {}
+
void AvoidCapturingLambdaCoroutinesCheck::registerMatchers(
MatchFinder *Finder) {
- Finder->addMatcher(
- lambdaExpr(hasCaptures(), hasCoroutineBody()).bind("lambda"), this);
+ auto Matcher = lambdaExpr(hasCaptures(), hasCoroutineBody());
+
+ if (AllowExplicitObjectParameters)
+ Matcher = lambdaExpr(hasCaptures(), hasCoroutineBody(),
+ unless(hasDeducingThis()));
+
+ Finder->addMatcher(Matcher.bind("lambda"), this);
}
bool AvoidCapturingLambdaCoroutinesCheck::isLanguageVersionSupported(
const LangOptions &LangOpts) const {
return LangOpts.CPlusPlus20;
}
+void AvoidCapturingLambdaCoroutinesCheck::storeOptions(
+ ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AllowExplicitObjectParameters",
+ AllowExplicitObjectParameters);
+}
+
void AvoidCapturingLambdaCoroutinesCheck::check(
const MatchFinder::MatchResult &Result) {
const auto *MatchedLambda = Result.Nodes.getNodeAs<LambdaExpr>("lambda");
- diag(MatchedLambda->getExprLoc(),
- "coroutine lambda may cause use-after-free, avoid captures or ensure "
- "lambda closure object has guaranteed lifetime");
+
+ if (AllowExplicitObjectParameters && getLangOpts().CPlusPlus23) {
+ const CXXMethodDecl *Call = MatchedLambda->getCallOperator();
+ const bool HasExplicitParams = MatchedLambda->hasExplicitParameters();
+
+ auto DiagBuilder =
+ diag(MatchedLambda->getExprLoc(),
+ "coroutine lambda with captures may cause use-after-free; use "
+ "'this auto' as the first parameter to move captures into the "
+ "coroutine frame");
+
+ if (HasExplicitParams) {
+ const bool HasParams = !Call->param_empty();
+ if (HasParams) {
+ const ParmVarDecl *FirstParam = Call->parameters().front();
+ DiagBuilder << FixItHint::CreateInsertion(FirstParam->getBeginLoc(),
+ "this auto, ");
+ } else {
+ DiagBuilder << FixItHint::CreateInsertion(
+ Call->getFunctionTypeLoc().getRParenLoc(), "this auto");
+ }
+ } else {
+ // No explicit parameter list — insert `(this auto) ` where the
+ // parameter list would go in the grammar:
+ // [captures] <tparams> t-requires front-attr (params)
+ // Start after the template parameter list (including its requires
+ // clause) or the capture list, then skip past any attributes that
+ // appear before the implicit parameter list position.
+ const auto &SM = *Result.SourceManager;
+ const auto &LO = getLangOpts();
+ SourceLocation InsertLoc;
+
+ if (const auto *TPL = MatchedLambda->getTemplateParameterList()) {
+ if (const Expr *RC = TPL->getRequiresClause())
+ InsertLoc = Lexer::getLocForEndOfToken(RC->getEndLoc(), 0, SM, LO);
+ else
+ InsertLoc =
+ Lexer::getLocForEndOfToken(TPL->getRAngleLoc(), 0, SM, LO);
+ } else {
+ InsertLoc = Lexer::getLocForEndOfToken(
+ MatchedLambda->getIntroducerRange().getEnd(), 0, SM, LO);
+ }
+
+ // Skip past any front-attributes. getRange() covers only the
+ // attribute name/arguments, not the enclosing brackets.
+ // Advance past the closing brackets based on the syntax:
+ // `[[attr]]` — 2 tokens (`]]`)
+ // `__attribute__((attr))` — 2 tokens (`))`)
+ // `__declspec(attr)` — 1 token (`)`)
+ // keyword / other — 0 tokens
+ if (Call->hasAttrs()) {
----------------
WillemKauf wrote:
If anyone has any better ideas than having to go the `Lexer` route here, I
would love to hear them... 🙂
https://github.com/llvm/llvm-project/pull/182916
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits