bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / store / constmethod.cxx
blobd4bae3f015f3d9224dc7afca21cd56348d0aab5d
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
10 #include <algorithm>
11 #include <string>
12 #include <unordered_set>
13 #include <unordered_map>
14 #include <iostream>
16 #include "plugin.hxx"
17 #include "compat.hxx"
18 #include "check.hxx"
19 #include "functionaddress.hxx"
21 /**
22 Find methods that can be declared const.
24 This analysis attempts to implement "logical const" as opposed to "technical const", which means
25 we ignore always-const nature of std::unique_ptr::operator->
27 This is not a sophisticated analysis. It deliberately skips all of the hard cases for now.
28 It is an exercise in getting the most benefit for the least effort.
30 namespace
33 static bool startswith(const std::string& rStr, const char* pSubStr) {
34 return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
37 class ConstMethod:
38 public loplugin::FunctionAddress<ConstMethod>
40 public:
41 explicit ConstMethod(InstantiationData const & data): loplugin::FunctionAddress<ConstMethod>(data) {}
43 virtual void run() override {
44 std::string fn( compiler.getSourceManager().getFileEntryForID(
45 compiler.getSourceManager().getMainFileID())->getName() );
46 normalizeDotDotInFilePath(fn);
47 if (fn.find("/qa/") != std::string::npos)
48 return;
49 // the rest of the stuff in these folders is technically const, but not logically const (IMO)
50 if (startswith(fn, SRCDIR "/unotools/"))
51 return;
52 if (startswith(fn, SRCDIR "/svl/"))
53 return;
54 if (startswith(fn, SRCDIR "/binaryurp/"))
55 return;
56 if (startswith(fn, SRCDIR "/cpputools/"))
57 return;
58 if (startswith(fn, SRCDIR "/opencl/"))
59 return;
60 if (startswith(fn, SRCDIR "/helpcompiler/"))
61 return;
62 // very little in here can usefully be made const. Could tighten this up a little and only exclude stuff declared in .cxx files
63 if (startswith(fn, SRCDIR "/vcl/"))
64 return;
65 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
67 for (const CXXMethodDecl *pMethodDecl : interestingMethodSet) {
68 if (methodCannotBeConstSet.find(pMethodDecl) != methodCannotBeConstSet.end())
69 continue;
70 auto canonicalDecl = pMethodDecl->getCanonicalDecl();
71 if (getFunctionsWithAddressTaken().find((FunctionDecl const *)canonicalDecl)
72 != getFunctionsWithAddressTaken().end())
73 continue;
74 StringRef aFileName = compiler.getSourceManager().getFilename(compiler.getSourceManager().getSpellingLoc(canonicalDecl->getLocStart()));
75 if (loplugin::isSamePathname(aFileName, SRCDIR "/include/LibreOfficeKit/LibreOfficeKit.hxx"))
76 continue;
77 report(
78 DiagnosticsEngine::Warning,
79 "this method can be const",
80 pMethodDecl->getLocStart())
81 << pMethodDecl->getSourceRange();
82 if (canonicalDecl->getLocation() != pMethodDecl->getLocation()) {
83 report(
84 DiagnosticsEngine::Note,
85 "canonical method declaration here",
86 canonicalDecl->getLocStart())
87 << canonicalDecl->getSourceRange();
89 //pMethodDecl->dump();
93 bool TraverseCXXMethodDecl(CXXMethodDecl *);
94 bool TraverseCXXConversionDecl(CXXConversionDecl *);
95 bool VisitCXXMethodDecl(const CXXMethodDecl *);
96 bool VisitCXXThisExpr(const CXXThisExpr *);
98 private:
99 bool isPointerOrReferenceToConst(const QualType& qt);
100 bool isPointerOrReferenceToNonConst(const QualType& qt);
101 bool checkIfCanBeConst(const Stmt*, const CXXMethodDecl*);
103 std::unordered_set<const CXXMethodDecl*> interestingMethodSet;
104 std::unordered_set<const CXXMethodDecl*> methodCannotBeConstSet;
105 CXXMethodDecl const * currCXXMethodDecl;
108 bool ConstMethod::TraverseCXXMethodDecl(CXXMethodDecl * cxxMethodDecl)
110 currCXXMethodDecl = cxxMethodDecl;
111 bool rv = RecursiveASTVisitor<ConstMethod>::TraverseCXXMethodDecl(cxxMethodDecl);
112 currCXXMethodDecl = nullptr;
113 return rv;
116 bool ConstMethod::TraverseCXXConversionDecl(CXXConversionDecl * cxxConversionDecl)
118 currCXXMethodDecl = cxxConversionDecl;
119 bool rv = RecursiveASTVisitor<ConstMethod>::TraverseCXXConversionDecl(cxxConversionDecl);
120 currCXXMethodDecl = nullptr;
121 return rv;
124 bool ConstMethod::VisitCXXMethodDecl(const CXXMethodDecl * cxxMethodDecl)
126 if (ignoreLocation(cxxMethodDecl) || !cxxMethodDecl->isThisDeclarationADefinition()) {
127 return true;
129 if (cxxMethodDecl->isConst())
130 return true;
131 // ignore stuff that forms part of the stable URE interface
132 if (isInUnoIncludeFile(cxxMethodDecl)) {
133 return true;
135 // TODO ignore template stuff for now
136 if (cxxMethodDecl->getTemplatedKind() != FunctionDecl::TK_NonTemplate) {
137 return true;
139 if (cxxMethodDecl->isDeleted())
140 return true;
141 if (cxxMethodDecl->isStatic())
142 return true;
143 if (cxxMethodDecl->isOverloadedOperator())
144 return true;
145 if (isa<CXXConstructorDecl>(cxxMethodDecl))
146 return true;
147 if (isa<CXXDestructorDecl>(cxxMethodDecl))
148 return true;
149 if (cxxMethodDecl->getParent()->getDescribedClassTemplate() != nullptr ) {
150 return true;
152 // ignore virtual methods
153 if (cxxMethodDecl->isVirtual() ) {
154 return true;
156 // ignore macro expansions so we can ignore the IMPL_LINK macros from include/tools/link.hxx
157 // TODO make this more precise
158 if (cxxMethodDecl->getLocation().isMacroID())
159 return true;
161 if (!cxxMethodDecl->getIdentifier())
162 return true;
164 StringRef name = cxxMethodDecl->getName();
165 if (name == "PAGE") // otherwise we have two methods that only differ in their return type
166 return true;
167 // stuff in comphelper I'm not sure about
168 if (name == "CommitImageSubStorage" || name == "CloseEmbeddedObjects" || name == "flush")
169 return true;
171 interestingMethodSet.insert(cxxMethodDecl);
173 return true;
176 bool ConstMethod::VisitCXXThisExpr( const CXXThisExpr* cxxThisExpr )
178 if (!currCXXMethodDecl)
179 return true;
180 if (ignoreLocation(cxxThisExpr))
181 return true;
182 // ignore stuff that forms part of the stable URE interface
183 if (isInUnoIncludeFile(cxxThisExpr->getLocStart()))
184 return true;
185 if (interestingMethodSet.find(currCXXMethodDecl) == interestingMethodSet.end())
186 return true;
187 // no need to check again if we have already eliminated this one
188 if (methodCannotBeConstSet.find(currCXXMethodDecl) != methodCannotBeConstSet.end())
189 return true;
190 if (!checkIfCanBeConst(cxxThisExpr, currCXXMethodDecl))
191 methodCannotBeConstSet.insert(currCXXMethodDecl);
193 return true;
196 // Walk up from a statement that contains a CXXThisExpr, checking if the usage means that the
197 // related CXXMethodDecl can be const.
198 bool ConstMethod::checkIfCanBeConst(const Stmt* stmt, const CXXMethodDecl* cxxMethodDecl)
200 const Stmt* parent = getParentStmt( stmt );
201 if (!parent) {
202 auto parentsRange = compiler.getASTContext().getParents(*stmt);
203 if ( parentsRange.begin() == parentsRange.end())
204 return true;
205 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
206 if (!varDecl)
208 report(
209 DiagnosticsEngine::Warning,
210 "no parent?",
211 stmt->getLocStart())
212 << stmt->getSourceRange();
213 return false;
215 return varDecl->getType()->isIntegralOrEnumerationType()
216 || loplugin::TypeCheck(varDecl->getType()).Pointer().Const()
217 || loplugin::TypeCheck(varDecl->getType()).LvalueReference().Const();
220 if (auto unaryOperator = dyn_cast<UnaryOperator>(parent)) {
221 UnaryOperator::Opcode op = unaryOperator->getOpcode();
222 if (op == UO_AddrOf || op == UO_PreInc || op == UO_PostInc
223 || op == UO_PreDec || op == UO_PostDec) {
224 return false;
226 if (op == UO_Deref) {
227 return checkIfCanBeConst(parent, cxxMethodDecl);
229 return true;
230 } else if (auto binaryOp = dyn_cast<BinaryOperator>(parent)) {
231 BinaryOperator::Opcode op = binaryOp->getOpcode();
232 if (binaryOp->getRHS() == stmt) {
233 return true;
235 if (op == BO_Assign || op == BO_PtrMemD || op == BO_PtrMemI || op == BO_MulAssign
236 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
237 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
238 || op == BO_AndAssign || op == BO_XorAssign || op == BO_OrAssign) {
239 return false;
241 // // for pointer arithmetic need to check parent
242 // if (binaryOp->getType()->isPointerType()) {
243 // return checkIfCanBeConst(parent, cxxMethodDecl);
244 // }
245 return true;
246 } else if (auto constructExpr = dyn_cast<CXXConstructExpr>(parent)) {
247 const CXXConstructorDecl * constructorDecl = constructExpr->getConstructor();
248 for (unsigned i = 0; i < constructExpr->getNumArgs(); ++i) {
249 if (constructExpr->getArg(i) == stmt) {
250 return isPointerOrReferenceToConst(constructorDecl->getParamDecl(i)->getType());
253 return false; // TODO ??
254 } else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent)) {
255 const CXXMethodDecl* calleeMethodDecl = dyn_cast_or_null<CXXMethodDecl>(operatorCallExpr->getDirectCallee());
256 if (calleeMethodDecl) {
257 // unary operator
258 if (calleeMethodDecl->getNumParams() == 0) {
259 // some classes like std::unique_ptr do not do a very good job with their operator-> which is always const
260 if (operatorCallExpr->getOperator() == OO_Arrow || operatorCallExpr->getOperator() == OO_Star) {
261 return checkIfCanBeConst(parent, cxxMethodDecl);
263 return calleeMethodDecl->isConst();
265 // some classes like std::unique_ptr do not do a very good job with their operator[] which is always const
266 if (calleeMethodDecl->getNumParams() == 1 && operatorCallExpr->getArg(0) == stmt) {
267 if (operatorCallExpr->getOperator() == OO_Subscript) {
268 return false;
271 // binary operator
272 if (operatorCallExpr->getArg(0) == stmt) {
273 return calleeMethodDecl->isConst();
275 unsigned const n = std::min(
276 operatorCallExpr->getNumArgs(),
277 calleeMethodDecl->getNumParams());
278 for (unsigned i = 1; i < n; ++i)
279 if (operatorCallExpr->getArg(i) == stmt) {
280 return isPointerOrReferenceToConst(calleeMethodDecl->getParamDecl(i - 1)->getType());
282 } else {
283 const Expr* callee = operatorCallExpr->getCallee()->IgnoreParenImpCasts();
284 const DeclRefExpr* dr = dyn_cast<DeclRefExpr>(callee);
285 const FunctionDecl* calleeFunctionDecl = nullptr;
286 if (dr) {
287 calleeFunctionDecl = dyn_cast<FunctionDecl>(dr->getDecl());
289 if (calleeFunctionDecl) {
290 for (unsigned i = 0; i < operatorCallExpr->getNumArgs(); ++i) {
291 if (operatorCallExpr->getArg(i) == stmt) {
292 return isPointerOrReferenceToConst(calleeFunctionDecl->getParamDecl(i)->getType());
297 return false; // TODO ???
298 } else if (auto callExpr = dyn_cast<CallExpr>(parent)) {
299 QualType functionType = callExpr->getCallee()->getType();
300 if (functionType->isFunctionPointerType()) {
301 functionType = functionType->getPointeeType();
303 if (const FunctionProtoType* prototype = functionType->getAs<FunctionProtoType>()) {
304 // TODO could do better
305 if (prototype->isVariadic()) {
306 return false;
308 if (callExpr->getCallee() == stmt) {
309 return true;
311 for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
312 if (callExpr->getArg(i) == stmt) {
313 return isPointerOrReferenceToConst(prototype->getParamType(i));
317 const FunctionDecl* calleeFunctionDecl = callExpr->getDirectCallee();
318 if (calleeFunctionDecl)
320 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(parent)) {
321 const MemberExpr* memberExpr = dyn_cast<MemberExpr>(stmt);
322 if (memberExpr && memberCallExpr->getImplicitObjectArgument() == memberExpr->getBase())
324 const CXXMethodDecl* calleeMethodDecl = dyn_cast<CXXMethodDecl>(calleeFunctionDecl);
325 // some classes like std::unique_ptr do not do a very good job with their get() which is always const
326 if (calleeMethodDecl->getIdentifier() && calleeMethodDecl->getName() == "get") {
327 return checkIfCanBeConst(parent, cxxMethodDecl);
329 // VclPtr<T>'s implicit conversion to T*
330 if (isa<CXXConversionDecl>(calleeMethodDecl)) {
331 if (loplugin::DeclCheck(calleeMethodDecl->getParent()).Class("OWeakObject").Namespace("cppu").GlobalNamespace())
332 return false;
333 return checkIfCanBeConst(parent, cxxMethodDecl);
335 return calleeMethodDecl->isConst();
338 // TODO could do better
339 if (calleeFunctionDecl->isVariadic()) {
340 return false;
342 if (callExpr->getCallee() == stmt) {
343 return true;
345 for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
346 if (i >= calleeFunctionDecl->getNumParams()) // can happen in template code
347 return false;
348 if (callExpr->getArg(i) == stmt) {
349 return isPointerOrReferenceToConst(calleeFunctionDecl->getParamDecl(i)->getType());
353 return false; // TODO ????
354 // } else if (auto callExpr = dyn_cast<ObjCMessageExpr>(parent)) {
355 // if (callExpr->getInstanceReceiver() == stmt) {
356 // return true;
357 // }
358 // if (auto const method = callExpr->getMethodDecl()) {
359 // // TODO could do better
360 // if (method->isVariadic()) {
361 // return false;
362 // }
363 // assert(method->param_size() == callExpr->getNumArgs());
364 // for (unsigned i = 0; i < callExpr->getNumArgs(); ++i) {
365 // if (callExpr->getArg(i) == stmt) {
366 // return isPointerOrReferenceToConst(
367 // method->param_begin()[i]->getType());
368 // }
369 // }
370 // }
371 // return false; // TODO ????
372 } else if (isa<CXXReinterpretCastExpr>(parent)) {
373 return false;
374 } else if (isa<ImplicitCastExpr>(parent)) {
375 return checkIfCanBeConst(parent, cxxMethodDecl);
376 } else if (isa<CXXStaticCastExpr>(parent)) {
377 return checkIfCanBeConst(parent, cxxMethodDecl);
378 } else if (isa<CXXDynamicCastExpr>(parent)) {
379 return checkIfCanBeConst(parent, cxxMethodDecl);
380 } else if (isa<CXXFunctionalCastExpr>(parent)) {
381 return checkIfCanBeConst(parent, cxxMethodDecl);
382 } else if (isa<CXXConstCastExpr>(parent)) {
383 return false;
384 } else if (isa<CStyleCastExpr>(parent)) {
385 return checkIfCanBeConst(parent, cxxMethodDecl);
386 // } else if (isa<CastExpr>(parent)) { // all other cast expression subtypes
387 // if (auto e = dyn_cast<ExplicitCastExpr>(parent)) {
388 // if (loplugin::TypeCheck(e->getTypeAsWritten()).Void()) {
389 // if (auto const sub = dyn_cast<DeclRefExpr>(
390 // e->getSubExpr()->IgnoreParenImpCasts()))
391 // {
392 // if (sub->getDecl() == cxxMethodDecl) {
393 // return false;
394 // }
395 // }
396 // }
397 // }
398 // return checkIfCanBeConst(parent, cxxMethodDecl);
399 } else if (isa<MemberExpr>(parent)) {
400 return checkIfCanBeConst(parent, cxxMethodDecl);
401 } else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent)) {
402 if (arraySubscriptExpr->getIdx() == stmt)
403 return true;
404 return checkIfCanBeConst(parent, cxxMethodDecl);
405 } else if (isa<ParenExpr>(parent)) {
406 return checkIfCanBeConst(parent, cxxMethodDecl);
407 } else if (auto declStmt = dyn_cast<DeclStmt>(parent)) {
408 for (Decl const * decl : declStmt->decls())
409 if (auto varDecl = dyn_cast<VarDecl>(decl)) {
410 if (varDecl->getInit() == stmt) {
411 auto tc = loplugin::TypeCheck(varDecl->getType());
412 if (tc.LvalueReference() && !tc.LvalueReference().Const())
413 return false;
414 if (tc.Pointer() && !tc.Pointer().Const())
415 return false;
416 return true;
419 // fall through
420 } else if (isa<ReturnStmt>(parent)) {
421 return !isPointerOrReferenceToNonConst(cxxMethodDecl->getReturnType());
422 } else if (isa<InitListExpr>(parent)) {
423 return false; // TODO could be improved
424 } else if (isa<IfStmt>(parent)) {
425 return true;
426 } else if (isa<WhileStmt>(parent)) {
427 return true;
428 } else if (isa<ForStmt>(parent)) {
429 return true;
430 } else if (isa<CompoundStmt>(parent)) {
431 return true;
432 } else if (isa<SwitchStmt>(parent)) {
433 return true;
434 } else if (isa<DoStmt>(parent)) {
435 return true;
436 } else if (isa<CXXDeleteExpr>(parent)) {
437 return false;
438 // } else if (isa<VAArgExpr>(parent)) {
439 // return false;
440 } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
441 return false;
442 } else if (isa<MaterializeTemporaryExpr>(parent)) {
443 return checkIfCanBeConst(parent, cxxMethodDecl);
444 } else if (auto conditionalExpr = dyn_cast<ConditionalOperator>(parent)) {
445 if (conditionalExpr->getCond() == stmt)
446 return true;
447 return checkIfCanBeConst(parent, cxxMethodDecl);
448 // } else if (isa<UnaryExprOrTypeTraitExpr>(parent)) {
449 // return false; // ???
450 } else if (auto cxxNewExpr = dyn_cast<CXXNewExpr>(parent)) {
451 // for (auto pa : cxxNewExpr->placement_arguments())
452 // if (pa == stmt)
453 // return false;
454 return true; // because the Stmt must be a parameter to the expression, probably an array length
455 // } else if (auto lambdaExpr = dyn_cast<LambdaExpr>(parent)) {
456 //// for (auto it = lambdaExpr->capture_begin(); it != lambdaExpr->capture_end(); ++it)
457 //// {
458 //// if (it->capturesVariable() && it->getCapturedVar() == cxxMethodDecl)
459 //// return it->getCaptureKind() != LCK_ByRef;
460 //// }
461 // return true;
462 // } else if (isa<CXXTypeidExpr>(parent)) {
463 // return true;
464 } else if (isa<ParenListExpr>(parent)) {
465 return true;
466 } else if (isa<CXXUnresolvedConstructExpr>(parent)) {
467 return false;
468 // } else if (isa<UnresolvedMemberExpr>(parent)) {
469 // return false;
470 // } else if (isa<PackExpansionExpr>(parent)) {
471 // return false;
472 } else if (isa<ExprWithCleanups>(parent)) {
473 return checkIfCanBeConst(parent, cxxMethodDecl);
474 // } else if (isa<CaseStmt>(parent)) {
475 // return true;
476 // } else if (isa<CXXPseudoDestructorExpr>(parent)) {
477 // return false;
478 // } else if (isa<CXXDependentScopeMemberExpr>(parent)) {
479 // return false;
480 // } else if (isa<ObjCIvarRefExpr>(parent)) {
481 // return checkIfCanBeConst(parent, cxxMethodDecl);
482 } else if (isa<CXXTemporaryObjectExpr>(parent)) {
483 return true;
484 } else if (isa<CXXBindTemporaryExpr>(parent)) {
485 return true;
487 if (parent)
488 parent->dump();
489 // if (cxxMethodDecl)
490 // cxxMethodDecl->dump();
491 report(
492 DiagnosticsEngine::Warning,
493 "oh dear, what can the matter be?",
494 parent->getLocStart())
495 << parent->getSourceRange();
496 return false;
499 bool ConstMethod::isPointerOrReferenceToConst(const QualType& qt) {
500 auto const type = loplugin::TypeCheck(qt);
501 if (type.Pointer()) {
502 return bool(type.Pointer().Const());
503 } else if (type.LvalueReference()) {
504 return bool(type.LvalueReference().Const());
506 return false;
509 bool ConstMethod::isPointerOrReferenceToNonConst(const QualType& qt) {
510 auto const type = loplugin::TypeCheck(qt);
511 if (type.Pointer()) {
512 return !bool(type.Pointer().Const());
513 } else if (type.LvalueReference()) {
514 return !bool(type.LvalueReference().Const());
516 return false;
519 loplugin::Plugin::Registration< ConstMethod > X("constmethod", true);
523 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */