File indexing completed on 2024-05-12 05:40:59

0001 /*
0002     SPDX-FileCopyrightText: 2016-2018 Sergio Martins <smartins@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "function-args-by-value.h"
0008 #include "ClazyContext.h"
0009 #include "FixItUtils.h"
0010 #include "StringUtils.h"
0011 #include "TypeUtils.h"
0012 #include "Utils.h"
0013 #include "clazy_stl.h"
0014 
0015 #include <clang/AST/ASTContext.h>
0016 #include <clang/AST/Decl.h>
0017 #include <clang/AST/DeclCXX.h>
0018 #include <clang/AST/Expr.h>
0019 #include <clang/AST/ExprCXX.h>
0020 #include <clang/AST/PrettyPrinter.h>
0021 #include <clang/AST/Redeclarable.h>
0022 #include <clang/AST/Stmt.h>
0023 #include <clang/AST/Type.h>
0024 #include <clang/Basic/LLVM.h>
0025 #include <clang/Basic/SourceLocation.h>
0026 #include <llvm/ADT/ArrayRef.h>
0027 #include <llvm/ADT/StringRef.h>
0028 #include <llvm/Support/Casting.h>
0029 #include <llvm/Support/raw_ostream.h>
0030 
0031 #include <iterator>
0032 #include <vector>
0033 
0034 namespace clang
0035 {
0036 class Decl;
0037 } // namespace clang
0038 
0039 using namespace clang;
0040 
0041 // TODO, go over all these
0042 bool FunctionArgsByValue::shouldIgnoreClass(CXXRecordDecl *record)
0043 {
0044     if (!record) {
0045         return false;
0046     }
0047 
0048     if (Utils::isSharedPointer(record)) {
0049         return true;
0050     }
0051 
0052     static const std::vector<std::string> ignoreList = {
0053         "QDebug", // Too many warnings
0054         "QGenericReturnArgument",
0055         "QColor", // TODO: Remove in Qt6
0056         "QStringRef", // TODO: Remove in Qt6
0057         "QList::const_iterator", // TODO: Remove in Qt6
0058         "QJsonArray::const_iterator", // TODO: Remove in Qt6
0059         "QList<QString>::const_iterator", // TODO: Remove in Qt6
0060         "QtMetaTypePrivate::QSequentialIterableImpl",
0061         "QtMetaTypePrivate::QAssociativeIterableImpl",
0062         "QVariantComparisonHelper",
0063         "QHashDummyValue",
0064         "QCharRef",
0065         "QString::Null",
0066     };
0067     return clazy::contains(ignoreList, record->getQualifiedNameAsString());
0068 }
0069 
0070 bool FunctionArgsByValue::shouldIgnoreOperator(FunctionDecl *function)
0071 {
0072     // Too many warnings in operator<<
0073     static const std::vector<StringRef> ignoreList = {"operator<<"};
0074 
0075     return clazy::contains(ignoreList, clazy::name(function));
0076 }
0077 
0078 bool FunctionArgsByValue::shouldIgnoreFunction(clang::FunctionDecl *function)
0079 {
0080     static const std::vector<std::string> qualifiedIgnoreList = {
0081         "QDBusMessage::createErrorReply", // Fixed in Qt6
0082         "QMenu::exec", // Fixed in Qt6
0083         "QTextFrame::iterator", // Fixed in Qt6
0084         "QGraphicsWidget::addActions", // Fixed in Qt6
0085         "QListWidget::mimeData", // Fixed in Qt6
0086         "QTableWidget::mimeData", // Fixed in Qt6
0087         "QTreeWidget::mimeData", // Fixed in Qt6
0088         "QWidget::addActions", // Fixed in Qt6
0089         "QSslCertificate::verify", // Fixed in Qt6
0090         "QSslConfiguration::setAllowedNextProtocols", // Fixed in Qt6
0091     };
0092 
0093     return clazy::contains(qualifiedIgnoreList, function->getQualifiedNameAsString());
0094 }
0095 
0096 FunctionArgsByValue::FunctionArgsByValue(const std::string &name, ClazyContext *context)
0097     : CheckBase(name, context, Option_CanIgnoreIncludes)
0098 {
0099 }
0100 
0101 void FunctionArgsByValue::VisitDecl(Decl *decl)
0102 {
0103     processFunction(dyn_cast<FunctionDecl>(decl));
0104 }
0105 
0106 void FunctionArgsByValue::VisitStmt(Stmt *stmt)
0107 {
0108     if (auto *lambda = dyn_cast<LambdaExpr>(stmt)) {
0109         processFunction(lambda->getCallOperator());
0110     }
0111 }
0112 
0113 void FunctionArgsByValue::processFunction(FunctionDecl *func)
0114 {
0115     if (!func || !func->isThisDeclarationADefinition() || func->isDeleted()) {
0116         return;
0117     }
0118 
0119     auto *ctor = dyn_cast<CXXConstructorDecl>(func);
0120     if (ctor && ctor->isCopyConstructor()) {
0121         return; // copy-ctor must take by ref
0122     }
0123 
0124     const bool warnForOverriddenMethods = isOptionSet("warn-for-overridden-methods");
0125     if (!warnForOverriddenMethods && Utils::methodOverrides(dyn_cast<CXXMethodDecl>(func))) {
0126         // When overriding you can't change the signature. You should fix the base classes first
0127         return;
0128     }
0129 
0130     if (shouldIgnoreOperator(func)) {
0131         return;
0132     }
0133 
0134     if (m_context->isQtDeveloper() && shouldIgnoreFunction(func)) {
0135         return;
0136     }
0137 
0138     Stmt *body = func->getBody();
0139 
0140     int i = -1;
0141     for (auto *param : Utils::functionParameters(func)) {
0142         i++;
0143         const QualType paramQt = clazy::unrefQualType(param->getType());
0144         const Type *paramType = paramQt.getTypePtrOrNull();
0145         if (!paramType || paramType->isIncompleteType() || paramType->isDependentType()) {
0146             continue;
0147         }
0148 
0149         if (shouldIgnoreClass(paramType->getAsCXXRecordDecl())) {
0150             continue;
0151         }
0152 
0153         clazy::QualTypeClassification classif;
0154         bool success = clazy::classifyQualType(m_context, param->getType(), param, classif, body);
0155         if (!success) {
0156             continue;
0157         }
0158 
0159         if (classif.passSmallTrivialByValue) {
0160             if (ctor) { // Implements fix for Bug #379342
0161                 std::vector<CXXCtorInitializer *> initializers = Utils::ctorInitializer(ctor, param);
0162                 bool found_by_ref_member_init = false;
0163                 for (auto *initializer : initializers) {
0164                     if (!initializer->isMemberInitializer()) {
0165                         continue; // skip base class initializer
0166                     }
0167                     FieldDecl *field = initializer->getMember();
0168                     if (!field) {
0169                         continue;
0170                     }
0171 
0172                     QualType type = field->getType();
0173                     if (type.isNull() || type->isReferenceType()) {
0174                         found_by_ref_member_init = true;
0175                         break;
0176                     }
0177                 }
0178 
0179                 if (found_by_ref_member_init) {
0180                     continue;
0181                 }
0182             }
0183 
0184             std::vector<FixItHint> fixits;
0185             auto *method = dyn_cast<CXXMethodDecl>(func);
0186             const bool isVirtualMethod = method && method->isVirtual();
0187             if (!isVirtualMethod || warnForOverriddenMethods) { // Don't try to fix virtual methods, as build can fail
0188                 for (auto *redecl : func->redecls()) { // Fix in both header and .cpp
0189                     auto *fdecl = dyn_cast<FunctionDecl>(redecl);
0190                     const ParmVarDecl *param = fdecl->getParamDecl(i);
0191                     fixits.push_back(fixit(fdecl, param, classif));
0192                 }
0193             }
0194 
0195             const std::string paramStr = param->getType().getAsString(lo());
0196             std::string error = "Pass small and trivially-copyable type by value (" + paramStr + ')';
0197             emitWarning(param->getBeginLoc(), error, fixits);
0198         }
0199     }
0200 }
0201 
0202 FixItHint FunctionArgsByValue::fixit(FunctionDecl *func, const ParmVarDecl *param, clazy::QualTypeClassification)
0203 {
0204     QualType qt = clazy::unrefQualType(param->getType());
0205     qt.removeLocalConst();
0206     const std::string typeName = qt.getAsString(PrintingPolicy(lo()));
0207     std::string replacement = typeName + ' ' + std::string(clazy::name(param));
0208     SourceLocation startLoc = param->getBeginLoc();
0209     SourceLocation endLoc = param->getEndLoc();
0210 
0211     const int numRedeclarations = std::distance(func->redecls_begin(), func->redecls_end());
0212     const bool definitionIsAlsoDeclaration = numRedeclarations == 1;
0213     const bool isDeclarationButNotDefinition = !func->doesThisDeclarationHaveABody();
0214 
0215     if (param->hasDefaultArg() && (isDeclarationButNotDefinition || definitionIsAlsoDeclaration)) {
0216         endLoc = param->getDefaultArg()->getBeginLoc().getLocWithOffset(-1);
0217         replacement += " =";
0218     }
0219 
0220     if (!startLoc.isValid() || !endLoc.isValid()) {
0221         llvm::errs() << "Internal error could not apply fixit " << startLoc.printToString(sm()) << ';' << endLoc.printToString(sm()) << "\n";
0222         return {};
0223     }
0224 
0225     return clazy::createReplacement({startLoc, endLoc}, replacement);
0226 }