| 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 | Location: AmpLocation, SM: Context.getSourceManager(), LangOpts: Context.getLangOpts()); |
| 24 | if (!Token.is(tok::unknown)) |
| 25 | AmpLocation = Lexer::getLocForEndOfToken(Loc: Token.getLocation(), Offset: 0, |
| 26 | SM: Context.getSourceManager(), |
| 27 | LangOpts: Context.getLangOpts()); |
| 28 | return FixItHint::CreateInsertion(InsertionLoc: AmpLocation, Code: "&" ); |
| 29 | } |
| 30 | |
| 31 | static bool isValueType(const Type *T) { |
| 32 | return !(isa<PointerType>(Val: T) || isa<ReferenceType>(Val: T) || isa<ArrayType>(Val: T) || |
| 33 | isa<MemberPointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T)); |
| 34 | } |
| 35 | static bool isValueType(QualType QT) { return isValueType(T: QT.getTypePtr()); } |
| 36 | static bool isMemberOrFunctionPointer(QualType QT) { |
| 37 | return (QT->isPointerType() && QT->isFunctionPointerType()) || |
| 38 | isa<MemberPointerType>(Val: 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(S: Start)) |
| 48 | return std::nullopt; |
| 49 | |
| 50 | auto PreviousTokenLParen = [&Start, &Context]() { |
| 51 | Token T; |
| 52 | T = lexer::getPreviousToken(Location: Start, SM: Context.getSourceManager(), |
| 53 | LangOpts: Context.getLangOpts()); |
| 54 | return T.is(K: tok::l_paren); |
| 55 | }; |
| 56 | |
| 57 | while (Start.isValid() && PreviousTokenLParen()) |
| 58 | Start = lexer::findPreviousTokenStart(Start, SM: Context.getSourceManager(), |
| 59 | LangOpts: Context.getLangOpts()); |
| 60 | |
| 61 | if (locDangerous(S: Start)) |
| 62 | return std::nullopt; |
| 63 | return Start; |
| 64 | } |
| 65 | |
| 66 | static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc, |
| 67 | StringRef Text) { |
| 68 | if (locDangerous(S: Loc)) |
| 69 | return std::nullopt; |
| 70 | return FixItHint::CreateInsertion(InsertionLoc: Loc, Code: 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(CVR: Qualifier).getAsString()) |
| 79 | .str(); |
| 80 | return (llvm::Twine(Qualifiers::fromCVRMask(CVR: 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(Loc: *IgnoredParens, Text: 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(Loc: *IgnoredParens, Text: 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(T: 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 | Start: Var.getLocation(), SM: Context.getSourceManager(), LangOpts: Context.getLangOpts(), |
| 140 | TK: tok::star); |
| 141 | if (locDangerous(S: BeforeStar)) |
| 142 | return std::nullopt; |
| 143 | |
| 144 | std::optional<SourceLocation> IgnoredParens = |
| 145 | skipLParensBackwards(Start: BeforeStar, Context); |
| 146 | |
| 147 | if (IgnoredParens) |
| 148 | return fixIfNotDangerous(Loc: *IgnoredParens, |
| 149 | Text: buildQualifier(Qualifier, WhitespaceBefore: 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 | Start: Var.getLocation(), SM: Context.getSourceManager(), LangOpts: Context.getLangOpts(), |
| 161 | TK: tok::star); |
| 162 | return fixIfNotDangerous(Loc: BeforeStar, Text: buildQualifier(Qualifier, WhitespaceBefore: 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(QT: 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(Start: BeforeRef, Context); |
| 181 | if (IgnoredParens) |
| 182 | return fixIfNotDangerous(Loc: *IgnoredParens, Text: buildQualifier(Qualifier, WhitespaceBefore: 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(QT: 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(QT: ParenStrippedType)) |
| 208 | return changePointerItself(Var, Qualifier, Context); |
| 209 | |
| 210 | if (ParenStrippedType->isPointerType()) |
| 211 | return changePointer(Var, Qualifier, |
| 212 | Pointee: 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(T: AT)) |
| 220 | return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); |
| 221 | |
| 222 | if (AT->isPointerType()) |
| 223 | return changePointer(Var, Qualifier, Pointee: 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>(Val: &Node)) |
| 232 | return false; |
| 233 | |
| 234 | if (isa<clang::BinaryOperator>(Val: &Node) || isa<UnaryOperator>(Val: &Node)) |
| 235 | return true; |
| 236 | |
| 237 | if (isa<clang::ConditionalOperator>(Val: &Node) || |
| 238 | isa<BinaryConditionalOperator>(Val: &Node)) |
| 239 | return true; |
| 240 | |
| 241 | if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Val: &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>(Val: &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>(Val: &ExprNode) || |
| 269 | isa<clang::ConditionalOperator>(Val: &ExprNode)) { |
| 270 | return true; |
| 271 | } |
| 272 | if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(Val: &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>(Val: &ExprNode)) { |
| 284 | if (Op->getOpcode() == UO_AddrOf) { |
| 285 | // Strip leading '&'. |
| 286 | return std::string( |
| 287 | tooling::fixit::getText(Node: *Op->getSubExpr()->IgnoreParens(), Context)); |
| 288 | } |
| 289 | } |
| 290 | StringRef Text = tooling::fixit::getText(Node: ExprNode, Context); |
| 291 | |
| 292 | if (Text.empty()) |
| 293 | return {}; |
| 294 | |
| 295 | // Remove remaining '->' from overloaded operator call |
| 296 | Text.consume_back(Suffix: "->" ); |
| 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 | |