1 //===--- FixItHintUtils.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 "FixItHintUtils.h" 10 #include "LexerUtils.h" 11 #include "clang/AST/ASTContext.h" 12 #include "clang/AST/ExprCXX.h" 13 #include "clang/AST/Type.h" 14 #include "clang/Sema/DeclSpec.h" 15 #include "clang/Tooling/FixIt.h" 16 #include <optional> 17 18 namespace clang::tidy::utils::fixit { 19 20 FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) { 21 SourceLocation AmpLocation = Var.getLocation(); 22 auto Token = utils::lexer::getPreviousToken( 23 AmpLocation, Context.getSourceManager(), Context.getLangOpts()); 24 if (!Token.is(tok::unknown)) 25 AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0, 26 Context.getSourceManager(), 27 Context.getLangOpts()); 28 return FixItHint::CreateInsertion(AmpLocation, "&"); 29 } 30 31 static bool isValueType(const Type *T) { 32 return !(isa<PointerType>(T) || isa<ReferenceType>(T) || isa<ArrayType>(T) || 33 isa<MemberPointerType>(T) || isa<ObjCObjectPointerType>(T)); 34 } 35 static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); } 36 static bool isMemberOrFunctionPointer(QualType QT) { 37 return (QT->isPointerType() && QT->isFunctionPointerType()) || 38 isa<MemberPointerType>(QT.getTypePtr()); 39 } 40 41 static bool locDangerous(SourceLocation S) { 42 return S.isInvalid() || S.isMacroID(); 43 } 44 45 static std::optional<SourceLocation> 46 skipLParensBackwards(SourceLocation Start, const ASTContext &Context) { 47 if (locDangerous(Start)) 48 return std::nullopt; 49 50 auto PreviousTokenLParen = [&Start, &Context]() { 51 Token T; 52 T = lexer::getPreviousToken(Start, Context.getSourceManager(), 53 Context.getLangOpts()); 54 return T.is(tok::l_paren); 55 }; 56 57 while (Start.isValid() && PreviousTokenLParen()) 58 Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(), 59 Context.getLangOpts()); 60 61 if (locDangerous(Start)) 62 return std::nullopt; 63 return Start; 64 } 65 66 static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc, 67 StringRef Text) { 68 if (locDangerous(Loc)) 69 return std::nullopt; 70 return FixItHint::CreateInsertion(Loc, Text); 71 } 72 73 // Build a string that can be emitted as FixIt with either a space in before 74 // or after the qualifier, either ' const' or 'const '. 75 static std::string buildQualifier(Qualifiers::TQ Qualifier, 76 bool WhitespaceBefore = false) { 77 if (WhitespaceBefore) 78 return (llvm::Twine(' ') + Qualifiers::fromCVRMask(Qualifier).getAsString()) 79 .str(); 80 return (llvm::Twine(Qualifiers::fromCVRMask(Qualifier).getAsString()) + " ") 81 .str(); 82 } 83 84 static std::optional<FixItHint> changeValue(const VarDecl &Var, 85 Qualifiers::TQ Qualifier, 86 QualifierTarget QualTarget, 87 QualifierPolicy QualPolicy, 88 const ASTContext &Context) { 89 switch (QualPolicy) { 90 case QualifierPolicy::Left: 91 return fixIfNotDangerous(Var.getTypeSpecStartLoc(), 92 buildQualifier(Qualifier)); 93 case QualifierPolicy::Right: 94 std::optional<SourceLocation> IgnoredParens = 95 skipLParensBackwards(Var.getLocation(), Context); 96 97 if (IgnoredParens) 98 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); 99 return std::nullopt; 100 } 101 llvm_unreachable("Unknown QualifierPolicy enum"); 102 } 103 104 static std::optional<FixItHint> changePointerItself(const VarDecl &Var, 105 Qualifiers::TQ Qualifier, 106 const ASTContext &Context) { 107 if (locDangerous(Var.getLocation())) 108 return std::nullopt; 109 110 std::optional<SourceLocation> IgnoredParens = 111 skipLParensBackwards(Var.getLocation(), Context); 112 if (IgnoredParens) 113 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); 114 return std::nullopt; 115 } 116 117 static std::optional<FixItHint> 118 changePointer(const VarDecl &Var, Qualifiers::TQ Qualifier, const Type *Pointee, 119 QualifierTarget QualTarget, QualifierPolicy QualPolicy, 120 const ASTContext &Context) { 121 // The pointer itself shall be marked as `const`. This is always to the right 122 // of the '*' or in front of the identifier. 123 if (QualTarget == QualifierTarget::Value) 124 return changePointerItself(Var, Qualifier, Context); 125 126 // Mark the pointee `const` that is a normal value (`int* p = nullptr;`). 127 if (QualTarget == QualifierTarget::Pointee && isValueType(Pointee)) { 128 // Adding the `const` on the left side is just the beginning of the type 129 // specification. (`const int* p = nullptr;`) 130 if (QualPolicy == QualifierPolicy::Left) 131 return fixIfNotDangerous(Var.getTypeSpecStartLoc(), 132 buildQualifier(Qualifier)); 133 134 // Adding the `const` on the right side of the value type requires finding 135 // the `*` token and placing the `const` left of it. 136 // (`int const* p = nullptr;`) 137 if (QualPolicy == QualifierPolicy::Right) { 138 SourceLocation BeforeStar = lexer::findPreviousTokenKind( 139 Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), 140 tok::star); 141 if (locDangerous(BeforeStar)) 142 return std::nullopt; 143 144 std::optional<SourceLocation> IgnoredParens = 145 skipLParensBackwards(BeforeStar, Context); 146 147 if (IgnoredParens) 148 return fixIfNotDangerous(*IgnoredParens, 149 buildQualifier(Qualifier, true)); 150 return std::nullopt; 151 } 152 } 153 154 if (QualTarget == QualifierTarget::Pointee && Pointee->isPointerType()) { 155 // Adding the `const` to the pointee if the pointee is a pointer 156 // is the same as 'QualPolicy == Right && isValueType(Pointee)'. 157 // The `const` must be left of the last `*` token. 158 // (`int * const* p = nullptr;`) 159 SourceLocation BeforeStar = lexer::findPreviousTokenKind( 160 Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), 161 tok::star); 162 return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true)); 163 } 164 165 return std::nullopt; 166 } 167 168 static std::optional<FixItHint> 169 changeReferencee(const VarDecl &Var, Qualifiers::TQ Qualifier, QualType Pointee, 170 QualifierTarget QualTarget, QualifierPolicy QualPolicy, 171 const ASTContext &Context) { 172 if (QualPolicy == QualifierPolicy::Left && isValueType(Pointee)) 173 return fixIfNotDangerous(Var.getTypeSpecStartLoc(), 174 buildQualifier(Qualifier)); 175 176 SourceLocation BeforeRef = lexer::findPreviousAnyTokenKind( 177 Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), 178 tok::amp, tok::ampamp); 179 std::optional<SourceLocation> IgnoredParens = 180 skipLParensBackwards(BeforeRef, Context); 181 if (IgnoredParens) 182 return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true)); 183 184 return std::nullopt; 185 } 186 187 std::optional<FixItHint> addQualifierToVarDecl(const VarDecl &Var, 188 const ASTContext &Context, 189 Qualifiers::TQ Qualifier, 190 QualifierTarget QualTarget, 191 QualifierPolicy QualPolicy) { 192 assert((QualPolicy == QualifierPolicy::Left || 193 QualPolicy == QualifierPolicy::Right) && 194 "Unexpected Insertion Policy"); 195 assert((QualTarget == QualifierTarget::Pointee || 196 QualTarget == QualifierTarget::Value) && 197 "Unexpected Target"); 198 199 QualType ParenStrippedType = Var.getType().IgnoreParens(); 200 if (isValueType(ParenStrippedType)) 201 return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); 202 203 if (ParenStrippedType->isReferenceType()) 204 return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(), 205 QualTarget, QualPolicy, Context); 206 207 if (isMemberOrFunctionPointer(ParenStrippedType)) 208 return changePointerItself(Var, Qualifier, Context); 209 210 if (ParenStrippedType->isPointerType()) 211 return changePointer(Var, Qualifier, 212 ParenStrippedType->getPointeeType().getTypePtr(), 213 QualTarget, QualPolicy, Context); 214 215 if (ParenStrippedType->isArrayType()) { 216 const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe(); 217 assert(AT && "Did not retrieve array element type for an array."); 218 219 if (isValueType(AT)) 220 return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); 221 222 if (AT->isPointerType()) 223 return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(), 224 QualTarget, QualPolicy, Context); 225 } 226 227 return std::nullopt; 228 } 229 230 bool areParensNeededForStatement(const Stmt &Node) { 231 if (isa<ParenExpr>(&Node)) 232 return false; 233 234 if (isa<clang::BinaryOperator>(&Node) || isa<UnaryOperator>(&Node)) 235 return true; 236 237 if (isa<clang::ConditionalOperator>(&Node) || 238 isa<BinaryConditionalOperator>(&Node)) 239 return true; 240 241 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&Node)) { 242 switch (Op->getOperator()) { 243 case OO_PlusPlus: 244 [[fallthrough]]; 245 case OO_MinusMinus: 246 return Op->getNumArgs() != 2; 247 case OO_Call: 248 [[fallthrough]]; 249 case OO_Subscript: 250 [[fallthrough]]; 251 case OO_Arrow: 252 return false; 253 default: 254 return true; 255 }; 256 } 257 258 if (isa<CStyleCastExpr>(&Node)) 259 return true; 260 261 return false; 262 } 263 264 // Return true if expr needs to be put in parens when it is an argument of a 265 // prefix unary operator, e.g. when it is a binary or ternary operator 266 // syntactically. 267 static bool needParensAfterUnaryOperator(const Expr &ExprNode) { 268 if (isa<clang::BinaryOperator>(&ExprNode) || 269 isa<clang::ConditionalOperator>(&ExprNode)) { 270 return true; 271 } 272 if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) { 273 return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && 274 Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && 275 Op->getOperator() != OO_Subscript; 276 } 277 return false; 278 } 279 280 // Format a pointer to an expression: prefix with '*' but simplify 281 // when it already begins with '&'. Return empty string on failure. 282 std::string formatDereference(const Expr &ExprNode, const ASTContext &Context) { 283 if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) { 284 if (Op->getOpcode() == UO_AddrOf) { 285 // Strip leading '&'. 286 return std::string( 287 tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(), Context)); 288 } 289 } 290 StringRef Text = tooling::fixit::getText(ExprNode, Context); 291 292 if (Text.empty()) 293 return {}; 294 295 // Remove remaining '->' from overloaded operator call 296 Text.consume_back("->"); 297 298 // Add leading '*'. 299 if (needParensAfterUnaryOperator(ExprNode)) { 300 return (llvm::Twine("*(") + Text + ")").str(); 301 } 302 return (llvm::Twine("*") + Text).str(); 303 } 304 305 } // namespace clang::tidy::utils::fixit 306