File indexing completed on 2024-04-28 16:57:50

0001 /*
0002   This file is part of the clazy static checker.
0003 
0004     Copyright (C) 2015 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
0005     Author: SĂ©rgio Martins <sergio.martins@kdab.com>
0006 
0007     This library is free software; you can redistribute it and/or
0008     modify it under the terms of the GNU Library General Public
0009     License as published by the Free Software Foundation; either
0010     version 2 of the License, or (at your option) any later version.
0011 
0012     This library is distributed in the hope that it will be useful,
0013     but WITHOUT ANY WARRANTY; without even the implied warranty of
0014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015     Library General Public License for more details.
0016 
0017     You should have received a copy of the GNU Library General Public License
0018     along with this library; see the file COPYING.LIB.  If not, write to
0019     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020     Boston, MA 02110-1301, USA.
0021 */
0022 
0023 #include "FixItUtils.h"
0024 #include "StringUtils.h"
0025 #include "SourceCompatibilityHelpers.h"
0026 
0027 #include <clang/AST/Expr.h>
0028 #include <clang/AST/ExprCXX.h>
0029 #include <clang/Basic/Diagnostic.h>
0030 #include <clang/Lex/Lexer.h>
0031 #include <clang/AST/ASTContext.h>
0032 #include <clang/AST/Stmt.h>
0033 #include <clang/Basic/SourceLocation.h>
0034 #include <clang/Basic/SourceManager.h>
0035 #include <clang/Lex/Token.h>
0036 
0037 using namespace clazy;
0038 using namespace clang;
0039 using namespace std;
0040 
0041 clang::FixItHint clazy::createReplacement(clang::SourceRange range, const std::string &replacement)
0042 {
0043     if (range.getBegin().isInvalid()) {
0044         return {};
0045     } else {
0046         return FixItHint::CreateReplacement(range, replacement);
0047     }
0048 }
0049 
0050 clang::FixItHint clazy::createInsertion(clang::SourceLocation start, const std::string &insertion)
0051 {
0052     if (start.isInvalid()) {
0053         return {};
0054     } else {
0055         return FixItHint::CreateInsertion(start, insertion);
0056     }
0057 }
0058 
0059 SourceRange clazy::rangeForLiteral(const ASTContext *context, StringLiteral *lt)
0060 {
0061     if (!lt)
0062         return {};
0063 
0064     const int numTokens = lt->getNumConcatenated();
0065     const SourceLocation lastTokenLoc = lt->getStrTokenLoc(numTokens - 1);
0066     if (lastTokenLoc.isInvalid()) {
0067         return {};
0068     }
0069 
0070     SourceRange range;
0071     range.setBegin(clazy::getLocStart(lt));
0072 
0073     SourceLocation end = Lexer::getLocForEndOfToken(lastTokenLoc, 0,
0074                                                     context->getSourceManager(),
0075                                                     context->getLangOpts()); // For some reason getLocStart(lt) is == to getLocEnd(lt)
0076 
0077     if (!end.isValid()) {
0078         return {};
0079     }
0080 
0081     range.setEnd(end);
0082     return range;
0083 }
0084 
0085 void clazy::insertParentMethodCall(const std::string &method, SourceRange range, std::vector<FixItHint> &fixits)
0086 {
0087     fixits.push_back(clazy::createInsertion(range.getEnd(), ")"));
0088     fixits.push_back(clazy::createInsertion(range.getBegin(), method + '('));
0089 }
0090 
0091 bool clazy::insertParentMethodCallAroundStringLiteral(const ASTContext *context,
0092                                                       const std::string &method,
0093                                                       StringLiteral *lt,
0094                                                       std::vector<FixItHint> &fixits)
0095 {
0096     if (!lt)
0097         return false;
0098 
0099     const SourceRange range = rangeForLiteral(context, lt);
0100     if (range.isInvalid())
0101         return false;
0102 
0103     insertParentMethodCall(method, range, /*by-ref*/ fixits);
0104     return true;
0105 }
0106 
0107 SourceLocation clazy::locForNextToken(const ASTContext *context, SourceLocation start, tok::TokenKind kind)
0108 {
0109     if (!start.isValid())
0110         return {};
0111 
0112     Token result;
0113     Lexer::getRawToken(start, result, context->getSourceManager(), context->getLangOpts());
0114 
0115     if (result.getKind() == kind)
0116         return start;
0117 
0118     auto nextStart = Lexer::getLocForEndOfToken(start, 0, context->getSourceManager(), context->getLangOpts());
0119     if (nextStart.getRawEncoding() == start.getRawEncoding())
0120         return {};
0121 
0122     return locForNextToken(context, nextStart, kind);
0123 }
0124 
0125 SourceLocation clazy::biggestSourceLocationInStmt(const SourceManager &sm, Stmt *stmt)
0126 {
0127     if (!stmt)
0128         return {};
0129 
0130     SourceLocation biggestLoc = getLocEnd(stmt);
0131 
0132     for (auto child : stmt->children()) {
0133         SourceLocation candidateLoc = biggestSourceLocationInStmt(sm, child);
0134         if (candidateLoc.isValid() && sm.isBeforeInSLocAddrSpace(biggestLoc, candidateLoc))
0135             biggestLoc = candidateLoc;
0136     }
0137 
0138     return biggestLoc;
0139 }
0140 
0141 SourceLocation clazy::locForEndOfToken(const ASTContext *context, SourceLocation start, int offset)
0142 {
0143     return Lexer::getLocForEndOfToken(start, offset, context->getSourceManager(), context->getLangOpts());
0144 }
0145 
0146 bool clazy::transformTwoCallsIntoOne(const ASTContext *context, CallExpr *call1, CXXMemberCallExpr *call2,
0147                                      const string &replacement, vector<FixItHint> &fixits)
0148 {
0149     Expr *implicitArgument = call2->getImplicitObjectArgument();
0150     if (!implicitArgument)
0151         return false;
0152 
0153     const SourceLocation start1 = clazy::getLocStart(call1);
0154     const SourceLocation end1 = clazy::locForEndOfToken(context, start1, -1); // -1 of offset, so we don't need to insert '('
0155     if (end1.isInvalid())
0156         return false;
0157 
0158     const SourceLocation start2 = getLocEnd(implicitArgument);
0159     const SourceLocation end2 = getLocEnd(call2);
0160     if (start2.isInvalid() || end2.isInvalid())
0161         return false;
0162 
0163     // qgetenv("foo").isEmpty()
0164     // ^                         start1
0165     //       ^                   end1
0166     //              ^            start2
0167     //                        ^  end2
0168     fixits.push_back(clazy::createReplacement({ start1, end1 }, replacement));
0169     fixits.push_back(clazy::createReplacement({ start2, end2 }, ")"));
0170 
0171     return true;
0172 }
0173 
0174 bool clazy::transformTwoCallsIntoOneV2(const ASTContext *context, CXXMemberCallExpr *call2,
0175                                        const string &replacement, std::vector<FixItHint> &fixits)
0176 {
0177     Expr *implicitArgument = call2->getImplicitObjectArgument();
0178     if (!implicitArgument)
0179         return false;
0180 
0181     SourceLocation start = clazy::getLocStart(implicitArgument);
0182     start = clazy::locForEndOfToken(context, start, 0);
0183     const SourceLocation end = getLocEnd(call2);
0184     if (start.isInvalid() || end.isInvalid())
0185         return false;
0186 
0187     fixits.push_back(clazy::createReplacement({ start, end }, replacement));
0188     return true;
0189 }
0190 
0191 FixItHint clazy::fixItReplaceWordWithWord(const ASTContext *context, clang::Stmt *begin,
0192                                           const string &replacement, const string &replacee)
0193 {
0194     auto &sm = context->getSourceManager();
0195     SourceLocation rangeStart = clazy::getLocStart(begin);
0196     SourceLocation rangeEnd = Lexer::getLocForEndOfToken(rangeStart, -1, sm, context->getLangOpts());
0197 
0198     if (rangeEnd.isInvalid()) {
0199         // Fallback. Have seen a case in the wild where the above would fail, it's very rare
0200         rangeEnd = rangeStart.getLocWithOffset(replacee.size() - 2);
0201         if (rangeEnd.isInvalid()) {
0202             clazy::printLocation(sm, rangeStart);
0203             clazy::printLocation(sm, rangeEnd);
0204             clazy::printLocation(sm, Lexer::getLocForEndOfToken(rangeStart, 0, sm, context->getLangOpts()));
0205             return {};
0206         }
0207     }
0208 
0209     return FixItHint::CreateReplacement(SourceRange(rangeStart, rangeEnd), replacement);
0210 }
0211 
0212 vector<FixItHint> clazy::fixItRemoveToken(const ASTContext *context, Stmt *stmt, bool removeParenthesis)
0213 {
0214     SourceLocation start = clazy::getLocStart(stmt);
0215     SourceLocation end = Lexer::getLocForEndOfToken(start, removeParenthesis ? 0 : -1,
0216                                                     context->getSourceManager(), context->getLangOpts());
0217 
0218     vector<FixItHint> fixits;
0219 
0220     if (start.isValid() && end.isValid()) {
0221         fixits.push_back(FixItHint::CreateRemoval(SourceRange(start, end)));
0222 
0223         if (removeParenthesis) {
0224             // Remove the last parenthesis
0225             fixits.push_back(FixItHint::CreateRemoval(SourceRange(clazy::getLocEnd(stmt), clazy::getLocEnd(stmt))));
0226         }
0227     }
0228 
0229     return fixits;
0230 }