| //===--- FixItHintUtils.cpp - clang-tidy-----------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "FixItHintUtils.h" |
| #include "LexerUtils.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/AST/ExprCXX.h" |
| #include "clang/AST/Type.h" |
| #include "clang/Tooling/FixIt.h" |
| #include <optional> |
| |
| namespace clang::tidy::utils::fixit { |
| |
| FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context) { |
| SourceLocation AmpLocation = Var.getLocation(); |
| auto Token = utils::lexer::getPreviousToken( |
| AmpLocation, Context.getSourceManager(), Context.getLangOpts()); |
| if (!Token.is(tok::unknown)) |
| AmpLocation = Lexer::getLocForEndOfToken(Token.getLocation(), 0, |
| Context.getSourceManager(), |
| Context.getLangOpts()); |
| return FixItHint::CreateInsertion(AmpLocation, "&"); |
| } |
| |
| static bool isValueType(const Type *T) { |
| return !(isa<PointerType>(T) || isa<ReferenceType>(T) || isa<ArrayType>(T) || |
| isa<MemberPointerType>(T) || isa<ObjCObjectPointerType>(T)); |
| } |
| static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); } |
| static bool isMemberOrFunctionPointer(QualType QT) { |
| return (QT->isPointerType() && QT->isFunctionPointerType()) || |
| isa<MemberPointerType>(QT.getTypePtr()); |
| } |
| |
| static bool locDangerous(SourceLocation S) { |
| return S.isInvalid() || S.isMacroID(); |
| } |
| |
| static std::optional<SourceLocation> |
| skipLParensBackwards(SourceLocation Start, const ASTContext &Context) { |
| if (locDangerous(Start)) |
| return std::nullopt; |
| |
| auto PreviousTokenLParen = [&Start, &Context]() { |
| Token T; |
| T = lexer::getPreviousToken(Start, Context.getSourceManager(), |
| Context.getLangOpts()); |
| return T.is(tok::l_paren); |
| }; |
| |
| while (Start.isValid() && PreviousTokenLParen()) |
| Start = lexer::findPreviousTokenStart(Start, Context.getSourceManager(), |
| Context.getLangOpts()); |
| |
| if (locDangerous(Start)) |
| return std::nullopt; |
| return Start; |
| } |
| |
| static std::optional<FixItHint> fixIfNotDangerous(SourceLocation Loc, |
| StringRef Text) { |
| if (locDangerous(Loc)) |
| return std::nullopt; |
| return FixItHint::CreateInsertion(Loc, Text); |
| } |
| |
| // Build a string that can be emitted as FixIt with either a space in before |
| // or after the qualifier, either ' const' or 'const '. |
| static std::string buildQualifier(DeclSpec::TQ Qualifier, |
| bool WhitespaceBefore = false) { |
| if (WhitespaceBefore) |
| return (llvm::Twine(' ') + DeclSpec::getSpecifierName(Qualifier)).str(); |
| return (llvm::Twine(DeclSpec::getSpecifierName(Qualifier)) + " ").str(); |
| } |
| |
| static std::optional<FixItHint> changeValue(const VarDecl &Var, |
| DeclSpec::TQ Qualifier, |
| QualifierTarget QualTarget, |
| QualifierPolicy QualPolicy, |
| const ASTContext &Context) { |
| switch (QualPolicy) { |
| case QualifierPolicy::Left: |
| return fixIfNotDangerous(Var.getTypeSpecStartLoc(), |
| buildQualifier(Qualifier)); |
| case QualifierPolicy::Right: |
| std::optional<SourceLocation> IgnoredParens = |
| skipLParensBackwards(Var.getLocation(), Context); |
| |
| if (IgnoredParens) |
| return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); |
| return std::nullopt; |
| } |
| llvm_unreachable("Unknown QualifierPolicy enum"); |
| } |
| |
| static std::optional<FixItHint> changePointerItself(const VarDecl &Var, |
| DeclSpec::TQ Qualifier, |
| const ASTContext &Context) { |
| if (locDangerous(Var.getLocation())) |
| return std::nullopt; |
| |
| std::optional<SourceLocation> IgnoredParens = |
| skipLParensBackwards(Var.getLocation(), Context); |
| if (IgnoredParens) |
| return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); |
| return std::nullopt; |
| } |
| |
| static std::optional<FixItHint> |
| changePointer(const VarDecl &Var, DeclSpec::TQ Qualifier, const Type *Pointee, |
| QualifierTarget QualTarget, QualifierPolicy QualPolicy, |
| const ASTContext &Context) { |
| // The pointer itself shall be marked as `const`. This is always to the right |
| // of the '*' or in front of the identifier. |
| if (QualTarget == QualifierTarget::Value) |
| return changePointerItself(Var, Qualifier, Context); |
| |
| // Mark the pointee `const` that is a normal value (`int* p = nullptr;`). |
| if (QualTarget == QualifierTarget::Pointee && isValueType(Pointee)) { |
| // Adding the `const` on the left side is just the beginning of the type |
| // specification. (`const int* p = nullptr;`) |
| if (QualPolicy == QualifierPolicy::Left) |
| return fixIfNotDangerous(Var.getTypeSpecStartLoc(), |
| buildQualifier(Qualifier)); |
| |
| // Adding the `const` on the right side of the value type requires finding |
| // the `*` token and placing the `const` left of it. |
| // (`int const* p = nullptr;`) |
| if (QualPolicy == QualifierPolicy::Right) { |
| SourceLocation BeforeStar = lexer::findPreviousTokenKind( |
| Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), |
| tok::star); |
| if (locDangerous(BeforeStar)) |
| return std::nullopt; |
| |
| std::optional<SourceLocation> IgnoredParens = |
| skipLParensBackwards(BeforeStar, Context); |
| |
| if (IgnoredParens) |
| return fixIfNotDangerous(*IgnoredParens, |
| buildQualifier(Qualifier, true)); |
| return std::nullopt; |
| } |
| } |
| |
| if (QualTarget == QualifierTarget::Pointee && Pointee->isPointerType()) { |
| // Adding the `const` to the pointee if the pointee is a pointer |
| // is the same as 'QualPolicy == Right && isValueType(Pointee)'. |
| // The `const` must be left of the last `*` token. |
| // (`int * const* p = nullptr;`) |
| SourceLocation BeforeStar = lexer::findPreviousTokenKind( |
| Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), |
| tok::star); |
| return fixIfNotDangerous(BeforeStar, buildQualifier(Qualifier, true)); |
| } |
| |
| return std::nullopt; |
| } |
| |
| static std::optional<FixItHint> |
| changeReferencee(const VarDecl &Var, DeclSpec::TQ Qualifier, QualType Pointee, |
| QualifierTarget QualTarget, QualifierPolicy QualPolicy, |
| const ASTContext &Context) { |
| if (QualPolicy == QualifierPolicy::Left && isValueType(Pointee)) |
| return fixIfNotDangerous(Var.getTypeSpecStartLoc(), |
| buildQualifier(Qualifier)); |
| |
| SourceLocation BeforeRef = lexer::findPreviousAnyTokenKind( |
| Var.getLocation(), Context.getSourceManager(), Context.getLangOpts(), |
| tok::amp, tok::ampamp); |
| std::optional<SourceLocation> IgnoredParens = |
| skipLParensBackwards(BeforeRef, Context); |
| if (IgnoredParens) |
| return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true)); |
| |
| return std::nullopt; |
| } |
| |
| std::optional<FixItHint> addQualifierToVarDecl(const VarDecl &Var, |
| const ASTContext &Context, |
| DeclSpec::TQ Qualifier, |
| QualifierTarget QualTarget, |
| QualifierPolicy QualPolicy) { |
| assert((QualPolicy == QualifierPolicy::Left || |
| QualPolicy == QualifierPolicy::Right) && |
| "Unexpected Insertion Policy"); |
| assert((QualTarget == QualifierTarget::Pointee || |
| QualTarget == QualifierTarget::Value) && |
| "Unexpected Target"); |
| |
| QualType ParenStrippedType = Var.getType().IgnoreParens(); |
| if (isValueType(ParenStrippedType)) |
| return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); |
| |
| if (ParenStrippedType->isReferenceType()) |
| return changeReferencee(Var, Qualifier, Var.getType()->getPointeeType(), |
| QualTarget, QualPolicy, Context); |
| |
| if (isMemberOrFunctionPointer(ParenStrippedType)) |
| return changePointerItself(Var, Qualifier, Context); |
| |
| if (ParenStrippedType->isPointerType()) |
| return changePointer(Var, Qualifier, |
| ParenStrippedType->getPointeeType().getTypePtr(), |
| QualTarget, QualPolicy, Context); |
| |
| if (ParenStrippedType->isArrayType()) { |
| const Type *AT = ParenStrippedType->getBaseElementTypeUnsafe(); |
| assert(AT && "Did not retrieve array element type for an array."); |
| |
| if (isValueType(AT)) |
| return changeValue(Var, Qualifier, QualTarget, QualPolicy, Context); |
| |
| if (AT->isPointerType()) |
| return changePointer(Var, Qualifier, AT->getPointeeType().getTypePtr(), |
| QualTarget, QualPolicy, Context); |
| } |
| |
| return std::nullopt; |
| } |
| |
| bool areParensNeededForStatement(const Stmt &Node) { |
| if (isa<ParenExpr>(&Node)) |
| return false; |
| |
| if (isa<clang::BinaryOperator>(&Node) || isa<UnaryOperator>(&Node)) |
| return true; |
| |
| if (isa<clang::ConditionalOperator>(&Node) || |
| isa<BinaryConditionalOperator>(&Node)) |
| return true; |
| |
| if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&Node)) { |
| switch (Op->getOperator()) { |
| case OO_PlusPlus: |
| [[fallthrough]]; |
| case OO_MinusMinus: |
| return Op->getNumArgs() != 2; |
| case OO_Call: |
| [[fallthrough]]; |
| case OO_Subscript: |
| [[fallthrough]]; |
| case OO_Arrow: |
| return false; |
| default: |
| return true; |
| }; |
| } |
| |
| if (isa<CStyleCastExpr>(&Node)) |
| return true; |
| |
| return false; |
| } |
| |
| // Return true if expr needs to be put in parens when it is an argument of a |
| // prefix unary operator, e.g. when it is a binary or ternary operator |
| // syntactically. |
| static bool needParensAfterUnaryOperator(const Expr &ExprNode) { |
| if (isa<clang::BinaryOperator>(&ExprNode) || |
| isa<clang::ConditionalOperator>(&ExprNode)) { |
| return true; |
| } |
| if (const auto *Op = dyn_cast<CXXOperatorCallExpr>(&ExprNode)) { |
| return Op->getNumArgs() == 2 && Op->getOperator() != OO_PlusPlus && |
| Op->getOperator() != OO_MinusMinus && Op->getOperator() != OO_Call && |
| Op->getOperator() != OO_Subscript; |
| } |
| return false; |
| } |
| |
| // Format a pointer to an expression: prefix with '*' but simplify |
| // when it already begins with '&'. Return empty string on failure. |
| std::string formatDereference(const Expr &ExprNode, const ASTContext &Context) { |
| if (const auto *Op = dyn_cast<clang::UnaryOperator>(&ExprNode)) { |
| if (Op->getOpcode() == UO_AddrOf) { |
| // Strip leading '&'. |
| return std::string( |
| tooling::fixit::getText(*Op->getSubExpr()->IgnoreParens(), Context)); |
| } |
| } |
| StringRef Text = tooling::fixit::getText(ExprNode, Context); |
| |
| if (Text.empty()) |
| return {}; |
| |
| // Remove remaining '->' from overloaded operator call |
| Text.consume_back("->"); |
| |
| // Add leading '*'. |
| if (needParensAfterUnaryOperator(ExprNode)) { |
| return (llvm::Twine("*(") + Text + ")").str(); |
| } |
| return (llvm::Twine("*") + Text).str(); |
| } |
| |
| } // namespace clang::tidy::utils::fixit |