1 //===--- ReturnConstRefFromParameterCheck.cpp - clang-tidy ----------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 9 #include "ReturnConstRefFromParameterCheck.h" 10 #include "clang/AST/Attrs.inc" 11 #include "clang/AST/Expr.h" 12 #include "clang/ASTMatchers/ASTMatchFinder.h" 13 #include "clang/ASTMatchers/ASTMatchers.h" 14 15 using namespace clang::ast_matchers; 16 17 namespace clang::tidy::bugprone { 18 19 namespace { 20 21 AST_MATCHER(ParmVarDecl, hasLifetimeBoundAttr) { 22 return Node.hasAttr<LifetimeBoundAttr>(); 23 } 24 25 } // namespace 26 27 void ReturnConstRefFromParameterCheck::registerMatchers(MatchFinder *Finder) { 28 const auto DRef = ignoringParens( 29 declRefExpr( 30 to(parmVarDecl(hasType(hasCanonicalType( 31 qualType(lValueReferenceType(pointee( 32 qualType(isConstQualified())))) 33 .bind("type"))), 34 hasDeclContext(functionDecl( 35 equalsBoundNode("func"), 36 hasReturnTypeLoc(loc(qualType( 37 hasCanonicalType(equalsBoundNode("type"))))))), 38 unless(hasLifetimeBoundAttr())) 39 .bind("param"))) 40 .bind("dref")); 41 42 Finder->addMatcher( 43 returnStmt( 44 hasAncestor(functionDecl().bind("func")), 45 hasReturnValue(anyOf( 46 DRef, ignoringParens(conditionalOperator(eachOf( 47 hasTrueExpression(DRef), hasFalseExpression(DRef))))))), 48 this); 49 } 50 51 static bool isSameTypeIgnoringConst(QualType A, QualType B) { 52 return A.getCanonicalType().withConst() == B.getCanonicalType().withConst(); 53 } 54 55 static bool isSameTypeIgnoringConstRef(QualType A, QualType B) { 56 return isSameTypeIgnoringConst(A.getCanonicalType().getNonReferenceType(), 57 B.getCanonicalType().getNonReferenceType()); 58 } 59 60 static bool hasSameParameterTypes(const FunctionDecl &FD, const FunctionDecl &O, 61 const ParmVarDecl &PD) { 62 if (FD.getNumParams() != O.getNumParams()) 63 return false; 64 for (unsigned I = 0, E = FD.getNumParams(); I < E; ++I) { 65 const ParmVarDecl *DPD = FD.getParamDecl(I); 66 const QualType OPT = O.getParamDecl(I)->getType(); 67 if (DPD == &PD) { 68 if (!llvm::isa<RValueReferenceType>(OPT) || 69 !isSameTypeIgnoringConstRef(DPD->getType(), OPT)) 70 return false; 71 } else { 72 if (!isSameTypeIgnoringConst(DPD->getType(), OPT)) 73 return false; 74 } 75 } 76 return true; 77 } 78 79 static const Decl *findRVRefOverload(const FunctionDecl &FD, 80 const ParmVarDecl &PD) { 81 // Actually it would be better to do lookup in caller site. 82 // But in most of cases, overloads of LVRef and RVRef will appear together. 83 // FIXME: 84 // 1. overload in anonymous namespace 85 // 2. forward reference 86 DeclContext::lookup_result LookupResult = 87 FD.getParent()->lookup(FD.getNameInfo().getName()); 88 if (LookupResult.isSingleResult()) { 89 return nullptr; 90 } 91 for (const Decl *Overload : LookupResult) { 92 if (Overload == &FD) 93 continue; 94 if (const auto *O = dyn_cast<FunctionDecl>(Overload)) 95 if (hasSameParameterTypes(FD, *O, PD)) 96 return O; 97 } 98 return nullptr; 99 } 100 101 void ReturnConstRefFromParameterCheck::check( 102 const MatchFinder::MatchResult &Result) { 103 const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("func"); 104 const auto *PD = Result.Nodes.getNodeAs<ParmVarDecl>("param"); 105 const auto *DRef = Result.Nodes.getNodeAs<DeclRefExpr>("dref"); 106 const SourceRange Range = DRef->getSourceRange(); 107 if (Range.isInvalid()) 108 return; 109 110 if (findRVRefOverload(*FD, *PD) != nullptr) 111 return; 112 113 diag(Range.getBegin(), 114 "returning a constant reference parameter may cause use-after-free " 115 "when the parameter is constructed from a temporary") 116 << Range; 117 } 118 119 } // namespace clang::tidy::bugprone 120