1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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/.
10 #if !defined _WIN32 //TODO, #include <sys/file.h>
16 #include <unordered_set>
22 #include "config_clang.h"
28 #if CLANG_VERSION >= 110000
29 #include "clang/AST/ParentMapContext.h"
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:
44 $ make FORCE_COMPILE=all COMPILER_PLUGIN_TOOL='unusedfields' check
45 $ ./compilerplugins/clang/unusedfields.py
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
60 const RecordDecl
* parentRecord
;
61 std::string parentClass
;
62 std::string fieldName
;
63 std::string fieldType
;
64 std::string sourceLocation
;
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
;
83 * Wrap the different kinds of callable and callee objects in the clang AST so I can define methods that handle everything.
87 const CallExpr
* m_callExpr
;
88 const CXXConstructExpr
* m_cxxConstructExpr
;
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
); }
99 const FunctionDecl
* m_calleeFunctionDecl
= nullptr;
100 const CXXConstructorDecl
* m_cxxConstructorDecl
= nullptr;
101 const FunctionProtoType
* m_functionPrototype
= nullptr;
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
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();
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();
136 CXXMethodDecl
const * getAsCXXMethodDecl() const
138 if (m_calleeFunctionDecl
)
139 return dyn_cast
<CXXMethodDecl
>(m_calleeFunctionDecl
);
145 public loplugin::FilteringPlugin
<UnusedFields
>
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
* );
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
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
);
215 for (const MyFieldInfo
& s
: readFromSet
)
217 DiagnosticsEngine::Warning
,
219 compat::getBeginLoc(s
.parentRecord
))
221 for (const MyFieldInfo
& s
: writeToSet
)
223 DiagnosticsEngine::Warning
,
225 compat::getBeginLoc(s
.parentRecord
))
227 for (const MyFieldInfo
& s
: touchedFromOutsideConstructorSet
)
229 DiagnosticsEngine::Warning
,
230 "outside-constructor %0",
231 compat::getBeginLoc(s
.parentRecord
))
233 for (const MyFieldInfo
& s
: touchedFromOutsideSet
)
235 DiagnosticsEngine::Warning
,
237 compat::getBeginLoc(s
.parentRecord
))
243 MyFieldInfo
UnusedFields::niceName(const FieldDecl
* fieldDecl
)
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();
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;
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())
296 bool ignore
= checkIgnoreLocation(loc
);
297 checkedMap
.emplace(loc
, 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
))
307 PresumedLoc presumedLoc
= compiler
.getSourceManager().getPresumedLoc( expansionLoc
);
308 if( presumedLoc
.isInvalid())
310 const char* bufferName
= presumedLoc
.getFilename();
311 if (bufferName
== NULL
312 || loplugin::hasPathnamePrefix(bufferName
, SRCDIR
"/external/")
313 || loplugin::hasPathnamePrefix(bufferName
, WORKDIR
"/"))
315 if( loplugin::hasPathnamePrefix(bufferName
, BUILDDIR
"/")
316 || loplugin::hasPathnamePrefix(bufferName
, SRCDIR
"/") )
322 bool UnusedFields::VisitFieldDecl( const FieldDecl
* fieldDecl
)
324 fieldDecl
= fieldDecl
->getCanonicalDecl();
325 if (ignoreLocation( compat::getBeginLoc(fieldDecl
) )) {
328 // ignore stuff that forms part of the stable URE interface
329 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
333 if (fieldDecl
->getInClassInitializer() && !isSomeKindOfZero(fieldDecl
->getInClassInitializer())) {
334 writeToSet
.insert(niceName(fieldDecl
));
337 definitionSet
.insert(niceName(fieldDecl
));
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
)
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
)) {
357 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(arg
)) {
358 return cxxConstructExpr
->getConstructor()->isDefaultConstructor();
361 if (compat::EvaluateAsInt(arg
, x1
, compiler
.getASTContext()))
365 if (isa
<CXXNullPtrLiteralExpr
>(arg
)) {
368 if (isa
<MaterializeTemporaryExpr
>(arg
))
370 const CXXBindTemporaryExpr
* strippedArg
= dyn_cast_or_null
<CXXBindTemporaryExpr
>(arg
->IgnoreParenCasts());
373 auto temp
= dyn_cast
<CXXTemporaryObjectExpr
>(strippedArg
->getSubExpr());
374 if (temp
->getNumArgs() == 0)
376 if (loplugin::TypeCheck(temp
->getType()).Class("OUString").Namespace("rtl").GlobalNamespace()) {
379 if (loplugin::TypeCheck(temp
->getType()).Class("OString").Namespace("rtl").GlobalNamespace()) {
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) {
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()")
407 else if (s
== "OString()")
412 static char easytolower(char in
)
414 if (in
<='Z' && in
>='A')
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
;
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
;
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
;
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
);
523 insideConditionalCheckOfMemberSet
.pop_back();
527 bool UnusedFields::VisitMemberExpr( const MemberExpr
* memberExpr
)
529 const ValueDecl
* decl
= memberExpr
->getMemberDecl();
530 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
534 fieldDecl
= fieldDecl
->getCanonicalDecl();
535 if (ignoreLocation(compat::getBeginLoc(fieldDecl
))) {
538 // ignore stuff that forms part of the stable URE interface
539 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
543 checkTouchedFromOutside(fieldDecl
, memberExpr
);
545 checkIfReadFrom(fieldDecl
, memberExpr
);
547 checkIfWrittenTo(fieldDecl
, memberExpr
);
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
))
560 // we don't care about reads when the field is being used in an output operator, this is normally
562 if (cxxRecordDecl1
&& (cxxRecordDecl1
== insideStreamOutputOperator
))
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;
572 auto walkUp
= [&]() {
574 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
575 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
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
)
593 if (isa
<CXXReinterpretCastExpr
>(parent
))
595 // once we see one of these, there is not much useful we can know
596 bPotentiallyReadFrom
= true;
599 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
600 || isa
<ArrayInitLoopExpr
>(parent
) || isa
<ExprWithCleanups
>(parent
))
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;
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.
625 else if (auto caseStmt
= dyn_cast
<CaseStmt
>(parent
))
627 bPotentiallyReadFrom
= caseStmt
->getLHS() == child
|| caseStmt
->getRHS() == child
;
630 else if (auto ifStmt
= dyn_cast
<IfStmt
>(parent
))
632 bPotentiallyReadFrom
= ifStmt
->getCond() == child
;
635 else if (auto doStmt
= dyn_cast
<DoStmt
>(parent
))
637 bPotentiallyReadFrom
= doStmt
->getCond() == child
;
640 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
642 if (arraySubscriptExpr
->getIdx() == child
)
644 bPotentiallyReadFrom
= true;
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
)
660 bPotentiallyReadFrom
= true;
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
||
673 if (operatorCallExpr
->getArg(0) == child
&& assignmentOp
)
675 else if (op
== OO_GreaterGreaterEqual
&& operatorCallExpr
->getArg(1) == child
)
676 break; // this is a write-only call
679 bPotentiallyReadFrom
= true;
683 else if (auto cxxMemberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
))
685 bool bWriteOnlyCall
= false;
686 const CXXMethodDecl
* callee
= cxxMemberCallExpr
->getMethodDecl();
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;
715 bPotentiallyReadFrom
= true;
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
);
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;
734 bPotentiallyReadFrom
= true;
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;
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
))
766 bPotentiallyReadFrom
= true;
775 DiagnosticsEngine::Warning
,
776 "oh dear, what can the matter be?",
777 compat::getBeginLoc(memberExpr
))
778 << memberExpr
->getSourceRange();
780 DiagnosticsEngine::Note
,
782 compat::getBeginLoc(parent
))
783 << parent
->getSourceRange();
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
))
807 // if we're inside a block that looks like
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())
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;
820 auto walkUp
= [&]() {
822 auto parentsRange
= compiler
.getASTContext().getParents(*parent
);
823 parent
= parentsRange
.begin() == parentsRange
.end() ? nullptr : parentsRange
.begin()->get
<Stmt
>();
829 // check if we have an expression like
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;
844 if (isa
<CXXReinterpretCastExpr
>(parent
))
846 // once we see one of these, there is not much useful we can know
847 bPotentiallyWrittenTo
= true;
850 else if (isa
<CastExpr
>(parent
) || isa
<MemberExpr
>(parent
) || isa
<ParenExpr
>(parent
) || isa
<ParenListExpr
>(parent
)
851 || isa
<ArrayInitLoopExpr
>(parent
) || isa
<ExprWithCleanups
>(parent
))
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;
864 else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
))
866 if (arraySubscriptExpr
->getIdx() == child
)
870 else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
))
872 auto callee
= getCallee(operatorCallExpr
);
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;
888 bPotentiallyWrittenTo
= true; // conservative, could improve
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
);
907 else if (IsPassedByNonConst(fieldDecl
, child
, cxxMemberCallExpr
, CalleeWrapper(calleeMethodDecl
)))
908 bPotentiallyWrittenTo
= true;
911 bPotentiallyWrittenTo
= true; // can happen in templates
914 else if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(parent
))
916 if (IsPassedByNonConst(fieldDecl
, child
, cxxConstructExpr
, CalleeWrapper(cxxConstructExpr
)))
917 bPotentiallyWrittenTo
= true;
920 else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
))
922 auto callee
= getCallee(callExpr
);
924 if (IsPassedByNonConst(fieldDecl
, child
, callExpr
, *callee
))
925 bPotentiallyWrittenTo
= true;
927 bPotentiallyWrittenTo
= true; // conservative, could improve
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
;
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;
947 else if (isa
<ReturnStmt
>(parent
))
949 if (insideFunctionDecl
)
951 auto tc
= loplugin::TypeCheck(insideFunctionDecl
->getReturnType());
952 if (tc
.LvalueReference().NonConst())
953 bPotentiallyWrittenTo
= true;
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
))
982 bPotentiallyWrittenTo
= true;
991 DiagnosticsEngine::Warning
,
992 "oh dear, what can the matter be? writtenTo=%0",
993 compat::getBeginLoc(memberExpr
))
994 << bPotentiallyWrittenTo
995 << memberExpr
->getSourceRange();
999 DiagnosticsEngine::Note
,
1001 compat::getBeginLoc(parent
))
1002 << parent
->getSourceRange();
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())
1028 else if (tc
.Class("set").StdNamespace()
1029 || tc
.Class("unordered_set").StdNamespace())
1033 else if (tc
.Class("map").StdNamespace()
1034 || tc
.Class("unordered_map").StdNamespace())
1038 else if (tc
.Class("Sequence").Namespace("uno").Namespace("star").Namespace("sun").Namespace("com").GlobalNamespace())
1045 if (calleeMethodDecl
->isOverloadedOperator())
1047 auto oo
= calleeMethodDecl
->getOverloadedOperator();
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
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")
1074 if (name
== "getArray" || name
== "begin" || name
== "end")
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())
1097 for (unsigned i
= 0; i
< len
; ++i
)
1098 if (callExpr
.getArg(i
) == child
)
1099 if (loplugin::TypeCheck(calleeFunctionDecl
.getParamType(i
)).LvalueReference().NonConst())
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
) )) {
1112 // ignore stuff that forms part of the stable URE interface
1113 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(cxxConstructorDecl
->getLocation()))) {
1117 // templates make EvaluateAsInt crash inside clang
1118 if (cxxConstructorDecl
->isDependentContext())
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
)
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
);
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
) ))
1145 QualType varType
= initListExpr
->getType().getDesugaredType(compiler
.getASTContext());
1146 auto recordType
= varType
->getAs
<RecordType
>();
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
);
1160 bool UnusedFields::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
1162 const Decl
* decl
= declRefExpr
->getDecl();
1163 const FieldDecl
* fieldDecl
= dyn_cast
<FieldDecl
>(decl
);
1167 fieldDecl
= fieldDecl
->getCanonicalDecl();
1168 if (ignoreLocation(compat::getBeginLoc(fieldDecl
))) {
1171 // ignore stuff that forms part of the stable URE interface
1172 if (isInUnoIncludeFile(compiler
.getSourceManager().getSpellingLoc(fieldDecl
->getLocation()))) {
1175 checkTouchedFromOutside(fieldDecl
, declRefExpr
);
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
1187 touchedFromOutsideSet
.insert(fieldInfo
);
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
1197 if (memberExprParentFunction
->getParent() == fieldDecl
->getParent()) {
1198 touchedFromInsideSet
.insert(fieldInfo
);
1199 if (!constructorDecl
)
1200 touchedFromOutsideConstructorSet
.insert(fieldInfo
);
1202 if (fieldDecl
->getName() == "m_pShell")
1204 if (memberExprParentFunction
)
1205 memberExprParentFunction
->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();
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);
1237 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */