1 /*
2     This file is part of the clazy static checker.
3 
4     Copyright (C) 2016-2018 Sergio Martins <smartins@kde.org>
5 
6     This library is free software; you can redistribute it and/or
7     modify it under the terms of the GNU Library General Public
8     License as published by the Free Software Foundation; either
9     version 2 of the License, or (at your option) any later version.
10 
11     This library is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14     Library General Public License for more details.
15 
16     You should have received a copy of the GNU Library General Public License
17     along with this library; see the file COPYING.LIB.  If not, write to
18     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19     Boston, MA 02110-1301, USA.
20 */
21 
22 #include "function-args-by-value.h"
23 #include "Utils.h"
24 #include "StringUtils.h"
25 #include "TypeUtils.h"
26 #include "FixItUtils.h"
27 #include "ClazyContext.h"
28 #include "SourceCompatibilityHelpers.h"
29 #include "clazy_stl.h"
30 
31 #include <clang/AST/ASTContext.h>
32 #include <clang/AST/Decl.h>
33 #include <clang/AST/DeclCXX.h>
34 #include <clang/AST/Expr.h>
35 #include <clang/AST/ExprCXX.h>
36 #include <clang/AST/PrettyPrinter.h>
37 #include <clang/AST/Redeclarable.h>
38 #include <clang/AST/Stmt.h>
39 #include <clang/AST/Type.h>
40 #include <clang/Basic/LLVM.h>
41 #include <clang/Basic/SourceLocation.h>
42 #include <llvm/ADT/ArrayRef.h>
43 #include <llvm/ADT/StringRef.h>
44 #include <llvm/Support/Casting.h>
45 #include <llvm/Support/raw_ostream.h>
46 
47 #include <iterator>
48 #include <vector>
49 
50 namespace clang {
51 class Decl;
52 }  // namespace clang
53 
54 using namespace clang;
55 using namespace std;
56 
57 // TODO, go over all these
shouldIgnoreClass(CXXRecordDecl * record)58 static bool shouldIgnoreClass(CXXRecordDecl *record)
59 {
60     if (!record)
61         return false;
62 
63     if (Utils::isSharedPointer(record))
64         return true;
65 
66     static const vector<string> ignoreList = {"QDebug", // Too many warnings
67                                               "QGenericReturnArgument",
68                                               "QColor", // TODO: Remove in Qt6
69                                               "QStringRef", // TODO: Remove in Qt6
70                                               "QList::const_iterator", // TODO: Remove in Qt6
71                                               "QJsonArray::const_iterator", // TODO: Remove in Qt6
72                                               "QList<QString>::const_iterator",  // TODO: Remove in Qt6
73                                               "QtMetaTypePrivate::QSequentialIterableImpl",
74                                               "QtMetaTypePrivate::QAssociativeIterableImpl",
75                                               "QVariantComparisonHelper",
76                                               "QHashDummyValue", "QCharRef", "QString::Null"
77     };
78     return clazy::contains(ignoreList, record->getQualifiedNameAsString());
79 }
80 
shouldIgnoreOperator(FunctionDecl * function)81 static bool shouldIgnoreOperator(FunctionDecl *function)
82 {
83     // Too many warnings in operator<<
84     static const vector<StringRef> ignoreList = { "operator<<" };
85 
86     return clazy::contains(ignoreList, clazy::name(function));
87 }
88 
shouldIgnoreFunction(clang::FunctionDecl * function)89 static bool shouldIgnoreFunction(clang::FunctionDecl *function)
90 {
91     static const vector<string> qualifiedIgnoreList = {"QDBusMessage::createErrorReply", // Fixed in Qt6
92                                                        "QMenu::exec", // Fixed in Qt6
93                                                        "QTextFrame::iterator", // Fixed in Qt6
94                                                        "QGraphicsWidget::addActions", // Fixed in Qt6
95                                                        "QListWidget::mimeData", // Fixed in Qt6
96                                                        "QTableWidget::mimeData", // Fixed in Qt6
97                                                        "QTreeWidget::mimeData", // Fixed in Qt6
98                                                        "QWidget::addActions", // Fixed in Qt6
99                                                        "QSslCertificate::verify", // Fixed in Qt6
100                                                        "QSslConfiguration::setAllowedNextProtocols" // Fixed in Qt6
101     };
102 
103     return clazy::contains(qualifiedIgnoreList, function->getQualifiedNameAsString());
104 }
105 
FunctionArgsByValue(const std::string & name,ClazyContext * context)106 FunctionArgsByValue::FunctionArgsByValue(const std::string &name, ClazyContext *context)
107     : CheckBase(name, context, Option_CanIgnoreIncludes)
108 {
109 }
110 
VisitDecl(Decl * decl)111 void FunctionArgsByValue::VisitDecl(Decl *decl)
112 {
113     processFunction(dyn_cast<FunctionDecl>(decl));
114 }
115 
VisitStmt(Stmt * stmt)116 void FunctionArgsByValue::VisitStmt(Stmt *stmt)
117 {
118     if (auto lambda = dyn_cast<LambdaExpr>(stmt))
119         processFunction(lambda->getCallOperator());
120 }
121 
processFunction(FunctionDecl * func)122 void FunctionArgsByValue::processFunction(FunctionDecl *func)
123 {
124     if (!func || !func->isThisDeclarationADefinition() || func->isDeleted())
125         return;
126 
127     auto ctor = dyn_cast<CXXConstructorDecl>(func);
128     if (ctor && ctor->isCopyConstructor())
129         return; // copy-ctor must take by ref
130 
131     const bool warnForOverriddenMethods = isOptionSet("warn-for-overridden-methods");
132     if (!warnForOverriddenMethods && Utils::methodOverrides(dyn_cast<CXXMethodDecl>(func))) {
133         // When overriding you can't change the signature. You should fix the base classes first
134         return;
135     }
136 
137     if (shouldIgnoreOperator(func))
138         return;
139 
140     if (m_context->isQtDeveloper() && shouldIgnoreFunction(func))
141         return;
142 
143     Stmt *body = func->getBody();
144 
145     int i = -1;
146     for (auto param : Utils::functionParameters(func)) {
147         i++;
148         QualType paramQt = TypeUtils::unrefQualType(param->getType());
149         const Type *paramType = paramQt.getTypePtrOrNull();
150         if (!paramType || paramType->isIncompleteType() || paramType->isDependentType())
151             continue;
152 
153         if (shouldIgnoreClass(paramType->getAsCXXRecordDecl()))
154             continue;
155 
156         TypeUtils::QualTypeClassification classif;
157         bool success = TypeUtils::classifyQualType(m_context, param, classif, body);
158         if (!success)
159             continue;
160 
161         if (classif.passSmallTrivialByValue) {
162             if (ctor) { // Implements fix for Bug #379342
163                 vector<CXXCtorInitializer *> initializers = Utils::ctorInitializer(ctor, param);
164                 bool found_by_ref_member_init = false;
165                 for (auto initializer : initializers) {
166                     if (!initializer->isMemberInitializer())
167                         continue; // skip base class initializer
168                     FieldDecl *field = initializer->getMember();
169                     if (!field)
170                         continue;
171 
172                     QualType type = field->getType();
173                     if (type.isNull() || type->isReferenceType()) {
174                         found_by_ref_member_init = true;
175                         break;
176                     }
177                 }
178 
179                 if (found_by_ref_member_init)
180                     continue;
181             }
182 
183             std::vector<FixItHint> fixits;
184             auto method = dyn_cast<CXXMethodDecl>(func);
185             const bool isVirtualMethod = method && method->isVirtual();
186             if ((!isVirtualMethod || warnForOverriddenMethods) && isFixitEnabled()) { // Don't try to fix virtual methods, as build can fail
187                 for (auto redecl : func->redecls()) { // Fix in both header and .cpp
188                     auto fdecl = dyn_cast<FunctionDecl>(redecl);
189                     const ParmVarDecl *param = fdecl->getParamDecl(i);
190                     fixits.push_back(fixit(fdecl, param, classif));
191                 }
192             }
193 
194             const string paramStr = param->getType().getAsString();
195             string error = "Pass small and trivially-copyable type by value (" + paramStr + ')';
196             emitWarning(clazy::getLocStart(param), error.c_str(), fixits);
197         }
198     }
199 }
200 
fixit(FunctionDecl * func,const ParmVarDecl * param,TypeUtils::QualTypeClassification)201 FixItHint FunctionArgsByValue::fixit(FunctionDecl *func, const ParmVarDecl *param,
202                                      TypeUtils::QualTypeClassification)
203 {
204     QualType qt = TypeUtils::unrefQualType(param->getType());
205     qt.removeLocalConst();
206     const string typeName = qt.getAsString(PrintingPolicy(lo()));
207     string replacement = typeName + ' ' + string(clazy::name(param));
208     SourceLocation startLoc = clazy::getLocStart(param);
209     SourceLocation endLoc = clazy::getLocEnd(param);
210 
211     const int numRedeclarations = std::distance(func->redecls_begin(), func->redecls_end());
212     const bool definitionIsAlsoDeclaration = numRedeclarations == 1;
213     const bool isDeclarationButNotDefinition = !func->doesThisDeclarationHaveABody();
214 
215     if (param->hasDefaultArg() && (isDeclarationButNotDefinition || definitionIsAlsoDeclaration)) {
216         endLoc = clazy::getLocStart(param->getDefaultArg()).getLocWithOffset(-1);
217         replacement += " =";
218     }
219 
220     if (!startLoc.isValid() || !endLoc.isValid()) {
221         llvm::errs() << "Internal error could not apply fixit " << startLoc.printToString(sm())
222                      << ';' << endLoc.printToString(sm()) << "\n";
223         return {};
224     }
225 
226     return clazy::createReplacement({ startLoc, endLoc }, replacement);
227 }
228