null-deref seen in testing
[LibreOffice.git] / compilerplugins / clang / moveit.cxx
blobac1718a35ac73e9c1b0e55ed845b67b97028e76a
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 * Based on LLVM/Clang.
7 * This file is distributed under the University of Illinois Open Source
8 * License. See LICENSE.TXT for details.
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include "config_clang.h"
17 #include "plugin.hxx"
18 #include "check.hxx"
19 #include <unordered_set>
20 #include <unordered_map>
23 Look for local variables that can be std::move'd into parameters.
25 TODO
26 (*) Ideally we would use a proper data-flow analysis, to detect that the var is dead after this point,
27 like the one in clang at include/clang/Analysis/CFG.h
28 (*) we could expand the set of approved/interesting types
31 namespace
33 class MoveIt : public loplugin::FilteringPlugin<MoveIt>
35 public:
36 explicit MoveIt(loplugin::InstantiationData const& data)
37 : FilteringPlugin(data)
41 virtual bool preRun() override
43 std::string fn(handler.getMainFileName());
44 loplugin::normalizeDotDotInFilePath(fn);
45 // false +, needs to check if the moved-from var is outside a loop
46 if (loplugin::hasPathnamePrefix(
47 fn, SRCDIR "/drawinglayer/source/primitive3d/sdrdecompositiontools3d.cxx"))
48 return false;
49 if (loplugin::hasPathnamePrefix(
50 fn, SRCDIR "/drawinglayer/source/processor2d/vclmetafileprocessor2d.cxx"))
51 return false;
52 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/drawinglayer/source/tools/emfphelperdata.cxx"))
53 return false;
54 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/sc/source/core/tool/reftokenhelper.cxx"))
55 return false;
56 if (loplugin::hasPathnamePrefix(fn,
57 SRCDIR "/svx/source/svdraw/svdotextpathdecomposition.cxx"))
58 return false;
59 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svx/source/svdraw/svdcrtv.cxx"))
60 return false;
61 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svx/source/table/tablehandles.cxx"))
62 return false;
63 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/svx/source/xoutdev/xpool.cxx"))
64 return false;
65 return true;
68 virtual void run() override
70 if (preRun())
71 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
72 for (auto const& pair : m_possibles)
74 auto const& possible = pair.second;
75 report(DiagnosticsEngine::Warning, "can std::move this var into this param",
76 possible.argExpr->getBeginLoc());
77 report(DiagnosticsEngine::Note, "passing to this param",
78 possible.calleeParmVarDecl->getBeginLoc());
79 report(DiagnosticsEngine::Note, "local var declared here",
80 possible.localVarDecl->getBeginLoc());
81 report(DiagnosticsEngine::Note, "type declared here",
82 possible.recordDecl->getBeginLoc());
86 bool VisitCXXMemberCallExpr(const CXXMemberCallExpr*);
87 bool VisitCXXConstructExpr(const CXXConstructExpr*);
88 bool VisitDeclRefExpr(const DeclRefExpr*);
90 private:
91 bool isInterestingType(QualType);
92 struct Possible
94 const Expr* argExpr;
95 const ParmVarDecl* calleeParmVarDecl;
96 const VarDecl* localVarDecl;
97 const CXXRecordDecl* recordDecl;
98 const DeclRefExpr* dre;
100 std::unordered_map<const VarDecl*, Possible> m_possibles;
101 std::unordered_set<const VarDecl*> m_rejected;
104 bool MoveIt::VisitCXXMemberCallExpr(const CXXMemberCallExpr* topExpr)
106 if (ignoreLocation(topExpr))
107 return true;
108 const CXXMethodDecl* methodDecl = topExpr->getMethodDecl();
109 if (!methodDecl)
110 return true;
112 unsigned len = std::min(topExpr->getNumArgs(), methodDecl->getNumParams());
113 for (unsigned i = 0; i < len; ++i)
115 // check if the parameter is a moveable type
116 const ParmVarDecl* parmVarDecl = methodDecl->getParamDecl(i);
117 if (!parmVarDecl->getType()->isRecordType())
118 continue;
119 const CXXRecordDecl* recordDecl
120 = dyn_cast<CXXRecordDecl>(parmVarDecl->getType()->getAsRecordDecl());
121 if (!recordDecl || !recordDecl->hasMoveConstructor() || recordDecl->isTriviallyCopyable())
122 continue;
123 if (!isInterestingType(parmVarDecl->getType()))
124 continue;
126 // check if (a) we're making a copy to pass to the param and (b) we're making a copy of a local var
127 const Expr* argExpr = topExpr->getArg(i);
128 if (!argExpr)
129 continue;
130 const CXXConstructExpr* argSubExpr = dyn_cast<CXXConstructExpr>(argExpr->IgnoreImplicit());
131 if (!argSubExpr || argSubExpr->getNumArgs() == 0)
132 continue;
133 const DeclRefExpr* dre = dyn_cast<DeclRefExpr>(argSubExpr->getArg(0)->IgnoreImplicit());
134 if (!dre)
135 continue;
136 const VarDecl* localVarDecl = dyn_cast<VarDecl>(dre->getDecl());
137 if (!localVarDecl || localVarDecl->getType()->isReferenceType()
138 || localVarDecl->getType()->isPointerType() || !localVarDecl->hasLocalStorage())
139 continue;
140 // because sometimes the parameter type is some obscured STL thing
141 if (!isInterestingType(localVarDecl->getType()))
142 continue;
144 if (m_rejected.count(localVarDecl))
145 continue;
147 m_possibles[localVarDecl] = Possible{ argExpr, parmVarDecl, localVarDecl, recordDecl, dre };
150 return true;
153 bool MoveIt::VisitCXXConstructExpr(const CXXConstructExpr* topExpr)
155 if (ignoreLocation(topExpr))
156 return true;
157 if (isa<CXXTemporaryObjectExpr>(topExpr))
158 return true;
159 const CXXConstructorDecl* methodDecl = topExpr->getConstructor();
160 if (!methodDecl)
161 return true;
163 unsigned len = std::min(topExpr->getNumArgs(), methodDecl->getNumParams());
164 for (unsigned i = 0; i < len; ++i)
166 // check if the parameter is a moveable type
167 const ParmVarDecl* parmVarDecl = methodDecl->getParamDecl(i);
168 if (!parmVarDecl->getType()->isRecordType())
169 continue;
170 const CXXRecordDecl* recordDecl
171 = dyn_cast<CXXRecordDecl>(parmVarDecl->getType()->getAsRecordDecl());
172 if (!recordDecl || !recordDecl->hasMoveConstructor() || recordDecl->isTriviallyCopyable())
173 continue;
174 if (!isInterestingType(parmVarDecl->getType()))
175 continue;
177 // check if (a) we're making a copy to pass to the param and (b) we're making a copy of a local var
178 const Expr* argExpr = topExpr->getArg(i);
179 if (!argExpr)
180 continue;
181 const CXXConstructExpr* argSubExpr = dyn_cast<CXXConstructExpr>(argExpr->IgnoreImplicit());
182 if (!argSubExpr || argSubExpr->getNumArgs() == 0)
183 continue;
184 const DeclRefExpr* dre = dyn_cast<DeclRefExpr>(argSubExpr->getArg(0)->IgnoreImplicit());
185 if (!dre)
186 continue;
187 const VarDecl* localVarDecl = dyn_cast<VarDecl>(dre->getDecl());
188 if (!localVarDecl || localVarDecl->getType()->isReferenceType()
189 || localVarDecl->getType()->isPointerType() || !localVarDecl->hasLocalStorage())
190 continue;
191 // because sometimes the parameter type is some obscured STL thing
192 if (!isInterestingType(localVarDecl->getType()))
193 continue;
195 if (m_rejected.count(localVarDecl))
196 continue;
198 m_possibles[localVarDecl] = Possible{ argExpr, parmVarDecl, localVarDecl, recordDecl, dre };
201 return true;
204 /// If we have pushed a possibility, and then we see that possibility again,
205 /// then we cannot std::move it, because it is being referenced after being moved.
207 bool MoveIt::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
209 if (ignoreLocation(declRefExpr))
210 return true;
211 const VarDecl* localVarDecl = dyn_cast<VarDecl>(declRefExpr->getDecl());
212 if (!localVarDecl)
213 return true;
214 auto it = m_possibles.find(localVarDecl);
215 if (it == m_possibles.end())
216 return true;
217 // ignoring the DeclRefExpr* for the expression where we found the Possible
218 if (it->second.dre == declRefExpr)
219 return true;
220 m_possibles.erase(it);
221 m_rejected.insert(localVarDecl);
222 return true;
225 /// Exclude boring types, so that we don't generate too many low-value conversions.
226 /// e.g. for now I ignore ref-counted types like Sequence and OUString and css::uno::Reference,
227 /// because that generates too many changes
228 bool MoveIt::isInterestingType(QualType qt)
230 if (qt->isEnumeralType())
231 return false;
232 if (!qt->isRecordType())
233 return false;
235 auto tc = loplugin::TypeCheck(qt);
237 // clang-format off
238 return !tc.ClassOrStruct("iterator")
239 && !tc.ClassOrStruct("const_iterator")
240 && !tc.Typedef("iterator")
241 && !tc.Typedef("const_iterator")
242 && !tc.Class("_Safe_iterator")
243 && !tc.Typedef("string")
244 && !tc.ClassOrStruct("shared_ptr").StdNamespace()
245 && !tc.ClassOrStruct("shared_ptr").Namespace("boost")
246 && !tc.Class("B2DHomMatrix").Namespace("basegfx").GlobalNamespace()
247 && !tc.Class("Pipe").Namespace("osl")
248 && !tc.Class("Any").Namespace("uno")
249 && !tc.Class("TypeDescription").Namespace("uno")
250 && !tc.Class("UnoInterfaceReference").Namespace("uno")
251 && !tc.Class("ByteSequence").Namespace("rtl").GlobalNamespace()
252 && !tc.Class("OUString").Namespace("rtl").GlobalNamespace()
253 && !tc.Class("OString").Namespace("rtl").GlobalNamespace()
254 && !tc.Class("BinaryAny")
255 && !tc.Class("Reference")
256 && !tc.Class("SvRef").Namespace("tools").GlobalNamespace()
257 && !tc.ClassOrStruct("sk_sp") // skia shared pointer
258 && !tc.ClassOrStruct("VclPtr")
259 && !tc.Typedef("IterString") // SalInstanceTreeView::IterString
260 && !tc.Typedef("svtree_render_args")
261 && !tc.Typedef("render_args") // weld::ComboBox::render_args
263 // clang-format on
266 /// off by default because each warning needs to be hand checked to ensure it is not a false+
267 loplugin::Plugin::Registration<MoveIt> moveit("moveit", false);
269 } // namespace
271 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */