Version 7.6.3.2-android, tag libreoffice-7.6.3.2-android
[LibreOffice.git] / compilerplugins / clang / writeonlyvars.cxx
blob068d4058e09daa3a38dcceb0db56991c5ca64300
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 #if !defined _WIN32 //TODO, #include <sys/file.h>
12 #include <cassert>
13 #include <string>
14 #include <iostream>
15 #include <fstream>
16 #include <unordered_set>
17 #include <vector>
18 #include <algorithm>
19 #include <sys/file.h>
20 #include <unistd.h>
22 #include "config_clang.h"
24 #include "plugin.hxx"
25 #include "check.hxx"
26 #include "compat.hxx"
28 #include "clang/AST/ParentMapContext.h"
30 /**
31 Finds variables that are effectively write-only.
33 Largely the same as the unusedfields.cxx loplugin.
36 namespace
38 struct MyVarInfo
40 const VarDecl* varDecl;
41 std::string parent;
42 std::string varName;
43 std::string varType;
44 std::string sourceLocation;
46 bool operator<(const MyVarInfo& lhs, const MyVarInfo& rhs)
48 return std::tie(lhs.parent, lhs.varName) < std::tie(rhs.parent, rhs.varName);
51 // try to limit the voluminous output a little
52 static std::set<MyVarInfo> readFromSet;
53 static std::set<MyVarInfo> writeToSet;
54 static std::set<MyVarInfo> definitionSet;
56 /**
57 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
59 class CallerWrapper
61 const CallExpr* m_callExpr;
62 const CXXConstructExpr* m_cxxConstructExpr;
64 public:
65 CallerWrapper(const CallExpr* callExpr)
66 : m_callExpr(callExpr)
67 , m_cxxConstructExpr(nullptr)
70 CallerWrapper(const CXXConstructExpr* cxxConstructExpr)
71 : m_callExpr(nullptr)
72 , m_cxxConstructExpr(cxxConstructExpr)
75 unsigned getNumArgs() const
77 return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs();
79 const Expr* getArg(unsigned i) const
81 return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i);
84 class CalleeWrapper
86 const FunctionDecl* m_calleeFunctionDecl = nullptr;
87 const CXXConstructorDecl* m_cxxConstructorDecl = nullptr;
88 const FunctionProtoType* m_functionPrototype = nullptr;
90 public:
91 explicit CalleeWrapper(const FunctionDecl* calleeFunctionDecl)
92 : m_calleeFunctionDecl(calleeFunctionDecl)
95 explicit CalleeWrapper(const CXXConstructExpr* cxxConstructExpr)
96 : m_cxxConstructorDecl(cxxConstructExpr->getConstructor())
99 explicit CalleeWrapper(const FunctionProtoType* functionPrototype)
100 : m_functionPrototype(functionPrototype)
103 unsigned getNumParams() const
105 if (m_calleeFunctionDecl)
106 return m_calleeFunctionDecl->getNumParams();
107 else if (m_cxxConstructorDecl)
108 return m_cxxConstructorDecl->getNumParams();
109 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
110 // FunctionProtoType will assert if we call getParamTypes() and it has no params
111 return 0;
112 else
113 return m_functionPrototype->getParamTypes().size();
115 const QualType getParamType(unsigned i) const
117 if (m_calleeFunctionDecl)
118 return m_calleeFunctionDecl->getParamDecl(i)->getType();
119 else if (m_cxxConstructorDecl)
120 return m_cxxConstructorDecl->getParamDecl(i)->getType();
121 else
122 return m_functionPrototype->getParamTypes()[i];
124 std::string getNameAsString() const
126 if (m_calleeFunctionDecl)
127 return m_calleeFunctionDecl->getNameAsString();
128 else if (m_cxxConstructorDecl)
129 return m_cxxConstructorDecl->getNameAsString();
130 else
131 return "";
133 CXXMethodDecl const* getAsCXXMethodDecl() const
135 if (m_calleeFunctionDecl)
136 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
137 return nullptr;
141 class WriteOnlyVars : public loplugin::FilteringPlugin<WriteOnlyVars>
143 public:
144 explicit WriteOnlyVars(loplugin::InstantiationData const& data)
145 : FilteringPlugin(data)
149 virtual void run() override;
151 bool shouldVisitTemplateInstantiations() const { return true; }
152 bool shouldVisitImplicitCode() const { return true; }
154 bool VisitVarDecl(const VarDecl*);
155 bool VisitDeclRefExpr(const DeclRefExpr*);
156 bool TraverseIfStmt(IfStmt*);
158 private:
159 MyVarInfo niceName(const VarDecl*);
160 void checkIfReadFrom(const VarDecl* varDecl, const Expr* memberExpr);
161 void checkIfWrittenTo(const VarDecl* varDecl, const Expr* memberExpr);
162 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl* calleeMethodDecl);
163 bool IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child, CallerWrapper callExpr,
164 CalleeWrapper calleeFunctionDecl);
165 compat::optional<CalleeWrapper> getCallee(CallExpr const*);
167 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
168 // we store the parent function on the way down the AST.
169 FunctionDecl* insideFunctionDecl = nullptr;
170 std::vector<VarDecl const*> insideConditionalCheckOfMemberSet;
173 void WriteOnlyVars::run()
175 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
177 if (!isUnitTestMode())
179 StringRef fn(handler.getMainFileName());
180 // playing paging-in games with volatile
181 if (loplugin::isSamePathname(fn, SRCDIR "/sal/osl/unx/file.cxx"))
182 return;
183 // playing paging-in games with volatile
184 if (loplugin::isSamePathname(fn, SRCDIR "/desktop/unx/source/file_image_unx.c"))
185 return;
186 // false+
187 if (loplugin::isSamePathname(fn, SRCDIR "/store/source/storpage.cxx"))
188 return;
189 if (fn.contains("/qa/"))
190 return;
191 if (fn.contains("/vcl/workben/"))
192 return;
193 // preload
194 if (loplugin::isSamePathname(fn, SRCDIR "/cppuhelper/source/servicemanager.cxx"))
195 return;
196 // doing a "free items outside lock" thing
197 if (loplugin::isSamePathname(fn, SRCDIR "/unotools/source/config/itemholder1.cxx"))
198 return;
199 if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/config/itemholder2.cxx"))
200 return;
201 if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/config/itemholder2.cxx"))
202 return;
203 // doing a "keep objects alive" thing
204 if (loplugin::isSamePathname(fn, SRCDIR "/jvmfwk/source/framework.cxx"))
205 return;
206 if (loplugin::isSamePathname(fn, SRCDIR "/jvmfwk/plugins/sunmajor/pluginlib/util.cxx"))
207 return;
208 // debug code
209 if (loplugin::isSamePathname(fn, SRCDIR "/svl/source/items/style.cxx"))
210 return;
211 // ok
212 if (loplugin::isSamePathname(fn, SRCDIR "/stoc/source/inspect/introspection.cxx"))
213 return;
214 if (loplugin::isSamePathname(fn, SRCDIR "/package/source/zippackage/ZipPackage.cxx"))
215 return;
216 if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/hwpreader.cxx"))
217 return;
218 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/treelist/transfer.cxx"))
219 return;
220 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/app/brand.cxx"))
221 return;
222 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/igif/gifread.cxx"))
223 return;
224 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/gdi/metaact.cxx"))
225 return;
226 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/fontsubset/sft.cxx"))
227 return;
228 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/ipdf/pdfdocument.cxx"))
229 return;
230 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/ipdf/pdfdocument2.cxx"))
231 return;
232 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/generic/app/sm.cxx"))
233 return;
234 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/jpeg/JpegWriter.cxx"))
235 return;
236 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/generic/dtrans/X11_selection.cxx"))
237 return;
238 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/source/filter/jpeg/jpegc.cxx"))
239 return;
240 if (loplugin::isSamePathname(fn, SRCDIR "/vcl/unx/generic/window/FWS.cxx"))
241 return;
242 if (loplugin::isSamePathname(fn, SRCDIR "/toolkit/source/awt/vclxspinbutton.cxx"))
243 return;
244 if (loplugin::isSamePathname(fn, SRCDIR "/toolkit/source/controls/formattedcontrol.cxx"))
245 return;
246 if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/config/helpopt.cxx"))
247 return;
248 if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/filter/SvFilterOptionsDialog.cxx"))
249 return;
250 if (loplugin::isSamePathname(fn, SRCDIR "/svtools/source/java/javainteractionhandler.cxx"))
251 return;
252 if (loplugin::isSamePathname(fn, SRCDIR "/basic/source/classes/sbunoobj.cxx"))
253 return;
254 if (loplugin::isSamePathname(fn,
255 SRCDIR "/accessibility/source/standard/vclxaccessiblebox.cxx"))
256 return;
257 if (loplugin::isSamePathname(fn, SRCDIR "/cppcanvas/source/mtfrenderer/implrenderer.cxx"))
258 return;
259 if (loplugin::isSamePathname(fn, SRCDIR "/sfx2/source/doc/guisaveas.cxx"))
260 return;
261 if (loplugin::isSamePathname(fn, SRCDIR "/sfx2/source/appl/newhelp.cxx"))
262 return;
263 if (loplugin::isSamePathname(fn, SRCDIR "/sfx2/source/control/thumbnailview.cxx"))
264 return;
265 if (loplugin::isSamePathname(fn, SRCDIR "/sfx2/source/control/recentdocsview.cxx"))
266 return;
267 if (loplugin::isSamePathname(fn, SRCDIR "/sfx2/source/view/viewfrm.cxx"))
268 return;
269 if (loplugin::isSamePathname(fn, SRCDIR "/framework/source/services/desktop.cxx"))
270 return;
271 if (loplugin::isSamePathname(fn, SRCDIR
272 "/framework/source/uielement/generictoolbarcontroller.cxx"))
273 return;
274 if (loplugin::isSamePathname(fn, SRCDIR
275 "/framework/source/uielement/complextoolbarcontroller.cxx"))
276 return;
277 if (loplugin::isSamePathname(fn,
278 SRCDIR "/framework/source/interaction/quietinteraction.cxx"))
279 return;
280 if (loplugin::isSamePathname(fn, SRCDIR "/editeng/source/editeng/editdoc.cxx"))
281 return;
282 if (loplugin::isSamePathname(fn, SRCDIR "/editeng/source/editeng/impedit4.cxx"))
283 return;
284 if (loplugin::isSamePathname(fn, SRCDIR "/editeng/source/editeng/editobj.cxx"))
285 return;
286 if (loplugin::isSamePathname(fn, SRCDIR "/editeng/source/items/frmitems.cxx"))
287 return;
288 if (loplugin::isSamePathname(fn, SRCDIR "/binaryurp/source/bridge.cxx"))
289 return;
290 if (loplugin::isSamePathname(fn, SRCDIR "/svx/source/tbxctrls/fontworkgallery.cxx"))
291 return;
292 if (loplugin::isSamePathname(fn, SRCDIR "/basctl/source/basicide/moduldl2.cxx"))
293 return;
294 if (loplugin::isSamePathname(fn, SRCDIR "/canvas/source/cairo/cairo_spritecanvas.cxx"))
295 return;
296 if (loplugin::isSamePathname(fn, SRCDIR "/chart2/source/tools/DiagramHelper.cxx"))
297 return;
298 if (loplugin::isSamePathname(fn,
299 SRCDIR "/chart2/source/tools/ExplicitCategoriesProvider.cxx"))
300 return;
301 if (loplugin::isSamePathname(fn, SRCDIR "/chart2/source/tools/LegendHelper.cxx"))
302 return;
303 if (loplugin::isSamePathname(fn, SRCDIR "/chart2/source/tools/OPropertySet.cxx"))
304 return;
305 if (loplugin::isSamePathname(fn, SRCDIR "/chart2/source/tools/CommonConverters.cxx"))
306 return;
307 if (loplugin::isSamePathname(
309 SRCDIR "/chart2/source/controller/chartapiwrapper/WrappedNumberFormatProperty.cxx"))
310 return;
311 if (loplugin::isSamePathname(fn, SRCDIR "/chart2/source/tools/DataSourceHelper.cxx"))
312 return;
313 if (loplugin::isSamePathname(fn, SRCDIR "/oox/source/export/shapes.cxx"))
314 return;
315 if (loplugin::isSamePathname(fn, SRCDIR "/oox/source/export/chartexport.cxx"))
316 return;
317 if (loplugin::isSamePathname(fn,
318 SRCDIR "/filter/source/storagefilterdetect/filterdetect.cxx"))
319 return;
320 if (loplugin::isSamePathname(fn, SRCDIR "/filter/source/pdf/pdfexport.cxx"))
321 return;
322 if (loplugin::isSamePathname(fn, SRCDIR "/filter/source/svg/svgexport.cxx"))
323 return;
324 if (loplugin::isSamePathname(fn, SRCDIR "/filter/source/msfilter/svdfppt.cxx"))
325 return;
326 if (loplugin::isSamePathname(fn, SRCDIR
327 "/dbaccess/source/core/recovery/subcomponentrecovery.cxx"))
328 return;
329 if (loplugin::isSamePathname(fn, SRCDIR
330 "/dbaccess/source/core/dataaccess/documentcontainer.cxx"))
331 return;
332 if (loplugin::isSamePathname(fn, SRCDIR
333 "/dbaccess/source/core/dataaccess/databasedocument.cxx"))
334 return;
335 if (loplugin::isSamePathname(fn,
336 SRCDIR "/dbaccess/source/ui/browser/genericcontroller.cxx"))
337 return;
338 if (loplugin::isSamePathname(fn, SRCDIR "/ucb/source/core/ucbcmds.cxx"))
339 return;
340 if (loplugin::isSamePathname(fn,
341 SRCDIR "/desktop/source/deployment/manager/dp_manager.cxx"))
342 return;
343 if (loplugin::isSamePathname(fn, SRCDIR
344 "/desktop/source/deployment/registry/package/dp_package.cxx"))
345 return;
346 if (loplugin::isSamePathname(fn, SRCDIR "/desktop/source/lib/init.cxx"))
347 return;
348 if (loplugin::isSamePathname(fn, SRCDIR
349 "/extensions/source/propctrlr/formcomponenthandler.cxx"))
350 return;
351 if (loplugin::isSamePathname(fn, SRCDIR "/embeddedobj/source/general/docholder.cxx"))
352 return;
353 if (loplugin::isSamePathname(fn, SRCDIR
354 "/extensions/source/propctrlr/stringrepresentation.cxx"))
355 return;
356 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpcontent.cxx"))
357 return;
358 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpdivinfo.cxx"))
359 return;
360 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpdoc.cxx"))
361 return;
362 if (loplugin::isSamePathname(fn, SRCDIR "/filter/source/pdf/impdialog.cxx"))
363 return;
364 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwplayout.cxx"))
365 return;
366 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpoleobject.cxx"))
367 return;
368 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwprowlayout.cxx"))
369 return;
370 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpfoundry.cxx"))
371 return;
372 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpparastyle.cxx"))
373 return;
374 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpnotes.cxx"))
375 return;
376 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpfont.cxx"))
377 return;
378 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwptblcell.cxx"))
379 return;
380 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpusrdicts.cxx"))
381 return;
382 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwpverdocument.cxx"))
383 return;
384 if (loplugin::isSamePathname(fn, SRCDIR "/lotuswordpro/source/filter/lwptblformula.cxx"))
385 return;
386 if (loplugin::isSamePathname(fn, SRCDIR "/vbahelper/source/vbahelper/vbafontbase.cxx"))
387 return;
388 if (loplugin::isSamePathname(fn, SRCDIR "/vbahelper/source/vbahelper/vbadocumentbase.cxx"))
389 return;
390 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/docshell/docsh8.cxx"))
391 return;
392 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/docshell/docsh6.cxx"))
393 return;
394 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/core/data/table3.cxx"))
395 return;
396 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/unoobj/cellsuno.cxx"))
397 return;
398 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/excel/xelink.cxx"))
399 return;
400 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/filter/lotus/lotus.cxx"))
401 return;
402 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/vba/vbaworkbooks.cxx"))
403 return;
404 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/vba/vbaworksheets.cxx"))
405 return;
406 if (loplugin::isSamePathname(fn, SRCDIR "/sc/source/ui/vba/vbarange.cxx"))
407 return;
408 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/view/drviews2.cxx"))
409 return;
410 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/filter/ppt/pptin.cxx"))
411 return;
412 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/app/sdxfer.cxx"))
413 return;
414 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/ui/view/drviewsf.cxx"))
415 return;
416 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/filter/xml/sdxmlwrp.cxx"))
417 return;
418 if (loplugin::isSamePathname(fn, SRCDIR "/sd/source/filter/html/pubdlg.cxx"))
419 return;
420 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/core/txtnode/thints.cxx"))
421 return;
422 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/core/doc/docbm.cxx"))
423 return;
424 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/core/crsr/crsrsh.cxx"))
425 return;
426 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/filter/xml/swxml.cxx"))
427 return;
428 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/core/doc/docredln.cxx"))
429 return;
430 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/filter/ww8/ww8par2.cxx"))
431 return;
432 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/uibase/shells/drformsh.cxx"))
433 return;
434 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/filter/ww8/ww8par6.cxx"))
435 return;
436 if (loplugin::isSamePathname(fn, SRCDIR "/sw/source/ui/dbui/dbinsdlg.cxx"))
437 return;
438 if (loplugin::isSamePathname(fn, SRCDIR "/sdext/source/minimizer/impoptimizer.cxx"))
439 return;
440 if (loplugin::isSamePathname(fn, SRCDIR "/sdext/source/presenter/PresenterTheme.cxx"))
441 return;
442 if (loplugin::isSamePathname(fn, SRCDIR "/sdext/source/pdfimport/wrapper/wrapper.cxx"))
443 return;
444 if (loplugin::isSamePathname(fn, SRCDIR
445 "/slideshow/source/engine/animationnodes/generateevent.cxx"))
446 return;
447 if (loplugin::isSamePathname(fn, SRCDIR "/starmath/source/mathmlimport.cxx"))
448 return;
449 if (loplugin::isSamePathname(fn, SRCDIR "/starmath/source/eqnolefilehdr.cxx"))
450 return;
451 if (loplugin::isSamePathname(fn, SRCDIR "/svgio/source/svgreader/svgmarkernode.cxx"))
452 return;
453 if (loplugin::isSamePathname(fn, SRCDIR "/uui/source/iahndl-locking.cxx"))
454 return;
455 if (loplugin::isSamePathname(fn, SRCDIR
456 "/shell/source/sessioninstall/SyncDbusSessionHelper.cxx"))
457 return;
458 if (loplugin::isSamePathname(fn,
459 SRCDIR "/slideshow/source/engine/opengl/TransitionerImpl.cxx"))
460 return;
461 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/FormattedField.cxx"))
462 return;
463 if (loplugin::isSamePathname(fn, SRCDIR "/forms/source/component/DatabaseForm.cxx"))
464 return;
465 if (loplugin::isSamePathname(fn,
466 SRCDIR "/reportdesign/source/ui/report/ReportController.cxx"))
467 return;
468 if (loplugin::hasPathnamePrefix(fn, SRCDIR "/test/"))
469 return;
470 if (loplugin::isSamePathname(fn, SRCDIR "/i18npool/source/localedata/LocaleNode.cxx"))
471 return;
473 // yynerrs?
474 if (loplugin::isSamePathname(fn, SRCDIR "/hwpfilter/source/grammar.cxx"))
475 return;
477 for (MyVarInfo const& v : definitionSet)
479 bool read = readFromSet.find(v) != readFromSet.end();
480 bool write = writeToSet.find(v) != writeToSet.end();
481 if (!read && write)
482 report(DiagnosticsEngine::Warning, "write-only %0", v.varDecl->getBeginLoc())
483 << v.varName;
486 else
488 for (const MyVarInfo& s : readFromSet)
489 report(DiagnosticsEngine::Warning, "read %0", s.varDecl->getBeginLoc()) << s.varName;
490 for (const MyVarInfo& s : writeToSet)
491 report(DiagnosticsEngine::Warning, "write %0", s.varDecl->getBeginLoc()) << s.varName;
495 MyVarInfo WriteOnlyVars::niceName(const VarDecl* varDecl)
497 MyVarInfo aInfo;
499 aInfo.varDecl = varDecl->getCanonicalDecl();
500 aInfo.varName = varDecl->getNameAsString();
501 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
502 size_t idx = aInfo.varName.find(SRCDIR);
503 if (idx != std::string::npos)
505 aInfo.varName = aInfo.varName.replace(idx, strlen(SRCDIR), "");
507 aInfo.varType = varDecl->getType().getAsString();
509 SourceLocation expansionLoc
510 = compiler.getSourceManager().getExpansionLoc(varDecl->getLocation());
511 StringRef filename = getFilenameOfLocation(expansionLoc);
512 aInfo.sourceLocation
513 = std::string(filename.substr(strlen(SRCDIR) + 1)) + ":"
514 + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
515 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
516 aInfo.parent = filename.str();
518 return aInfo;
521 static bool contains(std::string const& s, std::string const& needle)
523 return s.find(needle) != std::string::npos;
526 bool WriteOnlyVars::VisitVarDecl(const VarDecl* varDecl)
528 if (varDecl->isImplicit() || varDecl->isExternC() || isa<ParmVarDecl>(varDecl))
529 return true;
530 auto tc = loplugin::TypeCheck(varDecl->getType());
531 if (tc.Pointer() || tc.LvalueReference() || tc.Class("shared_ptr").StdNamespace()
532 || tc.Class("unique_ptr").StdNamespace())
533 return true;
534 if (tc.Typedef("BitmapScopedWriteAccess"))
535 return true;
536 std::string typeName = varDecl->getType().getAsString();
537 if (contains(typeName, "Guard") || contains(typeName, "Reader") || contains(typeName, "Stream")
538 || contains(typeName, "Parser") || contains(typeName, "Codec")
539 || contains(typeName, "Exception"))
540 return true;
541 varDecl = varDecl->getCanonicalDecl();
542 if (!varDecl->getLocation().isValid() || ignoreLocation(varDecl))
543 return true;
544 if (!compiler.getSourceManager().isInMainFile(varDecl->getLocation()))
545 return true;
546 if (compiler.getSourceManager().isMacroBodyExpansion(varDecl->getBeginLoc()))
547 return true;
548 if (compiler.getSourceManager().isMacroArgExpansion(varDecl->getBeginLoc()))
549 return true;
550 // ignore stuff that forms part of the stable URE interface
551 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
552 return true;
554 definitionSet.insert(niceName(varDecl));
555 return true;
558 static char easytolower(char in)
560 if (in <= 'Z' && in >= 'A')
561 return in - ('Z' - 'z');
562 return in;
565 bool startswith(const std::string& rStr, const char* pSubStr)
567 return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
570 bool WriteOnlyVars::TraverseIfStmt(IfStmt* ifStmt)
572 VarDecl const* varDecl = nullptr;
573 Expr const* cond = ifStmt->getCond()->IgnoreParenImpCasts();
574 if (auto declRefExpr = dyn_cast<DeclRefExpr>(cond))
576 if ((varDecl = dyn_cast<VarDecl>(declRefExpr->getDecl())))
577 insideConditionalCheckOfMemberSet.push_back(varDecl);
579 bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
580 if (varDecl)
581 insideConditionalCheckOfMemberSet.pop_back();
582 return ret;
585 void WriteOnlyVars::checkIfReadFrom(const VarDecl* varDecl, const Expr* memberExpr)
587 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
588 const Stmt* child = memberExpr;
589 const Stmt* parent
590 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
591 // walk up the tree until we find something interesting
592 bool bPotentiallyReadFrom = false;
593 bool bDump = false;
594 auto walkupUp = [&]() {
595 child = parent;
596 auto parentsRange = compiler.getASTContext().getParents(*parent);
597 parent = parentsRange.begin() == parentsRange.end() ? nullptr
598 : parentsRange.begin()->get<Stmt>();
602 if (!parent)
604 // check if we're inside a CXXCtorInitializer or a VarDecl
605 auto parentsRange = compiler.getASTContext().getParents(*child);
606 if (parentsRange.begin() != parentsRange.end())
608 const Decl* decl = parentsRange.begin()->get<Decl>();
609 if (decl && (isa<CXXConstructorDecl>(decl) || isa<VarDecl>(decl)))
610 bPotentiallyReadFrom = true;
612 if (!bPotentiallyReadFrom)
613 return;
614 break;
616 if (isa<CXXReinterpretCastExpr>(parent))
618 // once we see one of these, there is not much useful we can know
619 bPotentiallyReadFrom = true;
620 break;
622 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
623 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
624 || isa<ExprWithCleanups>(parent))
626 walkupUp();
628 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
630 UnaryOperator::Opcode op = unaryOperator->getOpcode();
631 if (memberExpr->getType()->isArrayType() && op == UO_Deref)
633 // ignore, deref'ing an array does not count as a read
635 else if (op == UO_AddrOf || op == UO_Deref || op == UO_Plus || op == UO_Minus
636 || op == UO_Not || op == UO_LNot)
638 bPotentiallyReadFrom = true;
639 break;
641 /* The following are technically reads, but from a code-sense they're more of a write/modify, so
642 ignore them to find interesting fields that only modified, not usefully read:
643 UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec
644 But we still walk up in case the result of the expression is used in a read sense.
646 walkupUp();
648 else if (auto caseStmt = dyn_cast<CaseStmt>(parent))
650 bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child;
651 break;
653 else if (auto ifStmt = dyn_cast<IfStmt>(parent))
655 bPotentiallyReadFrom = ifStmt->getCond() == child;
656 break;
658 else if (auto doStmt = dyn_cast<DoStmt>(parent))
660 bPotentiallyReadFrom = doStmt->getCond() == child;
661 break;
663 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
665 if (arraySubscriptExpr->getIdx() == child)
667 bPotentiallyReadFrom = true;
668 break;
670 walkupUp();
672 else if (auto callExpr = dyn_cast<CXXMemberCallExpr>(parent))
674 // check for calls to ReadXXX() type methods and the operator>>= methods on Any.
675 auto callee = getCallee(callExpr);
676 if (callee && *callExpr->child_begin() == child)
678 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
679 // which we could scatter around.
680 std::string name = callee->getNameAsString();
681 std::transform(name.begin(), name.end(), name.begin(), easytolower);
682 if (startswith(name, "read"))
683 // this is a write-only call
685 else if (startswith(name, "emplace") || name == "insert" || name == "erase"
686 || name == "remove" || name == "remove_if" || name == "sort"
687 || name == "push_back" || name == "pop_back" || name == "push_front"
688 || name == "pop_front" || name == "reserve" || name == "resize"
689 || name == "clear" || name == "fill")
690 // write-only modifications to collections
692 else if (name.find(">>=") != std::string::npos && callExpr->getArg(1) == child)
693 // this is a write-only call
695 else if (name == "dispose" || name == "disposeAndClear" || name == "swap")
696 // we're abusing the write-only analysis here to look for vars which don't have anything useful
697 // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
698 // and VclPtr::disposeAndClear
700 else
701 bPotentiallyReadFrom = true;
703 else
704 bPotentiallyReadFrom = true;
705 break;
707 else if (auto callExpr = dyn_cast<CallExpr>(parent))
709 // check for calls to ReadXXX() type methods and the operator>>= methods on Any.
710 auto callee = getCallee(callExpr);
711 if (callee)
713 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
714 // which we could scatter around.
715 std::string name = callee->getNameAsString();
716 std::transform(name.begin(), name.end(), name.begin(), easytolower);
717 if (startswith(name, "read"))
718 // this is a write-only call
720 else if (name.find(">>=") != std::string::npos && callExpr->getArg(1) == child)
721 // this is a write-only call
723 else
724 bPotentiallyReadFrom = true;
726 else
727 bPotentiallyReadFrom = true;
728 break;
730 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
732 BinaryOperator::Opcode op = binaryOp->getOpcode();
733 // If the child is on the LHS and it is an assignment op, we are obviously not reading from it
734 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
735 || op == BO_RemAssign || op == BO_AddAssign
736 || op == BO_SubAssign || op == BO_ShlAssign
737 || op == BO_ShrAssign || op == BO_AndAssign
738 || op == BO_XorAssign || op == BO_OrAssign;
739 if (!(binaryOp->getLHS() == child && assignmentOp))
741 bPotentiallyReadFrom = true;
743 break;
745 else if (isa<ReturnStmt>(parent) || isa<CXXConstructExpr>(parent)
746 || isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent)
747 || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
748 || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
749 || isa<CXXDependentScopeMemberExpr>(parent) || isa<UnresolvedMemberExpr>(parent)
750 || isa<MaterializeTemporaryExpr>(parent))
752 bPotentiallyReadFrom = true;
753 break;
755 else if (isa<CXXDeleteExpr>(parent) || isa<UnaryExprOrTypeTraitExpr>(parent)
756 || isa<CXXUnresolvedConstructExpr>(parent) || isa<CompoundStmt>(parent)
757 || isa<LabelStmt>(parent) || isa<CXXForRangeStmt>(parent)
758 || isa<CXXTypeidExpr>(parent) || isa<DefaultStmt>(parent)
759 || isa<GCCAsmStmt>(parent) || isa<VAArgExpr>(parent) || isa<ConstantExpr>(parent)
760 || isa<CXXDefaultArgExpr>(parent) || isa<LambdaExpr>(parent))
762 break;
764 else
766 bPotentiallyReadFrom = true;
767 bDump = true;
768 break;
770 } while (true);
772 if (bDump)
774 report(DiagnosticsEngine::Warning, "oh dear, what can the matter be?",
775 memberExpr->getBeginLoc())
776 << memberExpr->getSourceRange();
777 report(DiagnosticsEngine::Note, "parent over here", parent->getBeginLoc())
778 << parent->getSourceRange();
779 parent->dump();
780 memberExpr->dump();
783 MyVarInfo varInfo = niceName(varDecl);
784 if (bPotentiallyReadFrom)
786 readFromSet.insert(varInfo);
790 void WriteOnlyVars::checkIfWrittenTo(const VarDecl* varDecl, const Expr* memberExpr)
792 // if we're inside a block that looks like
793 // if (varDecl)
794 // ...
795 // then writes to this var don't matter, because unless we find another write to this var, this var is dead
796 if (std::find(insideConditionalCheckOfMemberSet.begin(),
797 insideConditionalCheckOfMemberSet.end(), varDecl)
798 != insideConditionalCheckOfMemberSet.end())
799 return;
801 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
802 const Stmt* child = memberExpr;
803 const Stmt* parent
804 = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
805 // walk up the tree until we find something interesting
806 bool bPotentiallyWrittenTo = false;
807 bool bDump = false;
808 auto walkupUp = [&]() {
809 child = parent;
810 auto parentsRange = compiler.getASTContext().getParents(*parent);
811 parent = parentsRange.begin() == parentsRange.end() ? nullptr
812 : parentsRange.begin()->get<Stmt>();
816 if (!parent)
818 // check if we have an expression like
819 // int& r = var;
820 auto parentsRange = compiler.getASTContext().getParents(*child);
821 if (parentsRange.begin() != parentsRange.end())
823 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
824 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
825 // which is of type 'T&&' and also an l-value-ref ?
826 if (varDecl && !varDecl->isImplicit()
827 && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
829 bPotentiallyWrittenTo = true;
832 break;
834 if (isa<CXXReinterpretCastExpr>(parent))
836 // once we see one of these, there is not much useful we can know
837 bPotentiallyWrittenTo = true;
838 break;
840 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent)
841 || isa<ParenListExpr>(parent) || isa<ArrayInitLoopExpr>(parent)
842 || isa<ExprWithCleanups>(parent))
844 walkupUp();
846 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
848 UnaryOperator::Opcode op = unaryOperator->getOpcode();
849 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc
850 || op == UO_PreDec)
852 bPotentiallyWrittenTo = true;
854 break;
856 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
858 if (arraySubscriptExpr->getIdx() == child)
859 break;
860 walkupUp();
862 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
864 auto callee = getCallee(operatorCallExpr);
865 if (callee)
867 // if calling a non-const operator on the var
868 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
869 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child)
871 if (!calleeMethodDecl->isConst())
872 bPotentiallyWrittenTo
873 = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
875 else if (IsPassedByNonConst(varDecl, child, operatorCallExpr, *callee))
877 bPotentiallyWrittenTo = true;
880 else
881 bPotentiallyWrittenTo = true; // conservative, could improve
882 break;
884 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
886 const CXXMethodDecl* calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
887 if (calleeMethodDecl)
889 // if calling a non-const method on the var
890 const Expr* tmp = dyn_cast<Expr>(child);
891 if (tmp->isBoundMemberFunction(compiler.getASTContext()))
893 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
895 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
897 if (!calleeMethodDecl->isConst())
898 bPotentiallyWrittenTo
899 = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
900 break;
902 else if (IsPassedByNonConst(varDecl, child, cxxMemberCallExpr,
903 CalleeWrapper(calleeMethodDecl)))
904 bPotentiallyWrittenTo = true;
906 else
907 bPotentiallyWrittenTo = true; // can happen in templates
908 break;
910 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
912 if (IsPassedByNonConst(varDecl, child, cxxConstructExpr,
913 CalleeWrapper(cxxConstructExpr)))
914 bPotentiallyWrittenTo = true;
915 break;
917 else if (auto callExpr = dyn_cast<CallExpr>(parent))
919 auto callee = getCallee(callExpr);
920 if (callee)
922 if (IsPassedByNonConst(varDecl, child, callExpr, *callee))
923 bPotentiallyWrittenTo = true;
925 else
926 bPotentiallyWrittenTo = true; // conservative, could improve
927 break;
929 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
931 BinaryOperator::Opcode op = binaryOp->getOpcode();
932 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign || op == BO_DivAssign
933 || op == BO_RemAssign || op == BO_AddAssign
934 || op == BO_SubAssign || op == BO_ShlAssign
935 || op == BO_ShrAssign || op == BO_AndAssign
936 || op == BO_XorAssign || op == BO_OrAssign;
937 if (assignmentOp)
939 if (binaryOp->getLHS() == child)
940 bPotentiallyWrittenTo = true;
941 else if (loplugin::TypeCheck(binaryOp->getLHS()->getType())
942 .LvalueReference()
943 .NonConst())
944 // if the LHS is a non-const reference, we could write to the var later on
945 bPotentiallyWrittenTo = true;
947 break;
949 else if (isa<ReturnStmt>(parent))
951 if (insideFunctionDecl)
953 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
954 if (tc.LvalueReference().NonConst())
955 bPotentiallyWrittenTo = true;
957 break;
959 else if (isa<ConditionalOperator>(parent) || isa<SwitchStmt>(parent)
960 || isa<DeclStmt>(parent) || isa<WhileStmt>(parent) || isa<CXXNewExpr>(parent)
961 || isa<ForStmt>(parent) || isa<InitListExpr>(parent)
962 || isa<CXXDependentScopeMemberExpr>(parent) || isa<UnresolvedMemberExpr>(parent)
963 || isa<MaterializeTemporaryExpr>(parent) || isa<IfStmt>(parent)
964 || isa<DoStmt>(parent) || isa<CXXDeleteExpr>(parent)
965 || isa<UnaryExprOrTypeTraitExpr>(parent) || isa<CXXUnresolvedConstructExpr>(parent)
966 || isa<CompoundStmt>(parent) || isa<LabelStmt>(parent)
967 || isa<CXXForRangeStmt>(parent) || isa<CXXTypeidExpr>(parent)
968 || isa<DefaultStmt>(parent) || isa<ConstantExpr>(parent) || isa<GCCAsmStmt>(parent)
969 || isa<VAArgExpr>(parent) || isa<CXXDefaultArgExpr>(parent)
970 || isa<LambdaExpr>(parent))
972 break;
974 else
976 bPotentiallyWrittenTo = true;
977 bDump = true;
978 break;
980 } while (true);
982 if (bDump)
984 report(DiagnosticsEngine::Warning, "oh dear2, what can the matter be? writtenTo=%0",
985 memberExpr->getBeginLoc())
986 << bPotentiallyWrittenTo << memberExpr->getSourceRange();
987 if (parent)
989 report(DiagnosticsEngine::Note, "parent over here", parent->getBeginLoc())
990 << parent->getSourceRange();
991 parent->dump();
993 memberExpr->dump();
994 varDecl->getType()->dump();
997 MyVarInfo varInfo = niceName(varDecl);
998 if (bPotentiallyWrittenTo)
1000 writeToSet.insert(varInfo);
1004 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
1005 bool WriteOnlyVars::checkForWriteWhenUsingCollectionType(const CXXMethodDecl* calleeMethodDecl)
1007 auto const tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
1008 bool listLike = false, setLike = false, mapLike = false, cssSequence = false;
1009 if (tc.Class("deque").StdNamespace() || tc.Class("list").StdNamespace()
1010 || tc.Class("queue").StdNamespace() || tc.Class("vector").StdNamespace())
1012 listLike = true;
1014 else if (tc.Class("set").StdNamespace() || tc.Class("unordered_set").StdNamespace())
1016 setLike = true;
1018 else if (tc.Class("map").StdNamespace() || tc.Class("unordered_map").StdNamespace())
1020 mapLike = true;
1022 else if (tc.Class("Sequence")
1023 .Namespace("uno")
1024 .Namespace("star")
1025 .Namespace("sun")
1026 .Namespace("com")
1027 .GlobalNamespace())
1029 cssSequence = true;
1031 else
1032 return true;
1034 if (calleeMethodDecl->isOverloadedOperator())
1036 auto oo = calleeMethodDecl->getOverloadedOperator();
1037 if (oo == OO_Equal)
1038 return true;
1039 // This is operator[]. We only care about things that add elements to the collection.
1040 // if nothing modifies the size of the collection, then nothing useful
1041 // is stored in it.
1042 if (listLike)
1043 return false;
1044 return true;
1047 auto name = calleeMethodDecl->getName();
1048 if (listLike || setLike || mapLike)
1050 if (name == "reserve" || name == "shrink_to_fit" || name == "clear" || name == "erase"
1051 || name == "pop_back" || name == "pop_front" || name == "front" || name == "back"
1052 || name == "data" || name == "remove" || name == "remove_if" || name == "unique"
1053 || name == "sort" || name == "begin" || name == "end" || name == "rbegin"
1054 || name == "rend" || name == "at" || name == "find" || name == "equal_range"
1055 || name == "lower_bound" || name == "upper_bound")
1056 return false;
1058 if (cssSequence)
1060 if (name == "getArray" || name == "begin" || name == "end")
1061 return false;
1064 return true;
1067 bool WriteOnlyVars::IsPassedByNonConst(const VarDecl* varDecl, const Stmt* child,
1068 CallerWrapper callExpr, CalleeWrapper calleeFunctionDecl)
1070 unsigned len = std::min(callExpr.getNumArgs(), calleeFunctionDecl.getNumParams());
1071 // if it's an array, passing it by value to a method typically means the
1072 // callee takes a pointer and can modify the array
1073 if (varDecl->getType()->isConstantArrayType())
1075 for (unsigned i = 0; i < len; ++i)
1076 if (callExpr.getArg(i) == child)
1077 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
1078 return true;
1080 else
1082 for (unsigned i = 0; i < len; ++i)
1083 if (callExpr.getArg(i) == child)
1084 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i))
1085 .LvalueReference()
1086 .NonConst())
1087 return true;
1089 return false;
1092 bool WriteOnlyVars::VisitDeclRefExpr(const DeclRefExpr* declRefExpr)
1094 const Decl* decl = declRefExpr->getDecl();
1095 const VarDecl* varDecl = dyn_cast<VarDecl>(decl);
1096 if (!varDecl)
1097 return true;
1098 if (varDecl->isImplicit() || isa<ParmVarDecl>(varDecl))
1099 return true;
1100 varDecl = varDecl->getCanonicalDecl();
1101 if (ignoreLocation(varDecl))
1102 return true;
1103 // ignore stuff that forms part of the stable URE interface
1104 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(varDecl->getLocation())))
1105 return true;
1107 checkIfReadFrom(varDecl, declRefExpr);
1109 checkIfWrittenTo(varDecl, declRefExpr);
1111 return true;
1114 compat::optional<CalleeWrapper> WriteOnlyVars::getCallee(CallExpr const* callExpr)
1116 FunctionDecl const* functionDecl = callExpr->getDirectCallee();
1117 if (functionDecl)
1118 return CalleeWrapper(functionDecl);
1120 // Extract the functionprototype from a type
1121 clang::Type const* calleeType = callExpr->getCallee()->getType().getTypePtr();
1122 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>())
1124 if (auto prototype = pointerType->getPointeeType()
1125 ->getUnqualifiedDesugaredType()
1126 ->getAs<FunctionProtoType>())
1128 return CalleeWrapper(prototype);
1132 return compat::optional<CalleeWrapper>();
1135 loplugin::Plugin::Registration<WriteOnlyVars> X("writeonlyvars", false);
1138 #endif
1140 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */