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