//===--- 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 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(T) || isa(T) || isa(T) || isa(T) || isa(T)); } static bool isValueType(QualType QT) { return isValueType(QT.getTypePtr()); } static bool isMemberOrFunctionPointer(QualType QT) { return (QT->isPointerType() && QT->isFunctionPointerType()) || isa(QT.getTypePtr()); } static bool locDangerous(SourceLocation S) { return S.isInvalid() || S.isMacroID(); } static std::optional 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 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 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 IgnoredParens = skipLParensBackwards(Var.getLocation(), Context); if (IgnoredParens) return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); return std::nullopt; } llvm_unreachable("Unknown QualifierPolicy enum"); } static std::optional changePointerItself(const VarDecl &Var, DeclSpec::TQ Qualifier, const ASTContext &Context) { if (locDangerous(Var.getLocation())) return std::nullopt; std::optional IgnoredParens = skipLParensBackwards(Var.getLocation(), Context); if (IgnoredParens) return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier)); return std::nullopt; } static std::optional 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 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 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 IgnoredParens = skipLParensBackwards(BeforeRef, Context); if (IgnoredParens) return fixIfNotDangerous(*IgnoredParens, buildQualifier(Qualifier, true)); return std::nullopt; } std::optional 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(&Node)) return false; if (isa(&Node) || isa(&Node)) return true; if (isa(&Node) || isa(&Node)) return true; if (const auto *Op = dyn_cast(&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(&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(&ExprNode) || isa(&ExprNode)) { return true; } if (const auto *Op = dyn_cast(&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(&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