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/.
12 #include <unordered_set>
13 #include <unordered_map>
19 #include "functionaddress.hxx"
22 Find pointer and reference params that can be declared const.
24 This is not a sophisticated analysis. It deliberately skips all of the hard cases for now.
25 It is an exercise in getting the most benefit for the least effort.
31 public loplugin::FunctionAddress
<loplugin::FilteringPlugin
<ConstParams
>>
34 explicit ConstParams(loplugin::InstantiationData
const & data
): FunctionAddress(data
) {}
36 virtual void run() override
{
37 std::string
fn(handler
.getMainFileName());
38 loplugin::normalizeDotDotInFilePath(fn
);
39 if (loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sal/")
40 || fn
== SRCDIR
"/jurt/source/pipe/staticsalhack.cxx"
41 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/bridges/")
42 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/binaryurp/")
43 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/stoc/")
44 || loplugin::hasPathnamePrefix(fn
, WORKDIR
"/YaccTarget/unoidl/source/sourceprovider-parser.cxx")
45 // some weird calling through a function pointer
46 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/svtools/source/table/defaultinputhandler.cxx")
47 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sdext/source/pdfimport/test/pdfunzip.cxx")
49 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/basic/source/sbx/sbxdec.cxx")
50 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sfx2/source/doc/syspath.cxx")
51 // ignore this for now
52 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/libreofficekit")
53 // FunctionAddress not working well enough here
54 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/pyuno/source/module/pyuno_struct.cxx")
55 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/pyuno/source/module/pyuno.cxx")
56 || loplugin::hasPathnamePrefix(fn
, SRCDIR
"/sw/source/filter/ascii/ascatr.cxx")
60 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
62 for (const ParmVarDecl
*pParmVarDecl
: interestingParamSet
) {
63 auto functionDecl
= parmToFunction
[pParmVarDecl
];
64 auto canonicalDecl
= functionDecl
->getCanonicalDecl();
65 if (getFunctionsWithAddressTaken().find(canonicalDecl
)
66 != getFunctionsWithAddressTaken().end())
70 std::string fname
= functionDecl
->getQualifiedNameAsString();
72 DiagnosticsEngine::Warning
,
73 "this parameter can be const %0",
74 compat::getBeginLoc(pParmVarDecl
))
75 << fname
<< pParmVarDecl
->getSourceRange();
76 if (canonicalDecl
->getLocation() != functionDecl
->getLocation()) {
77 unsigned idx
= pParmVarDecl
->getFunctionScopeIndex();
78 const ParmVarDecl
* pOther
= canonicalDecl
->getParamDecl(idx
);
80 DiagnosticsEngine::Note
,
81 "canonical parameter declaration here",
82 compat::getBeginLoc(pOther
))
83 << pOther
->getSourceRange();
85 //functionDecl->dump();
89 bool TraverseFunctionDecl(FunctionDecl
*);
90 bool TraverseCXXMethodDecl(CXXMethodDecl
* f
);
91 bool TraverseCXXConstructorDecl(CXXConstructorDecl
* f
);
92 bool VisitDeclRefExpr(const DeclRefExpr
*);
95 bool CheckTraverseFunctionDecl(FunctionDecl
*);
96 bool checkIfCanBeConst(const Stmt
*, const ParmVarDecl
*);
97 // integral or enumeration or const * or const &
98 bool isOkForParameter(const QualType
& qt
);
99 bool isPointerOrReferenceToNonConst(const QualType
& qt
);
101 std::unordered_set
<const ParmVarDecl
*> interestingParamSet
;
102 std::unordered_map
<const ParmVarDecl
*, const FunctionDecl
*> parmToFunction
;
103 FunctionDecl
* currentFunctionDecl
= nullptr;
106 bool ConstParams::TraverseFunctionDecl(FunctionDecl
* functionDecl
)
108 // We cannot short-circuit the traverse here entirely without breaking the
109 // loplugin::FunctionAddress stuff.
110 auto prev
= currentFunctionDecl
;
111 if (CheckTraverseFunctionDecl(functionDecl
))
112 currentFunctionDecl
= functionDecl
;
113 auto rv
= FunctionAddress::TraverseFunctionDecl(functionDecl
);
114 currentFunctionDecl
= prev
;
117 bool ConstParams::TraverseCXXMethodDecl(CXXMethodDecl
* f
)
119 auto prev
= currentFunctionDecl
;
120 if (CheckTraverseFunctionDecl(f
))
121 currentFunctionDecl
= f
;
122 auto rv
= FunctionAddress::TraverseCXXMethodDecl(f
);
123 currentFunctionDecl
= prev
;
126 bool ConstParams::TraverseCXXConstructorDecl(CXXConstructorDecl
* f
)
128 auto prev
= currentFunctionDecl
;
129 if (CheckTraverseFunctionDecl(f
))
130 currentFunctionDecl
= f
;
131 auto rv
= FunctionAddress::TraverseCXXConstructorDecl(f
);
132 currentFunctionDecl
= prev
;
136 bool ConstParams::CheckTraverseFunctionDecl(FunctionDecl
* functionDecl
)
138 if (ignoreLocation(functionDecl
) || !functionDecl
->isThisDeclarationADefinition()) {
141 // ignore stuff that forms part of the stable URE interface
142 if (isInUnoIncludeFile(functionDecl
)) {
145 if (functionDecl
->isDeleted())
147 // ignore virtual methods
148 if (isa
<CXXMethodDecl
>(functionDecl
)
149 && dyn_cast
<CXXMethodDecl
>(functionDecl
)->isVirtual() ) {
153 if (functionDecl
->isMain()) {
157 // ignore the macros from include/tools/link.hxx
158 auto canonicalDecl
= functionDecl
->getCanonicalDecl();
159 if (compiler
.getSourceManager().isMacroBodyExpansion(compat::getBeginLoc(canonicalDecl
))
160 || compiler
.getSourceManager().isMacroArgExpansion(compat::getBeginLoc(canonicalDecl
))) {
161 StringRef name
{ Lexer::getImmediateMacroName(
162 compat::getBeginLoc(canonicalDecl
), compiler
.getSourceManager(), compiler
.getLangOpts()) };
163 if (name
.startswith("DECL_LINK") || name
.startswith("DECL_STATIC_LINK"))
165 auto loc2
= compat::getImmediateExpansionRange(compiler
.getSourceManager(), compat::getBeginLoc(canonicalDecl
)).first
;
166 if (compiler
.getSourceManager().isMacroBodyExpansion(loc2
))
168 StringRef name2
{ Lexer::getImmediateMacroName(
169 loc2
, compiler
.getSourceManager(), compiler
.getLangOpts()) };
170 if (name2
.startswith("DECL_DLLPRIVATE_LINK"))
175 if (functionDecl
->getIdentifier())
177 StringRef name
= functionDecl
->getName();
178 if ( name
== "file_write"
179 || name
== "SalMainPipeExchangeSignal_impl"
180 || name
.startswith("SbRtl_")
182 || name
== "GoPrevious"
183 || name
.startswith("Read_F_")
184 // UNO component entry points
185 || name
.endswith("component_getFactory")
186 || name
== "egiGraphicExport"
187 || name
== "etiGraphicExport"
188 || name
== "epsGraphicExport"
189 // callback for some external code?
190 || name
== "ScAddInAsyncCallBack"
191 // used as function pointers
192 || name
== "Read_Footnote"
193 || name
== "Read_Field"
194 || name
== "Read_And"
195 // passed as a LINK<> to another method
196 || name
== "GlobalBasicErrorHdl_Impl"
198 || name
== "extract_throw" || name
== "readProp"
200 || name
== "signalDragDropReceived" || name
== "signal_column_clicked" || name
== "signal_key_press"
206 std::string fqn
= functionDecl
->getQualifiedNameAsString();
207 if ( fqn
== "connectivity::jdbc::GlobalRef::set"
208 || fqn
== "(anonymous namespace)::ReorderNotifier::operator()"
209 || fqn
== "static_txtattr_cast")
212 // calculate the ones we want to check
213 bool foundInterestingParam
= false;
214 for (const ParmVarDecl
*pParmVarDecl
: functionDecl
->parameters()) {
215 // ignore unused params
216 if (pParmVarDecl
->getName().empty()
217 || pParmVarDecl
->hasAttr
<UnusedAttr
>())
219 auto const type
= loplugin::TypeCheck(pParmVarDecl
->getType());
220 if (!( type
.Pointer().NonConst()
221 || type
.LvalueReference().NonConst()))
223 // since we normally can't change typedefs, just ignore them
224 if (isa
<TypedefType
>(pParmVarDecl
->getType()))
226 // some typedefs turn into these
227 if (isa
<DecayedType
>(pParmVarDecl
->getType()))
229 // TODO ignore these for now, has some effects I don't understand
230 if (type
.Pointer().Pointer())
232 // const is meaningless when applied to function pointer types
233 if (pParmVarDecl
->getType()->isFunctionPointerType())
235 interestingParamSet
.insert(pParmVarDecl
);
236 parmToFunction
[pParmVarDecl
] = functionDecl
;
237 foundInterestingParam
= true;
239 return foundInterestingParam
;
242 bool ConstParams::VisitDeclRefExpr( const DeclRefExpr
* declRefExpr
)
244 if (!currentFunctionDecl
)
246 const ParmVarDecl
* parmVarDecl
= dyn_cast_or_null
<ParmVarDecl
>(declRefExpr
->getDecl());
249 if (interestingParamSet
.find(parmVarDecl
) == interestingParamSet
.end())
251 if (!checkIfCanBeConst(declRefExpr
, parmVarDecl
))
252 interestingParamSet
.erase(parmVarDecl
);
256 // Walk up from a statement that contains a DeclRefExpr, checking if the usage means that the
257 // related ParamVarDecl can be const.
258 bool ConstParams::checkIfCanBeConst(const Stmt
* stmt
, const ParmVarDecl
* parmVarDecl
)
260 const Stmt
* parent
= getParentStmt( stmt
);
263 // check if we're inside a CXXCtorInitializer
264 auto parentsRange
= compiler
.getASTContext().getParents(*stmt
);
265 if ( parentsRange
.begin() != parentsRange
.end())
267 if (auto cxxConstructorDecl
= dyn_cast_or_null
<CXXConstructorDecl
>(parentsRange
.begin()->get
<Decl
>()))
269 for ( auto cxxCtorInitializer
: cxxConstructorDecl
->inits())
271 if ( cxxCtorInitializer
->getInit() == stmt
)
273 if (cxxCtorInitializer
->isAnyMemberInitializer())
275 // if the member is not pointer-to-const or ref-to-const or value, we cannot make the param const
276 auto fieldDecl
= cxxCtorInitializer
->getAnyMember();
277 auto tc
= loplugin::TypeCheck(fieldDecl
->getType());
278 if (tc
.Pointer() || tc
.LvalueReference())
279 return tc
.Pointer().Const() || tc
.LvalueReference().Const();
285 // probably base initialiser, but no simple way to look up the relevant constructor decl
291 if (auto varDecl
= dyn_cast_or_null
<VarDecl
>(parentsRange
.begin()->get
<Decl
>()))
293 return isOkForParameter(varDecl
->getType());
299 DiagnosticsEngine::Warning
,
301 compat::getBeginLoc(stmt
))
302 << stmt
->getSourceRange();
306 if (auto unaryOperator
= dyn_cast
<UnaryOperator
>(parent
)) {
307 UnaryOperator::Opcode op
= unaryOperator
->getOpcode();
308 if (op
== UO_AddrOf
|| op
== UO_PreInc
|| op
== UO_PostInc
309 || op
== UO_PreDec
|| op
== UO_PostDec
) {
312 if (op
== UO_Deref
) {
313 return checkIfCanBeConst(parent
, parmVarDecl
);
316 } else if (auto binaryOp
= dyn_cast
<BinaryOperator
>(parent
)) {
317 BinaryOperator::Opcode op
= binaryOp
->getOpcode();
318 if (binaryOp
->getRHS() == stmt
&& op
== BO_Assign
) {
319 return isOkForParameter(binaryOp
->getLHS()->getType());
321 if (binaryOp
->getRHS() == stmt
) {
324 if (op
== BO_Assign
|| op
== BO_PtrMemD
|| op
== BO_PtrMemI
|| op
== BO_MulAssign
325 || op
== BO_DivAssign
|| op
== BO_RemAssign
|| op
== BO_AddAssign
326 || op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_ShrAssign
327 || op
== BO_AndAssign
|| op
== BO_XorAssign
|| op
== BO_OrAssign
) {
330 // for pointer arithmetic need to check parent
331 if (binaryOp
->getType()->isPointerType()) {
332 return checkIfCanBeConst(parent
, parmVarDecl
);
335 } else if (auto constructExpr
= dyn_cast
<CXXConstructExpr
>(parent
)) {
336 const CXXConstructorDecl
* constructorDecl
= constructExpr
->getConstructor();
337 for (unsigned i
= 0; i
< constructExpr
->getNumArgs(); ++i
) {
338 if (constructExpr
->getArg(i
) == stmt
) {
339 return isOkForParameter(constructorDecl
->getParamDecl(i
)->getType());
342 } else if (auto operatorCallExpr
= dyn_cast
<CXXOperatorCallExpr
>(parent
)) {
343 const CXXMethodDecl
* calleeMethodDecl
= dyn_cast_or_null
<CXXMethodDecl
>(operatorCallExpr
->getDirectCallee());
344 if (calleeMethodDecl
) {
346 if (calleeMethodDecl
->getNumParams() == 0)
347 return calleeMethodDecl
->isConst();
348 // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
350 auto Opc
= operatorCallExpr
->getOperator();
351 if (Opc
== OO_Equal
|| Opc
== OO_StarEqual
||
352 Opc
== OO_SlashEqual
|| Opc
== OO_PercentEqual
||
353 Opc
== OO_PlusEqual
|| Opc
== OO_MinusEqual
||
354 Opc
== OO_LessLessEqual
|| Opc
== OO_GreaterGreaterEqual
||
355 Opc
== OO_AmpEqual
|| Opc
== OO_CaretEqual
||
358 if (operatorCallExpr
->getArg(0) == stmt
) // assigning to the param
360 // not all operator= take a const&
361 return isOkForParameter(calleeMethodDecl
->getParamDecl(0)->getType());
363 if (operatorCallExpr
->getOperator() == OO_Subscript
&& operatorCallExpr
->getArg(1) == stmt
)
365 if (operatorCallExpr
->getOperator() == OO_EqualEqual
|| operatorCallExpr
->getOperator() == OO_ExclaimEqual
)
368 if (operatorCallExpr
->getArg(0) == stmt
)
369 return calleeMethodDecl
->isConst();
370 unsigned const n
= std::min(
371 operatorCallExpr
->getNumArgs(),
372 calleeMethodDecl
->getNumParams() + 1);
373 for (unsigned i
= 1; i
< n
; ++i
)
374 if (operatorCallExpr
->getArg(i
) == stmt
) {
375 auto qt
= calleeMethodDecl
->getParamDecl(i
- 1)->getType();
376 return isOkForParameter(qt
);
379 const Expr
* callee
= operatorCallExpr
->getCallee()->IgnoreParenImpCasts();
380 const DeclRefExpr
* dr
= dyn_cast
<DeclRefExpr
>(callee
);
381 const FunctionDecl
* calleeFunctionDecl
= nullptr;
383 calleeFunctionDecl
= dyn_cast
<FunctionDecl
>(dr
->getDecl());
385 if (calleeFunctionDecl
) {
386 for (unsigned i
= 0; i
< operatorCallExpr
->getNumArgs(); ++i
) {
387 if (operatorCallExpr
->getArg(i
) == stmt
) {
388 return isOkForParameter(calleeFunctionDecl
->getParamDecl(i
)->getType());
394 } else if (auto callExpr
= dyn_cast
<CallExpr
>(parent
)) {
395 QualType functionType
= callExpr
->getCallee()->getType();
396 if (functionType
->isFunctionPointerType()) {
397 functionType
= functionType
->getPointeeType();
399 if (const FunctionProtoType
* prototype
= functionType
->getAs
<FunctionProtoType
>()) {
400 // TODO could do better
401 if (prototype
->isVariadic()) {
404 if (callExpr
->getCallee() == stmt
) {
407 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
408 if (callExpr
->getArg(i
) == stmt
) {
409 return isOkForParameter(prototype
->getParamType(i
));
413 const FunctionDecl
* calleeFunctionDecl
= callExpr
->getDirectCallee();
414 if (calleeFunctionDecl
)
416 if (auto memberCallExpr
= dyn_cast
<CXXMemberCallExpr
>(parent
)) {
417 const MemberExpr
* memberExpr
= dyn_cast
<MemberExpr
>(stmt
);
418 if (memberExpr
&& memberCallExpr
->getImplicitObjectArgument() == memberExpr
->getBase())
420 const CXXMethodDecl
* calleeMethodDecl
= dyn_cast
<CXXMethodDecl
>(calleeFunctionDecl
);
421 return calleeMethodDecl
->isConst();
424 // TODO could do better
425 if (calleeFunctionDecl
->isVariadic()) {
428 if (callExpr
->getCallee() == stmt
) {
431 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
432 if (i
>= calleeFunctionDecl
->getNumParams()) // can happen in template code
434 if (callExpr
->getArg(i
) == stmt
) {
435 return isOkForParameter(calleeFunctionDecl
->getParamDecl(i
)->getType());
440 } else if (auto callExpr
= dyn_cast
<ObjCMessageExpr
>(parent
)) {
441 if (callExpr
->getInstanceReceiver() == stmt
) {
444 if (auto const method
= callExpr
->getMethodDecl()) {
445 // TODO could do better
446 if (method
->isVariadic()) {
449 assert(method
->param_size() == callExpr
->getNumArgs());
450 for (unsigned i
= 0; i
< callExpr
->getNumArgs(); ++i
) {
451 if (callExpr
->getArg(i
) == stmt
) {
452 return isOkForParameter(
453 method
->param_begin()[i
]->getType());
457 } else if (isa
<CXXReinterpretCastExpr
>(parent
)) {
459 } else if (isa
<CXXConstCastExpr
>(parent
)) {
461 } else if (isa
<CastExpr
>(parent
)) { // all other cast expression subtypes
462 if (auto e
= dyn_cast
<ExplicitCastExpr
>(parent
)) {
463 if (loplugin::TypeCheck(e
->getTypeAsWritten()).Void()) {
464 if (auto const sub
= dyn_cast
<DeclRefExpr
>(
465 e
->getSubExpr()->IgnoreParenImpCasts()))
467 if (sub
->getDecl() == parmVarDecl
)
472 return checkIfCanBeConst(parent
, parmVarDecl
);
473 } else if (isa
<MemberExpr
>(parent
)) {
474 return checkIfCanBeConst(parent
, parmVarDecl
);
475 } else if (auto arraySubscriptExpr
= dyn_cast
<ArraySubscriptExpr
>(parent
)) {
476 if (arraySubscriptExpr
->getIdx() == stmt
)
478 return checkIfCanBeConst(parent
, parmVarDecl
);
479 } else if (isa
<ParenExpr
>(parent
)) {
480 return checkIfCanBeConst(parent
, parmVarDecl
);
481 } else if (isa
<DeclStmt
>(parent
)) {
482 // TODO could do better here, but would require tracking the target(s)
484 } else if (isa
<ReturnStmt
>(parent
)) {
485 return !isPointerOrReferenceToNonConst(currentFunctionDecl
->getReturnType());
486 } else if (isa
<InitListExpr
>(parent
)) {
488 } else if (isa
<IfStmt
>(parent
)) {
490 } else if (isa
<WhileStmt
>(parent
)) {
492 } else if (isa
<ForStmt
>(parent
)) {
494 } else if (isa
<CompoundStmt
>(parent
)) {
496 } else if (isa
<SwitchStmt
>(parent
)) {
498 } else if (isa
<DoStmt
>(parent
)) {
500 } else if (isa
<CXXDeleteExpr
>(parent
)) {
502 } else if (isa
<VAArgExpr
>(parent
)) {
504 } else if (isa
<CXXDependentScopeMemberExpr
>(parent
)) {
506 } else if (isa
<MaterializeTemporaryExpr
>(parent
)) {
507 return checkIfCanBeConst(parent
, parmVarDecl
);
508 } else if (auto conditionalExpr
= dyn_cast
<ConditionalOperator
>(parent
)) {
509 if (conditionalExpr
->getCond() == stmt
)
511 return checkIfCanBeConst(parent
, parmVarDecl
);
512 } else if (isa
<UnaryExprOrTypeTraitExpr
>(parent
)) {
514 } else if (auto cxxNewExpr
= dyn_cast
<CXXNewExpr
>(parent
)) {
515 for (unsigned i
= 0; i
< cxxNewExpr
->getNumPlacementArgs(); ++i
)
516 if (cxxNewExpr
->getPlacementArg(i
) == stmt
)
519 } else if (auto lambdaExpr
= dyn_cast
<LambdaExpr
>(parent
)) {
520 for (auto it
= lambdaExpr
->capture_begin(); it
!= lambdaExpr
->capture_end(); ++it
)
522 if (it
->capturesVariable() && it
->getCapturedVar() == parmVarDecl
)
523 return it
->getCaptureKind() != LCK_ByRef
;
526 } else if (isa
<CXXTypeidExpr
>(parent
)) {
528 } else if (isa
<ParenListExpr
>(parent
)) {
529 return false; // could be improved, seen in constructors when calling base class constructor
530 } else if (isa
<CXXUnresolvedConstructExpr
>(parent
)) {
532 } else if (isa
<UnresolvedMemberExpr
>(parent
)) {
534 } else if (isa
<PackExpansionExpr
>(parent
)) {
536 } else if (isa
<ExprWithCleanups
>(parent
)) {
537 return checkIfCanBeConst(parent
, parmVarDecl
);
538 } else if (isa
<CaseStmt
>(parent
)) {
540 } else if (isa
<CXXPseudoDestructorExpr
>(parent
)) {
542 } else if (isa
<CXXDependentScopeMemberExpr
>(parent
)) {
544 } else if (isa
<ObjCIvarRefExpr
>(parent
)) {
545 return checkIfCanBeConst(parent
, parmVarDecl
);
550 DiagnosticsEngine::Warning
,
551 "oh dear, what can the matter be?",
552 compat::getBeginLoc(parent
))
553 << parent
->getSourceRange();
557 bool ConstParams::isOkForParameter(const QualType
& qt
) {
558 if (qt
->isIntegralOrEnumerationType())
560 auto const type
= loplugin::TypeCheck(qt
);
561 if (type
.Pointer()) {
562 return bool(type
.Pointer().Const());
563 } else if (type
.LvalueReference().Const().Pointer()) {
564 // If we have a method that takes (T* t) and it calls std::vector<T*>::push_back
565 // then the type of push_back is T * const &
566 // There is probably a more elegant way to check this, but it will probably require
567 // recalculating types while walking up the AST.
569 } else if (type
.LvalueReference()) {
570 return bool(type
.LvalueReference().Const());
575 bool ConstParams::isPointerOrReferenceToNonConst(const QualType
& qt
) {
576 auto const type
= loplugin::TypeCheck(qt
);
577 if (type
.Pointer()) {
578 return !bool(type
.Pointer().Const());
579 } else if (type
.LvalueReference()) {
580 return !bool(type
.LvalueReference().Const());
585 loplugin::Plugin::Registration
< ConstParams
> X("constparams", false);
589 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */