LanguageTool: don't crash if REST protocol isn't set
[LibreOffice.git] / compilerplugins / clang / unusedfields.cxx
blobac260d7e82d1bdb5b3ee7f7e74694578839b2da5
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 "compat.hxx"
26 #include "check.hxx"
28 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
30 #endif
32 /**
33 This performs two analyses:
34 (1) look for unused fields
35 (2) look for fields that are write-only
37 We dmp a list of calls to methods, and a list of field definitions.
38 Then we will post-process the 2 lists and find the set of unused methods.
40 Be warned that it produces around 5G of log file.
42 The process goes something like this:
43 $ make check
44 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='unusedfields' check
45 $ ./compilerplugins/clang/unusedfields.py
47 and then
48 $ for dir in *; do make FORCE_COMPILE=all UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='unusedfieldsremove' $dir; done
49 to auto-remove the method declarations
51 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
52 to get it to work :-)
56 namespace {
58 struct MyFieldInfo
60 const RecordDecl* parentRecord;
61 std::string parentClass;
62 std::string fieldName;
63 std::string fieldType;
64 std::string sourceLocation;
65 std::string access;
67 bool operator < (const MyFieldInfo &lhs, const MyFieldInfo &rhs)
69 return std::tie(lhs.parentClass, lhs.fieldName)
70 < std::tie(rhs.parentClass, rhs.fieldName);
74 // try to limit the voluminous output a little
75 static std::set<MyFieldInfo> touchedFromInsideSet;
76 static std::set<MyFieldInfo> touchedFromOutsideSet;
77 static std::set<MyFieldInfo> touchedFromOutsideConstructorSet;
78 static std::set<MyFieldInfo> readFromSet;
79 static std::set<MyFieldInfo> writeToSet;
80 static std::set<MyFieldInfo> definitionSet;
82 /**
83 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
85 class CallerWrapper
87 const CallExpr * m_callExpr;
88 const CXXConstructExpr * m_cxxConstructExpr;
89 public:
90 CallerWrapper(const CallExpr * callExpr) : m_callExpr(callExpr), m_cxxConstructExpr(nullptr) {}
91 CallerWrapper(const CXXConstructExpr * cxxConstructExpr) : m_callExpr(nullptr), m_cxxConstructExpr(cxxConstructExpr) {}
92 unsigned getNumArgs () const
93 { return m_callExpr ? m_callExpr->getNumArgs() : m_cxxConstructExpr->getNumArgs(); }
94 const Expr * getArg (unsigned i) const
95 { return m_callExpr ? m_callExpr->getArg(i) : m_cxxConstructExpr->getArg(i); }
97 class CalleeWrapper
99 const FunctionDecl * m_calleeFunctionDecl = nullptr;
100 const CXXConstructorDecl * m_cxxConstructorDecl = nullptr;
101 const FunctionProtoType * m_functionPrototype = nullptr;
102 public:
103 explicit CalleeWrapper(const FunctionDecl * calleeFunctionDecl) : m_calleeFunctionDecl(calleeFunctionDecl) {}
104 explicit CalleeWrapper(const CXXConstructExpr * cxxConstructExpr) : m_cxxConstructorDecl(cxxConstructExpr->getConstructor()) {}
105 explicit CalleeWrapper(const FunctionProtoType * functionPrototype) : m_functionPrototype(functionPrototype) {}
106 unsigned getNumParams() const
108 if (m_calleeFunctionDecl)
109 return m_calleeFunctionDecl->getNumParams();
110 else if (m_cxxConstructorDecl)
111 return m_cxxConstructorDecl->getNumParams();
112 else if (m_functionPrototype->param_type_begin() == m_functionPrototype->param_type_end())
113 // FunctionProtoType will assert if we call getParamTypes() and it has no params
114 return 0;
115 else
116 return m_functionPrototype->getParamTypes().size();
118 const QualType getParamType(unsigned i) const
120 if (m_calleeFunctionDecl)
121 return m_calleeFunctionDecl->getParamDecl(i)->getType();
122 else if (m_cxxConstructorDecl)
123 return m_cxxConstructorDecl->getParamDecl(i)->getType();
124 else
125 return m_functionPrototype->getParamTypes()[i];
127 std::string getNameAsString() const
129 if (m_calleeFunctionDecl)
130 return m_calleeFunctionDecl->getNameAsString();
131 else if (m_cxxConstructorDecl)
132 return m_cxxConstructorDecl->getNameAsString();
133 else
134 return "";
136 CXXMethodDecl const * getAsCXXMethodDecl() const
138 if (m_calleeFunctionDecl)
139 return dyn_cast<CXXMethodDecl>(m_calleeFunctionDecl);
140 return nullptr;
144 class UnusedFields:
145 public loplugin::FilteringPlugin<UnusedFields>
147 public:
148 explicit UnusedFields(loplugin::InstantiationData const & data):
149 FilteringPlugin(data) {}
151 virtual void run() override;
153 bool shouldVisitTemplateInstantiations () const { return true; }
154 bool shouldVisitImplicitCode() const { return true; }
156 bool VisitFieldDecl( const FieldDecl* );
157 bool VisitMemberExpr( const MemberExpr* );
158 bool VisitDeclRefExpr( const DeclRefExpr* );
159 bool VisitCXXConstructorDecl( const CXXConstructorDecl* );
160 bool VisitInitListExpr( const InitListExpr* );
161 bool TraverseCXXConstructorDecl( CXXConstructorDecl* );
162 bool TraverseCXXMethodDecl( CXXMethodDecl* );
163 bool TraverseFunctionDecl( FunctionDecl* );
164 bool TraverseIfStmt( IfStmt* );
166 private:
167 MyFieldInfo niceName(const FieldDecl*);
168 bool ignoreLocation(SourceLocation loc);
169 bool checkIgnoreLocation(SourceLocation loc);
170 void checkTouchedFromOutside(const FieldDecl* fieldDecl, const Expr* memberExpr);
171 void checkIfReadFrom(const FieldDecl* fieldDecl, const Expr* memberExpr);
172 void checkIfWrittenTo(const FieldDecl* fieldDecl, const Expr* memberExpr);
173 bool isSomeKindOfZero(const Expr* arg);
174 bool checkForWriteWhenUsingCollectionType(const CXXMethodDecl * calleeMethodDecl);
175 bool IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt * child, CallerWrapper callExpr,
176 CalleeWrapper calleeFunctionDecl);
177 llvm::Optional<CalleeWrapper> getCallee(CallExpr const *);
179 RecordDecl * insideMoveOrCopyOrCloneDeclParent = nullptr;
180 RecordDecl * insideStreamOutputOperator = nullptr;
181 // For reasons I do not understand, parentFunctionDecl() is not reliable, so
182 // we store the parent function on the way down the AST.
183 FunctionDecl * insideFunctionDecl = nullptr;
184 std::vector<FieldDecl const *> insideConditionalCheckOfMemberSet;
187 void UnusedFields::run()
189 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
191 if (!isUnitTestMode())
193 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
194 // writing to the same logfile
195 std::string output;
196 for (const MyFieldInfo & s : touchedFromInsideSet)
197 output += "inside:\t" + s.parentClass + "\t" + s.fieldName + "\n";
198 for (const MyFieldInfo & s : touchedFromOutsideSet)
199 output += "outside:\t" + s.parentClass + "\t" + s.fieldName + "\n";
200 for (const MyFieldInfo & s : touchedFromOutsideConstructorSet)
201 output += "outside-constructor:\t" + s.parentClass + "\t" + s.fieldName + "\n";
202 for (const MyFieldInfo & s : readFromSet)
203 output += "read:\t" + s.parentClass + "\t" + s.fieldName + "\n";
204 for (const MyFieldInfo & s : writeToSet)
205 output += "write:\t" + s.parentClass + "\t" + s.fieldName + "\n";
206 for (const MyFieldInfo & s : definitionSet)
207 output += "definition:\t" + s.access + "\t" + s.parentClass + "\t" + s.fieldName + "\t" + s.fieldType + "\t" + s.sourceLocation + "\n";
208 std::ofstream myfile;
209 myfile.open( WORKDIR "/loplugin.unusedfields.log", std::ios::app | std::ios::out);
210 myfile << output;
211 myfile.close();
213 else
215 for (const MyFieldInfo & s : readFromSet)
216 report(
217 DiagnosticsEngine::Warning,
218 "read %0",
219 compat::getBeginLoc(s.parentRecord))
220 << s.fieldName;
221 for (const MyFieldInfo & s : writeToSet)
222 report(
223 DiagnosticsEngine::Warning,
224 "write %0",
225 compat::getBeginLoc(s.parentRecord))
226 << s.fieldName;
227 for (const MyFieldInfo & s : touchedFromOutsideConstructorSet)
228 report(
229 DiagnosticsEngine::Warning,
230 "outside-constructor %0",
231 compat::getBeginLoc(s.parentRecord))
232 << s.fieldName;
233 for (const MyFieldInfo & s : touchedFromOutsideSet)
234 report(
235 DiagnosticsEngine::Warning,
236 "outside %0",
237 compat::getBeginLoc(s.parentRecord))
238 << s.fieldName;
243 MyFieldInfo UnusedFields::niceName(const FieldDecl* fieldDecl)
245 MyFieldInfo aInfo;
247 const RecordDecl* recordDecl = fieldDecl->getParent();
249 if (const CXXRecordDecl* cxxRecordDecl = dyn_cast<CXXRecordDecl>(recordDecl))
251 if (cxxRecordDecl->getTemplateInstantiationPattern())
252 cxxRecordDecl = cxxRecordDecl->getTemplateInstantiationPattern();
253 aInfo.parentRecord = cxxRecordDecl;
254 aInfo.parentClass = cxxRecordDecl->getQualifiedNameAsString();
256 else
258 aInfo.parentRecord = recordDecl;
259 aInfo.parentClass = recordDecl->getQualifiedNameAsString();
262 aInfo.fieldName = fieldDecl->getNameAsString();
263 // sometimes the name (if it's an anonymous thing) contains the full path of the build folder, which we don't need
264 size_t idx = aInfo.fieldName.find(SRCDIR);
265 if (idx != std::string::npos) {
266 aInfo.fieldName = aInfo.fieldName.replace(idx, strlen(SRCDIR), "");
268 aInfo.fieldType = fieldDecl->getType().getAsString();
270 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( fieldDecl->getLocation() );
271 StringRef name = getFilenameOfLocation(expansionLoc);
272 aInfo.sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
273 loplugin::normalizeDotDotInFilePath(aInfo.sourceLocation);
275 switch (fieldDecl->getAccess())
277 case AS_public: aInfo.access = "public"; break;
278 case AS_private: aInfo.access = "private"; break;
279 case AS_protected: aInfo.access = "protected"; break;
280 default: aInfo.access = "unknown"; break;
283 return aInfo;
287 * Our need to see everything conflicts with the PCH code in pluginhandler::ignoreLocation,
288 * so we have to do this ourselves.
290 bool UnusedFields::ignoreLocation(SourceLocation loc)
292 static std::unordered_map<SourceLocation, bool> checkedMap;
293 auto it = checkedMap.find(loc);
294 if (it != checkedMap.end())
295 return it->second;
296 bool ignore = checkIgnoreLocation(loc);
297 checkedMap.emplace(loc, ignore);
298 return ignore;
301 bool UnusedFields::checkIgnoreLocation(SourceLocation loc)
303 // simplified form of the code in PluginHandler::checkIgnoreLocation
304 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
305 if( compiler.getSourceManager().isInSystemHeader( expansionLoc ))
306 return true;
307 PresumedLoc presumedLoc = compiler.getSourceManager().getPresumedLoc( expansionLoc );
308 if( presumedLoc.isInvalid())
309 return true;
310 const char* bufferName = presumedLoc.getFilename();
311 if (bufferName == NULL
312 || loplugin::hasPathnamePrefix(bufferName, SRCDIR "/external/")
313 || loplugin::hasPathnamePrefix(bufferName, WORKDIR "/"))
314 return true;
315 if( loplugin::hasPathnamePrefix(bufferName, BUILDDIR "/")
316 || loplugin::hasPathnamePrefix(bufferName, SRCDIR "/") )
317 return false; // ok
318 return true;
322 bool UnusedFields::VisitFieldDecl( const FieldDecl* fieldDecl )
324 fieldDecl = fieldDecl->getCanonicalDecl();
325 if (ignoreLocation( compat::getBeginLoc(fieldDecl) )) {
326 return true;
328 // ignore stuff that forms part of the stable URE interface
329 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
330 return true;
333 if (fieldDecl->getInClassInitializer() && !isSomeKindOfZero(fieldDecl->getInClassInitializer())) {
334 writeToSet.insert(niceName(fieldDecl));
337 definitionSet.insert(niceName(fieldDecl));
338 return true;
342 Does the expression being used to initialise a field value evaluate to
343 the same as a default value?
345 bool UnusedFields::isSomeKindOfZero(const Expr* arg)
347 assert(arg);
348 arg = arg->IgnoreParenCasts();
349 if (isa<CXXDefaultArgExpr>(arg)) {
350 arg = dyn_cast<CXXDefaultArgExpr>(arg)->getExpr();
352 arg = arg->IgnoreParenCasts();
353 // ignore this, it seems to trigger an infinite recursion
354 if (isa<UnaryExprOrTypeTraitExpr>(arg)) {
355 return false;
357 if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(arg)) {
358 return cxxConstructExpr->getConstructor()->isDefaultConstructor();
360 APSInt x1;
361 if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
363 return x1 == 0;
365 if (isa<CXXNullPtrLiteralExpr>(arg)) {
366 return true;
368 if (isa<MaterializeTemporaryExpr>(arg))
370 const CXXBindTemporaryExpr* strippedArg = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
371 if (strippedArg)
373 auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
374 if (temp->getNumArgs() == 0)
376 if (loplugin::TypeCheck(temp->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
377 return true;
379 if (loplugin::TypeCheck(temp->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
380 return true;
382 return false;
387 // Get the expression contents.
388 // This helps us find params which are always initialised with something like "OUString()".
389 SourceManager& SM = compiler.getSourceManager();
390 SourceLocation startLoc = compat::getBeginLoc(arg);
391 SourceLocation endLoc = compat::getEndLoc(arg);
392 const char *p1 = SM.getCharacterData( startLoc );
393 const char *p2 = SM.getCharacterData( endLoc );
394 if (!p1 || !p2 || (p2 - p1) < 0 || (p2 - p1) > 40) {
395 return false;
397 unsigned n = Lexer::MeasureTokenLength( endLoc, SM, compiler.getLangOpts());
398 std::string s( p1, p2 - p1 + n);
399 // strip linefeed and tab characters so they don't interfere with the parsing of the log file
400 std::replace( s.begin(), s.end(), '\r', ' ');
401 std::replace( s.begin(), s.end(), '\n', ' ');
402 std::replace( s.begin(), s.end(), '\t', ' ');
404 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
405 if (s == "OUString()")
406 return true;
407 else if (s == "OString()")
408 return true;
409 return false;
412 static char easytolower(char in)
414 if (in<='Z' && in>='A')
415 return in-('Z'-'z');
416 return in;
419 bool startswith(const std::string& rStr, const char* pSubStr)
421 return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
424 bool UnusedFields::TraverseCXXConstructorDecl(CXXConstructorDecl* cxxConstructorDecl)
426 auto copy = insideMoveOrCopyOrCloneDeclParent;
427 if (!ignoreLocation(compat::getBeginLoc(cxxConstructorDecl)) && cxxConstructorDecl->isThisDeclarationADefinition())
429 if (cxxConstructorDecl->isCopyOrMoveConstructor())
430 insideMoveOrCopyOrCloneDeclParent = cxxConstructorDecl->getParent();
432 bool ret = RecursiveASTVisitor::TraverseCXXConstructorDecl(cxxConstructorDecl);
433 insideMoveOrCopyOrCloneDeclParent = copy;
434 return ret;
437 bool UnusedFields::TraverseCXXMethodDecl(CXXMethodDecl* cxxMethodDecl)
439 auto copy1 = insideMoveOrCopyOrCloneDeclParent;
440 auto copy2 = insideFunctionDecl;
441 if (!ignoreLocation(compat::getBeginLoc(cxxMethodDecl)) && cxxMethodDecl->isThisDeclarationADefinition())
443 if (cxxMethodDecl->isCopyAssignmentOperator()
444 || cxxMethodDecl->isMoveAssignmentOperator()
445 || (cxxMethodDecl->getIdentifier()
446 && (cxxMethodDecl->getName().startswith("Clone")
447 || cxxMethodDecl->getName().startswith("clone")
448 || cxxMethodDecl->getName().startswith("createClone"))))
449 insideMoveOrCopyOrCloneDeclParent = cxxMethodDecl->getParent();
450 // these are similar in that they tend to simply enumerate all the fields of an object without putting
451 // them to some useful purpose
452 auto op = cxxMethodDecl->getOverloadedOperator();
453 if (op == OO_EqualEqual || op == OO_ExclaimEqual)
454 insideMoveOrCopyOrCloneDeclParent = cxxMethodDecl->getParent();
456 insideFunctionDecl = cxxMethodDecl;
457 bool ret = RecursiveASTVisitor::TraverseCXXMethodDecl(cxxMethodDecl);
458 insideMoveOrCopyOrCloneDeclParent = copy1;
459 insideFunctionDecl = copy2;
460 return ret;
463 bool UnusedFields::TraverseFunctionDecl(FunctionDecl* functionDecl)
465 auto copy1 = insideStreamOutputOperator;
466 auto copy2 = insideFunctionDecl;
467 auto copy3 = insideMoveOrCopyOrCloneDeclParent;
468 if (functionDecl->getLocation().isValid() && !ignoreLocation(compat::getBeginLoc(functionDecl)) && functionDecl->isThisDeclarationADefinition())
470 auto op = functionDecl->getOverloadedOperator();
471 if (op == OO_LessLess
472 && functionDecl->getNumParams() == 2)
474 QualType qt = functionDecl->getParamDecl(1)->getType();
475 insideStreamOutputOperator = qt.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
477 // these are similar in that they tend to simply enumerate all the fields of an object without putting
478 // them to some useful purpose
479 if (op == OO_EqualEqual || op == OO_ExclaimEqual)
481 QualType qt = functionDecl->getParamDecl(1)->getType();
482 insideMoveOrCopyOrCloneDeclParent = qt.getNonReferenceType().getUnqualifiedType()->getAsCXXRecordDecl();
485 insideFunctionDecl = functionDecl;
486 bool ret = RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
487 insideStreamOutputOperator = copy1;
488 insideFunctionDecl = copy2;
489 insideMoveOrCopyOrCloneDeclParent = copy3;
490 return ret;
493 bool UnusedFields::TraverseIfStmt(IfStmt* ifStmt)
495 FieldDecl const * memberFieldDecl = nullptr;
496 Expr const * cond = ifStmt->getCond()->IgnoreParenImpCasts();
498 if (auto memberCallExpr = dyn_cast<CXXMemberCallExpr>(cond))
500 if (auto cxxConvert = dyn_cast_or_null<CXXConversionDecl>(memberCallExpr->getMethodDecl()))
502 if (cxxConvert->getConversionType()->isBooleanType())
503 if (auto memberExpr = dyn_cast<MemberExpr>(memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts()))
504 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
505 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
507 else if (auto cxxMethod = memberCallExpr->getMethodDecl())
509 if (cxxMethod->getIdentifier() && cxxMethod->getName() == "get" && memberCallExpr->getNumArgs()==0)
510 if (auto memberExpr = dyn_cast<MemberExpr>(memberCallExpr->getImplicitObjectArgument()->IgnoreParenImpCasts()))
511 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
512 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
515 else if (auto memberExpr = dyn_cast<MemberExpr>(cond))
517 if ((memberFieldDecl = dyn_cast<FieldDecl>(memberExpr->getMemberDecl())))
518 insideConditionalCheckOfMemberSet.push_back(memberFieldDecl);
521 bool ret = RecursiveASTVisitor::TraverseIfStmt(ifStmt);
522 if (memberFieldDecl)
523 insideConditionalCheckOfMemberSet.pop_back();
524 return ret;
527 bool UnusedFields::VisitMemberExpr( const MemberExpr* memberExpr )
529 const ValueDecl* decl = memberExpr->getMemberDecl();
530 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
531 if (!fieldDecl) {
532 return true;
534 fieldDecl = fieldDecl->getCanonicalDecl();
535 if (ignoreLocation(compat::getBeginLoc(fieldDecl))) {
536 return true;
538 // ignore stuff that forms part of the stable URE interface
539 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
540 return true;
543 checkTouchedFromOutside(fieldDecl, memberExpr);
545 checkIfReadFrom(fieldDecl, memberExpr);
547 checkIfWrittenTo(fieldDecl, memberExpr);
549 return true;
552 void UnusedFields::checkIfReadFrom(const FieldDecl* fieldDecl, const Expr* memberExpr)
554 if (insideMoveOrCopyOrCloneDeclParent || insideStreamOutputOperator)
556 RecordDecl const * cxxRecordDecl1 = fieldDecl->getParent();
557 // we don't care about reads from a field when inside the copy/move constructor/operator= for that field
558 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideMoveOrCopyOrCloneDeclParent))
559 return;
560 // we don't care about reads when the field is being used in an output operator, this is normally
561 // debug stuff
562 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideStreamOutputOperator))
563 return;
566 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
567 const Stmt* child = memberExpr;
568 const Stmt* parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
569 // walk up the tree until we find something interesting
570 bool bPotentiallyReadFrom = false;
571 bool bDump = false;
572 auto walkUp = [&]() {
573 child = parent;
574 auto parentsRange = compiler.getASTContext().getParents(*parent);
575 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
579 if (!parent)
581 // check if we're inside a CXXCtorInitializer or a VarDecl
582 auto parentsRange = compiler.getASTContext().getParents(*child);
583 if ( parentsRange.begin() != parentsRange.end())
585 const Decl* decl = parentsRange.begin()->get<Decl>();
586 if (decl && (isa<CXXConstructorDecl>(decl) || isa<VarDecl>(decl)))
587 bPotentiallyReadFrom = true;
589 if (!bPotentiallyReadFrom)
590 return;
591 break;
593 if (isa<CXXReinterpretCastExpr>(parent))
595 // once we see one of these, there is not much useful we can know
596 bPotentiallyReadFrom = true;
597 break;
599 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
600 || isa<ArrayInitLoopExpr>(parent) || isa<ExprWithCleanups>(parent))
602 walkUp();
604 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
606 UnaryOperator::Opcode op = unaryOperator->getOpcode();
607 if (memberExpr->getType()->isArrayType() && op == UO_Deref)
609 // ignore, deref'ing an array does not count as a read
611 else if (op == UO_AddrOf || op == UO_Deref
612 || op == UO_Plus || op == UO_Minus
613 || op == UO_Not || op == UO_LNot)
615 bPotentiallyReadFrom = true;
616 break;
618 /* The following are technically reads, but from a code-sense they're more of a write/modify, so
619 ignore them to find interesting fields that only modified, not usefully read:
620 UO_PreInc / UO_PostInc / UO_PreDec / UO_PostDec
621 But we still walk up in case the result of the expression is used in a read sense.
623 walkUp();
625 else if (auto caseStmt = dyn_cast<CaseStmt>(parent))
627 bPotentiallyReadFrom = caseStmt->getLHS() == child || caseStmt->getRHS() == child;
628 break;
630 else if (auto ifStmt = dyn_cast<IfStmt>(parent))
632 bPotentiallyReadFrom = ifStmt->getCond() == child;
633 break;
635 else if (auto doStmt = dyn_cast<DoStmt>(parent))
637 bPotentiallyReadFrom = doStmt->getCond() == child;
638 break;
640 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
642 if (arraySubscriptExpr->getIdx() == child)
644 bPotentiallyReadFrom = true;
645 break;
647 walkUp();
649 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
651 BinaryOperator::Opcode op = binaryOp->getOpcode();
652 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign
653 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
654 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
655 || op == BO_AndAssign || op == BO_XorAssign || op == BO_OrAssign;
656 if (binaryOp->getLHS() == child && assignmentOp)
657 break;
658 else
660 bPotentiallyReadFrom = true;
661 break;
664 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
666 auto op = operatorCallExpr->getOperator();
667 const bool assignmentOp = op == OO_Equal || op == OO_StarEqual ||
668 op == OO_SlashEqual || op == OO_PercentEqual ||
669 op == OO_PlusEqual || op == OO_MinusEqual ||
670 op == OO_LessLessEqual ||
671 op == OO_AmpEqual || op == OO_CaretEqual ||
672 op == OO_PipeEqual;
673 if (operatorCallExpr->getArg(0) == child && assignmentOp)
674 break;
675 else if (op == OO_GreaterGreaterEqual && operatorCallExpr->getArg(1) == child)
676 break; // this is a write-only call
677 else
679 bPotentiallyReadFrom = true;
680 break;
683 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
685 bool bWriteOnlyCall = false;
686 const CXXMethodDecl * callee = cxxMemberCallExpr->getMethodDecl();
687 if (callee)
689 const Expr* tmp = dyn_cast<Expr>(child);
690 if (tmp->isBoundMemberFunction(compiler.getASTContext())) {
691 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
693 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
695 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
696 // which we could scatter around.
697 std::string name = callee->getNameAsString();
698 std::transform(name.begin(), name.end(), name.begin(), easytolower);
699 if (startswith(name, "emplace") || name == "insert"
700 || name == "erase" || name == "remove" || name == "remove_if" || name == "sort"
701 || name == "push_back" || name == "pop_back"
702 || name == "push_front" || name == "pop_front"
703 || name == "reserve" || name == "resize" || name == "reset"
704 || name == "clear" || name == "fill")
705 // write-only modifications to collections
706 bWriteOnlyCall = true;
707 else if (name == "dispose" || name == "disposeAndClear" || name == "swap")
708 // we're abusing the write-only analysis here to look for fields which don't have anything useful
709 // being done to them, so we're ignoring things like std::vector::clear, std::vector::swap,
710 // and VclPtr::disposeAndClear
711 bWriteOnlyCall = true;
714 if (!bWriteOnlyCall)
715 bPotentiallyReadFrom = true;
716 break;
718 else if (auto callExpr = dyn_cast<CallExpr>(parent))
720 bool bWriteOnlyCall = false;
721 // check for calls to ReadXXX(foo) type methods, where foo is write-only
722 auto callee = getCallee(callExpr);
723 if (callee)
725 // FIXME perhaps a better solution here would be some kind of SAL_PARAM_WRITEONLY attribute
726 // which we could scatter around.
727 std::string name = callee->getNameAsString();
728 std::transform(name.begin(), name.end(), name.begin(), easytolower);
729 if (startswith(name, "read"))
730 // this is a write-only call
731 bWriteOnlyCall = true;
733 if (!bWriteOnlyCall)
734 bPotentiallyReadFrom = true;
735 break;
737 else if (isa<ReturnStmt>(parent)
738 || isa<CXXConstructExpr>(parent)
739 || isa<ConditionalOperator>(parent)
740 || isa<SwitchStmt>(parent)
741 || isa<DeclStmt>(parent)
742 || isa<WhileStmt>(parent)
743 || isa<CXXNewExpr>(parent)
744 || isa<ForStmt>(parent)
745 || isa<InitListExpr>(parent)
746 || isa<CXXDependentScopeMemberExpr>(parent)
747 || isa<UnresolvedMemberExpr>(parent)
748 || isa<MaterializeTemporaryExpr>(parent))
750 bPotentiallyReadFrom = true;
751 break;
753 else if (isa<CXXDeleteExpr>(parent)
754 || isa<UnaryExprOrTypeTraitExpr>(parent)
755 || isa<CXXUnresolvedConstructExpr>(parent)
756 || isa<CompoundStmt>(parent)
757 || isa<LabelStmt>(parent)
758 || isa<CXXForRangeStmt>(parent)
759 || isa<CXXTypeidExpr>(parent)
760 || isa<DefaultStmt>(parent))
762 break;
764 else
766 bPotentiallyReadFrom = true;
767 bDump = true;
768 break;
770 } while (true);
772 if (bDump)
774 report(
775 DiagnosticsEngine::Warning,
776 "oh dear, what can the matter be?",
777 compat::getBeginLoc(memberExpr))
778 << memberExpr->getSourceRange();
779 report(
780 DiagnosticsEngine::Note,
781 "parent over here",
782 compat::getBeginLoc(parent))
783 << parent->getSourceRange();
784 parent->dump();
785 memberExpr->dump();
788 MyFieldInfo fieldInfo = niceName(fieldDecl);
789 if (bPotentiallyReadFrom)
791 readFromSet.insert(fieldInfo);
795 void UnusedFields::checkIfWrittenTo(const FieldDecl* fieldDecl, const Expr* memberExpr)
797 if (insideMoveOrCopyOrCloneDeclParent)
799 RecordDecl const * cxxRecordDecl1 = fieldDecl->getParent();
800 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
801 if (cxxRecordDecl1 && (cxxRecordDecl1 == insideMoveOrCopyOrCloneDeclParent))
803 return;
807 // if we're inside a block that looks like
808 // if (fieldDecl)
809 // ...
810 // then writes to this field don't matter, because unless we find another write to this field, this field is dead
811 if (std::find(insideConditionalCheckOfMemberSet.begin(), insideConditionalCheckOfMemberSet.end(), fieldDecl) != insideConditionalCheckOfMemberSet.end())
812 return;
814 auto parentsRange = compiler.getASTContext().getParents(*memberExpr);
815 const Stmt* child = memberExpr;
816 const Stmt* parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
817 // walk up the tree until we find something interesting
818 bool bPotentiallyWrittenTo = false;
819 bool bDump = false;
820 auto walkUp = [&]() {
821 child = parent;
822 auto parentsRange = compiler.getASTContext().getParents(*parent);
823 parent = parentsRange.begin() == parentsRange.end() ? nullptr : parentsRange.begin()->get<Stmt>();
827 if (!parent)
829 // check if we have an expression like
830 // int& r = m_field;
831 auto parentsRange = compiler.getASTContext().getParents(*child);
832 if (parentsRange.begin() != parentsRange.end())
834 auto varDecl = dyn_cast_or_null<VarDecl>(parentsRange.begin()->get<Decl>());
835 // The isImplicit() call is to avoid triggering when we see the vardecl which is part of a for-range statement,
836 // which is of type 'T&&' and also an l-value-ref ?
837 if (varDecl && !varDecl->isImplicit() && loplugin::TypeCheck(varDecl->getType()).LvalueReference().NonConst())
839 bPotentiallyWrittenTo = true;
842 break;
844 if (isa<CXXReinterpretCastExpr>(parent))
846 // once we see one of these, there is not much useful we can know
847 bPotentiallyWrittenTo = true;
848 break;
850 else if (isa<CastExpr>(parent) || isa<MemberExpr>(parent) || isa<ParenExpr>(parent) || isa<ParenListExpr>(parent)
851 || isa<ArrayInitLoopExpr>(parent) || isa<ExprWithCleanups>(parent))
853 walkUp();
855 else if (auto unaryOperator = dyn_cast<UnaryOperator>(parent))
857 UnaryOperator::Opcode op = unaryOperator->getOpcode();
858 if (op == UO_AddrOf || op == UO_PostInc || op == UO_PostDec || op == UO_PreInc || op == UO_PreDec)
860 bPotentiallyWrittenTo = true;
862 break;
864 else if (auto arraySubscriptExpr = dyn_cast<ArraySubscriptExpr>(parent))
866 if (arraySubscriptExpr->getIdx() == child)
867 break;
868 walkUp();
870 else if (auto operatorCallExpr = dyn_cast<CXXOperatorCallExpr>(parent))
872 auto callee = getCallee(operatorCallExpr);
873 if (callee)
875 // if calling a non-const operator on the field
876 auto calleeMethodDecl = callee->getAsCXXMethodDecl();
877 if (calleeMethodDecl && operatorCallExpr->getArg(0) == child)
879 if (!calleeMethodDecl->isConst())
880 bPotentiallyWrittenTo = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
882 else if (IsPassedByNonConst(fieldDecl, child, operatorCallExpr, *callee))
884 bPotentiallyWrittenTo = true;
887 else
888 bPotentiallyWrittenTo = true; // conservative, could improve
889 break;
891 else if (auto cxxMemberCallExpr = dyn_cast<CXXMemberCallExpr>(parent))
893 const CXXMethodDecl * calleeMethodDecl = cxxMemberCallExpr->getMethodDecl();
894 if (calleeMethodDecl)
896 // if calling a non-const method on the field
897 const Expr* tmp = dyn_cast<Expr>(child);
898 if (tmp->isBoundMemberFunction(compiler.getASTContext())) {
899 tmp = dyn_cast<MemberExpr>(tmp)->getBase();
901 if (cxxMemberCallExpr->getImplicitObjectArgument() == tmp)
903 if (!calleeMethodDecl->isConst())
904 bPotentiallyWrittenTo = checkForWriteWhenUsingCollectionType(calleeMethodDecl);
905 break;
907 else if (IsPassedByNonConst(fieldDecl, child, cxxMemberCallExpr, CalleeWrapper(calleeMethodDecl)))
908 bPotentiallyWrittenTo = true;
910 else
911 bPotentiallyWrittenTo = true; // can happen in templates
912 break;
914 else if (auto cxxConstructExpr = dyn_cast<CXXConstructExpr>(parent))
916 if (IsPassedByNonConst(fieldDecl, child, cxxConstructExpr, CalleeWrapper(cxxConstructExpr)))
917 bPotentiallyWrittenTo = true;
918 break;
920 else if (auto callExpr = dyn_cast<CallExpr>(parent))
922 auto callee = getCallee(callExpr);
923 if (callee) {
924 if (IsPassedByNonConst(fieldDecl, child, callExpr, *callee))
925 bPotentiallyWrittenTo = true;
926 } else
927 bPotentiallyWrittenTo = true; // conservative, could improve
928 break;
930 else if (auto binaryOp = dyn_cast<BinaryOperator>(parent))
932 BinaryOperator::Opcode op = binaryOp->getOpcode();
933 const bool assignmentOp = op == BO_Assign || op == BO_MulAssign
934 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
935 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
936 || op == BO_AndAssign || 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()).LvalueReference().NonConst())
942 // if the LHS is a non-const reference, we could write to the field later on
943 bPotentiallyWrittenTo = true;
945 break;
947 else if (isa<ReturnStmt>(parent))
949 if (insideFunctionDecl)
951 auto tc = loplugin::TypeCheck(insideFunctionDecl->getReturnType());
952 if (tc.LvalueReference().NonConst())
953 bPotentiallyWrittenTo = true;
955 break;
957 else if (isa<ConditionalOperator>(parent)
958 || isa<SwitchStmt>(parent)
959 || isa<DeclStmt>(parent)
960 || isa<WhileStmt>(parent)
961 || isa<CXXNewExpr>(parent)
962 || isa<ForStmt>(parent)
963 || isa<InitListExpr>(parent)
964 || isa<CXXDependentScopeMemberExpr>(parent)
965 || isa<UnresolvedMemberExpr>(parent)
966 || isa<MaterializeTemporaryExpr>(parent)
967 || isa<IfStmt>(parent)
968 || isa<DoStmt>(parent)
969 || isa<CXXDeleteExpr>(parent)
970 || isa<UnaryExprOrTypeTraitExpr>(parent)
971 || isa<CXXUnresolvedConstructExpr>(parent)
972 || isa<CompoundStmt>(parent)
973 || isa<LabelStmt>(parent)
974 || isa<CXXForRangeStmt>(parent)
975 || isa<CXXTypeidExpr>(parent)
976 || isa<DefaultStmt>(parent))
978 break;
980 else
982 bPotentiallyWrittenTo = true;
983 bDump = true;
984 break;
986 } while (true);
988 if (bDump)
990 report(
991 DiagnosticsEngine::Warning,
992 "oh dear, what can the matter be? writtenTo=%0",
993 compat::getBeginLoc(memberExpr))
994 << bPotentiallyWrittenTo
995 << memberExpr->getSourceRange();
996 if (parent)
998 report(
999 DiagnosticsEngine::Note,
1000 "parent over here",
1001 compat::getBeginLoc(parent))
1002 << parent->getSourceRange();
1003 parent->dump();
1005 memberExpr->dump();
1006 fieldDecl->getType()->dump();
1009 MyFieldInfo fieldInfo = niceName(fieldDecl);
1010 if (bPotentiallyWrittenTo)
1012 writeToSet.insert(fieldInfo);
1016 // return true if this not a collection type, or if it is a collection type, and we might be writing to it
1017 bool UnusedFields::checkForWriteWhenUsingCollectionType(const CXXMethodDecl * calleeMethodDecl)
1019 auto const tc = loplugin::TypeCheck(calleeMethodDecl->getParent());
1020 bool listLike = false, setLike = false, mapLike = false, cssSequence = false;
1021 if (tc.Class("deque").StdNamespace()
1022 || tc.Class("list").StdNamespace()
1023 || tc.Class("queue").StdNamespace()
1024 || tc.Class("vector").StdNamespace())
1026 listLike = true;
1028 else if (tc.Class("set").StdNamespace()
1029 || tc.Class("unordered_set").StdNamespace())
1031 setLike = true;
1033 else if (tc.Class("map").StdNamespace()
1034 || tc.Class("unordered_map").StdNamespace())
1036 mapLike = true;
1038 else if (tc.Class("Sequence").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
1040 cssSequence = true;
1042 else
1043 return true;
1045 if (calleeMethodDecl->isOverloadedOperator())
1047 auto oo = calleeMethodDecl->getOverloadedOperator();
1048 if (oo == OO_Equal)
1049 return true;
1050 // This is operator[]. We only care about things that add elements to the collection.
1051 // if nothing modifies the size of the collection, then nothing useful
1052 // is stored in it.
1053 if (listLike)
1054 return false;
1055 return true;
1058 auto name = calleeMethodDecl->getName();
1059 if (listLike || setLike || mapLike)
1061 if (name == "reserve" || name == "shrink_to_fit" || name == "clear"
1062 || name == "erase" || name == "pop_back" || name == "pop_front"
1063 || name == "front" || name == "back" || name == "data"
1064 || name == "remove" || name == "remove_if"
1065 || name == "unique" || name == "sort"
1066 || name == "begin" || name == "end"
1067 || name == "rbegin" || name == "rend"
1068 || name == "at" || name == "find" || name == "equal_range"
1069 || name == "lower_bound" || name == "upper_bound")
1070 return false;
1072 if (cssSequence)
1074 if (name == "getArray" || name == "begin" || name == "end")
1075 return false;
1078 return true;
1081 bool UnusedFields::IsPassedByNonConst(const FieldDecl* fieldDecl, const Stmt * child, CallerWrapper callExpr,
1082 CalleeWrapper calleeFunctionDecl)
1084 unsigned len = std::min(callExpr.getNumArgs(),
1085 calleeFunctionDecl.getNumParams());
1086 // if it's an array, passing it by value to a method typically means the
1087 // callee takes a pointer and can modify the array
1088 if (fieldDecl->getType()->isConstantArrayType())
1090 for (unsigned i = 0; i < len; ++i)
1091 if (callExpr.getArg(i) == child)
1092 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).Pointer().NonConst())
1093 return true;
1095 else
1097 for (unsigned i = 0; i < len; ++i)
1098 if (callExpr.getArg(i) == child)
1099 if (loplugin::TypeCheck(calleeFunctionDecl.getParamType(i)).LvalueReference().NonConst())
1100 return true;
1102 return false;
1105 // fields that are assigned via member initialisers do not get visited in VisitDeclRef, so
1106 // have to do it here
1107 bool UnusedFields::VisitCXXConstructorDecl( const CXXConstructorDecl* cxxConstructorDecl )
1109 if (ignoreLocation( compat::getBeginLoc(cxxConstructorDecl) )) {
1110 return true;
1112 // ignore stuff that forms part of the stable URE interface
1113 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(cxxConstructorDecl->getLocation()))) {
1114 return true;
1117 // templates make EvaluateAsInt crash inside clang
1118 if (cxxConstructorDecl->isDependentContext())
1119 return true;
1121 // we don't care about writes to a field when inside the copy/move constructor/operator= for that field
1122 if (insideMoveOrCopyOrCloneDeclParent && cxxConstructorDecl->getParent() == insideMoveOrCopyOrCloneDeclParent)
1123 return true;
1125 for(auto it = cxxConstructorDecl->init_begin(); it != cxxConstructorDecl->init_end(); ++it)
1127 const CXXCtorInitializer* init = *it;
1128 const FieldDecl* fieldDecl = init->getMember();
1129 if (fieldDecl && init->getInit() && !isSomeKindOfZero(init->getInit()))
1131 MyFieldInfo fieldInfo = niceName(fieldDecl);
1132 writeToSet.insert(fieldInfo);
1135 return true;
1138 // Fields that are assigned via init-list-expr do not get visited in VisitDeclRef, so
1139 // have to do it here.
1140 bool UnusedFields::VisitInitListExpr( const InitListExpr* initListExpr)
1142 if (ignoreLocation( compat::getBeginLoc(initListExpr) ))
1143 return true;
1145 QualType varType = initListExpr->getType().getDesugaredType(compiler.getASTContext());
1146 auto recordType = varType->getAs<RecordType>();
1147 if (!recordType)
1148 return true;
1150 auto recordDecl = recordType->getDecl();
1151 for (auto it = recordDecl->field_begin(); it != recordDecl->field_end(); ++it)
1153 MyFieldInfo fieldInfo = niceName(*it);
1154 writeToSet.insert(fieldInfo);
1157 return true;
1160 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
1162 const Decl* decl = declRefExpr->getDecl();
1163 const FieldDecl* fieldDecl = dyn_cast<FieldDecl>(decl);
1164 if (!fieldDecl) {
1165 return true;
1167 fieldDecl = fieldDecl->getCanonicalDecl();
1168 if (ignoreLocation(compat::getBeginLoc(fieldDecl))) {
1169 return true;
1171 // ignore stuff that forms part of the stable URE interface
1172 if (isInUnoIncludeFile(compiler.getSourceManager().getSpellingLoc(fieldDecl->getLocation()))) {
1173 return true;
1175 checkTouchedFromOutside(fieldDecl, declRefExpr);
1176 return true;
1179 void UnusedFields::checkTouchedFromOutside(const FieldDecl* fieldDecl, const Expr* memberExpr) {
1180 const FunctionDecl* memberExprParentFunction = getParentFunctionDecl(memberExpr);
1181 const CXXMethodDecl* methodDecl = dyn_cast_or_null<CXXMethodDecl>(memberExprParentFunction);
1183 MyFieldInfo fieldInfo = niceName(fieldDecl);
1185 // it's touched from somewhere outside a class
1186 if (!methodDecl) {
1187 touchedFromOutsideSet.insert(fieldInfo);
1188 return;
1191 auto constructorDecl = dyn_cast<CXXConstructorDecl>(methodDecl);
1192 if (methodDecl->isCopyAssignmentOperator() || methodDecl->isMoveAssignmentOperator()) {
1193 // ignore move/copy operator, it's self->self
1194 } else if (constructorDecl && (constructorDecl->isCopyConstructor() || constructorDecl->isMoveConstructor())) {
1195 // ignore move/copy constructor, it's self->self
1196 } else {
1197 if (memberExprParentFunction->getParent() == fieldDecl->getParent()) {
1198 touchedFromInsideSet.insert(fieldInfo);
1199 if (!constructorDecl)
1200 touchedFromOutsideConstructorSet.insert(fieldInfo);
1201 } else {
1202 if (fieldDecl->getName() == "m_pShell")
1204 if (memberExprParentFunction)
1205 memberExprParentFunction->dump();
1206 memberExpr->dump();
1207 std::cout << "site2" << std::endl;
1209 touchedFromOutsideSet.insert(fieldInfo);
1214 llvm::Optional<CalleeWrapper> UnusedFields::getCallee(CallExpr const * callExpr)
1216 FunctionDecl const * functionDecl = callExpr->getDirectCallee();
1217 if (functionDecl)
1218 return CalleeWrapper(functionDecl);
1220 // Extract the functionprototype from a type
1221 clang::Type const * calleeType = callExpr->getCallee()->getType().getTypePtr();
1222 if (auto pointerType = calleeType->getUnqualifiedDesugaredType()->getAs<clang::PointerType>()) {
1223 if (auto prototype = pointerType->getPointeeType()->getUnqualifiedDesugaredType()->getAs<FunctionProtoType>()) {
1224 return CalleeWrapper(prototype);
1228 return llvm::Optional<CalleeWrapper>();
1231 loplugin::Plugin::Registration< UnusedFields > X("unusedfields", false);
1235 #endif
1237 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */