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/.
13 #include <unordered_set>
17 #include "config_clang.h"
18 #include "clang/AST/CXXInheritance.h"
19 #include "clang/AST/StmtVisitor.h"
21 // This checker aims to pull buried assignments out of complex expressions,
22 // where they are quite hard to notice amidst the other conditional logic.
26 class BuriedAssign
: public loplugin::FilteringPlugin
<BuriedAssign
>
29 explicit BuriedAssign(loplugin::InstantiationData
const& data
)
30 : FilteringPlugin(data
)
34 virtual void run() override
36 std::string
fn(handler
.getMainFileName());
37 loplugin::normalizeDotDotInFilePath(fn
);
39 // code where I don't have a better alternative
40 if (fn
== SRCDIR
"/sal/osl/unx/profile.cxx")
42 if (fn
== SRCDIR
"/sal/rtl/uri.cxx")
44 if (fn
== SRCDIR
"/sal/osl/unx/process.cxx")
46 if (fn
== SRCDIR
"/sal/rtl/bootstrap.cxx")
48 if (fn
== SRCDIR
"/i18npool/source/textconversion/genconv_dict.cxx")
50 if (fn
== SRCDIR
"/soltools/cpp/_macro.c")
52 if (fn
== SRCDIR
"/stoc/source/inspect/introspection.cxx")
54 if (fn
== SRCDIR
"/tools/source/fsys/urlobj.cxx")
56 if (fn
== SRCDIR
"/sax/source/tools/fastserializer.cxx")
58 if (fn
== SRCDIR
"/svl/source/crypto/cryptosign.cxx")
60 if (fn
== SRCDIR
"/svl/source/numbers/zforfind.cxx")
62 if (fn
== SRCDIR
"/svl/source/numbers/zformat.cxx")
64 if (fn
== SRCDIR
"/svl/source/numbers/zforscan.cxx")
66 if (fn
== SRCDIR
"/svl/source/numbers/zforlist.cxx")
68 if (fn
== SRCDIR
"/vcl/source/window/debugevent.cxx")
70 if (fn
== SRCDIR
"/vcl/source/control/scrbar.cxx")
72 if (fn
== SRCDIR
"/vcl/source/gdi/bitmap3.cxx")
74 if (fn
== SRCDIR
"/vcl/source/window/menu.cxx")
76 if (fn
== SRCDIR
"/vcl/source/fontsubset/sft.cxx")
78 if (fn
== SRCDIR
"/vcl/unx/generic/print/prtsetup.cxx")
80 if (fn
== SRCDIR
"/svtools/source/brwbox/brwbox1.cxx")
82 if (fn
== SRCDIR
"/svtools/source/control/valueset.cxx")
84 if (fn
== SRCDIR
"/basic/source/runtime/iosys.cxx")
86 if (fn
== SRCDIR
"/basic/source/runtime/runtime.cxx")
88 if (fn
== SRCDIR
"/basic/source/sbx/sbxvalue.cxx")
90 if (fn
== SRCDIR
"/basic/source/sbx/sbxvalue.cxx")
92 if (fn
== SRCDIR
"/sfx2/source/dialog/templdlg.cxx")
94 if (fn
== SRCDIR
"/sfx2/source/view/viewfrm.cxx")
96 if (fn
== SRCDIR
"/connectivity/source/commontools/dbtools.cxx")
98 if (fn
== SRCDIR
"/xmloff/source/style/xmlnumfi.cxx")
100 if (fn
== SRCDIR
"/xmloff/source/style/xmlnumfe .cxx")
102 if (fn
== SRCDIR
"/editeng/source/items/textitem.cxx")
104 if (fn
== SRCDIR
"/editeng/source/rtf/rtfitem.cxx")
106 if (fn
== SRCDIR
"/editeng/source/rtf/svxrtf.cxx")
108 if (fn
== SRCDIR
"/editeng/source/misc/svxacorr.cxx")
110 if (fn
== SRCDIR
"/svx/source/items/numfmtsh.cxx")
112 if (fn
== SRCDIR
"/svx/source/dialog/hdft.cxx")
114 if (fn
== SRCDIR
"/cui/source/dialogs/insdlg.cxx")
116 if (fn
== SRCDIR
"/cui/source/tabpages/paragrph.cxx")
118 if (fn
== SRCDIR
"/cui/source/tabpages/page.cxx")
120 if (fn
== SRCDIR
"/cui/source/tabpages/border.cxx")
122 if (fn
== SRCDIR
"/cui/source/tabpages/chardlg.cxx")
124 if (fn
== SRCDIR
"/cui/source/tabpages/numpages.cxx")
126 if (fn
== SRCDIR
"/cui/source/dialogs/SpellDialog.cxx")
128 if (fn
== SRCDIR
"/oox/source/drawingml/diagram/diagramlayoutatoms.cxx")
130 if (fn
== SRCDIR
"/dbaccess/source/core/dataaccess/intercept.cxx")
132 if (fn
== SRCDIR
"/sw/writerfilter/dmapper/DomainMapper.cxx")
134 if (fn
== SRCDIR
"/sw/writerfilter/dmapper/DomainMapper_Impl.cxx")
136 if (fn
== SRCDIR
"/lotuswordpro/source/filter/lwptablelayout.cxx")
138 if (fn
== SRCDIR
"/i18npool/source/characterclassification/cclass_unicode_parser.cxx")
140 if (fn
== SRCDIR
"/sd/source/filter/eppt/pptx-animations.cxx")
142 if (fn
== SRCDIR
"/sc/source/core/tool/address.cxx")
144 if (fn
== SRCDIR
"/sc/source/core/tool/interpr1.cxx")
146 if (fn
== SRCDIR
"/sc/source/core/tool/interpr4.cxx")
148 if (fn
== SRCDIR
"/sc/source/core/tool/interpr5.cxx")
150 if (fn
== SRCDIR
"/sc/source/core/tool/compiler.cxx")
152 if (fn
== SRCDIR
"/sc/source/core/data/table4.cxx")
154 if (fn
== SRCDIR
"/sc/source/ui/drawfunc/fudraw.cxx")
156 if (fn
== SRCDIR
"/sc/source/filter/xml/xmlcelli.cxx")
158 if (fn
== SRCDIR
"/sc/source/ui/miscdlgs/crnrdlg.cxx")
160 if (fn
== SRCDIR
"/sc/source/ui/app/inputwin.cxx")
162 if (fn
== SRCDIR
"/sc/source/ui/view/viewfun2.cxx")
164 if (fn
== SRCDIR
"/sc/source/ui/unoobj/docuno.cxx")
166 if (fn
== SRCDIR
"/sc/source/ui/view/gridwin.cxx")
168 if (fn
== SRCDIR
"/sw/source/core/crsr/callnk.cxx")
170 if (fn
== SRCDIR
"/sw/source/core/crsr/findtxt.cxx")
172 if (fn
== SRCDIR
"/sw/source/core/crsr/crsrsh.cxx")
174 if (fn
== SRCDIR
"/sw/source/core/crsr/crstrvl.cxx")
176 if (fn
== SRCDIR
"/sw/source/core/doc/doccomp.cxx")
178 if (fn
== SRCDIR
"/sw/source/core/doc/docedt.cxx")
180 if (fn
== SRCDIR
"/sw/source/core/doc/docfly.cxx")
182 if (fn
== SRCDIR
"/sw/source/core/doc/DocumentRedlineManager.cxx")
184 if (fn
== SRCDIR
"/sw/source/core/doc/notxtfrm.cxx")
186 if (fn
== SRCDIR
"/sw/source/core/docnode/node.cxx")
188 if (fn
== SRCDIR
"/sw/source/core/layout/ftnfrm.cxx")
190 if (fn
== SRCDIR
"/sw/source/core/table/swtable.cxx")
192 if (fn
== SRCDIR
"/sw/source/core/unocore/unoframe.cxx")
194 if (fn
== SRCDIR
"/sw/source/filter/xml/xmlimp.cxx")
196 if (fn
== SRCDIR
"/sw/source/uibase/docvw/edtwin.cxx")
198 if (fn
== SRCDIR
"/sw/source/uibase/shells/langhelper.cxx")
200 if (fn
== SRCDIR
"/sw/source/uibase/shells/tabsh.cxx")
202 if (fn
== SRCDIR
"/sw/source/uibase/shells/textsh1.cxx")
204 if (fn
== SRCDIR
"/sw/source/uibase/uiview/view2.cxx")
206 if (fn
== SRCDIR
"/starmath/source/mathtype.cxx")
208 if (fn
== SRCDIR
"/starmath/source/mathmlexport.cxx")
210 if (fn
== SRCDIR
"/starmath/source/view.cxx")
212 if (fn
== SRCDIR
"/xmlhelp/source/treeview/tvread.cxx")
214 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
217 bool VisitBinaryOperator(BinaryOperator
const*);
218 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const*);
219 bool VisitCompoundStmt(CompoundStmt
const*);
220 bool VisitIfStmt(IfStmt
const*);
221 bool VisitLabelStmt(LabelStmt
const*);
222 bool VisitForStmt(ForStmt
const*);
223 bool VisitCXXForRangeStmt(CXXForRangeStmt
const*);
224 bool VisitWhileStmt(WhileStmt
const*);
225 bool VisitDoStmt(DoStmt
const*);
226 bool VisitCaseStmt(CaseStmt
const*);
227 bool VisitDefaultStmt(DefaultStmt
const*);
228 bool VisitVarDecl(VarDecl
const*);
229 bool VisitCXXFoldExpr(CXXFoldExpr
const*);
232 void MarkIfAssignment(Stmt
const*);
233 void MarkAll(Stmt
const*);
234 void MarkConditionForControlLoops(Expr
const*);
236 std::unordered_set
<const Stmt
*> m_handled
;
239 static bool isAssignmentOp(clang::BinaryOperatorKind op
)
241 // We ignore BO_ShrAssign i.e. >>= because we use that everywhere for
242 // extracting data from css::uno::Any
243 return op
== BO_Assign
|| op
== BO_MulAssign
|| op
== BO_DivAssign
|| op
== BO_RemAssign
244 || op
== BO_AddAssign
|| op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_AndAssign
245 || op
== BO_XorAssign
|| op
== BO_OrAssign
;
248 static bool isAssignmentOp(clang::OverloadedOperatorKind Opc
)
250 // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
252 // Except that we ignore OO_GreaterGreaterEqual i.e. >>= because we use that everywhere for
253 // extracting data from css::uno::Any
254 return Opc
== OO_Equal
|| Opc
== OO_StarEqual
|| Opc
== OO_SlashEqual
|| Opc
== OO_PercentEqual
255 || Opc
== OO_PlusEqual
|| Opc
== OO_MinusEqual
|| Opc
== OO_LessLessEqual
256 || Opc
== OO_AmpEqual
|| Opc
== OO_CaretEqual
|| Opc
== OO_PipeEqual
;
259 static const Expr
* IgnoreImplicitAndConversionOperator(const Expr
* expr
)
261 expr
= expr
->IgnoreImplicit();
262 if (auto memberCall
= dyn_cast
<CXXMemberCallExpr
>(expr
))
264 if (auto conversionDecl
= dyn_cast_or_null
<CXXConversionDecl
>(memberCall
->getMethodDecl()))
266 if (!conversionDecl
->isExplicit())
267 expr
= memberCall
->getImplicitObjectArgument()->IgnoreImplicit();
273 bool BuriedAssign::VisitBinaryOperator(BinaryOperator
const* binaryOp
)
275 if (ignoreLocation(binaryOp
))
277 if (binaryOp
->getBeginLoc().isMacroID())
279 if (!isAssignmentOp(binaryOp
->getOpcode()))
281 auto expr
= IgnoreImplicitAndConversionOperator(binaryOp
->getRHS());
282 if (auto rhs
= dyn_cast
<BinaryOperator
>(expr
))
284 // Ignore chained assignment.
285 // TODO limit this to only ordinary assignment
286 if (isAssignmentOp(rhs
->getOpcode()))
287 m_handled
.insert(rhs
);
289 else if (auto rhs
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
291 // Ignore chained assignment.
292 // TODO limit this to only ordinary assignment
293 if (isAssignmentOp(rhs
->getOperator()))
294 m_handled
.insert(rhs
);
296 else if (auto cxxConstruct
= dyn_cast
<CXXConstructExpr
>(expr
))
298 if (cxxConstruct
->getNumArgs() == 1)
299 MarkIfAssignment(cxxConstruct
->getArg(0));
301 if (!m_handled
.insert(binaryOp
).second
)
304 // assignment in constructor
305 StringRef aFileName
= getFilenameOfLocation(
306 compiler
.getSourceManager().getSpellingLoc(binaryOp
->getBeginLoc()));
307 if (loplugin::hasPathnamePrefix(aFileName
, SRCDIR
"/include/comphelper/flagguard.hxx"))
310 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
311 binaryOp
->getBeginLoc())
312 << binaryOp
->getSourceRange();
313 //getParentStmt(getParentStmt(getParentStmt(getParentStmt(getParentStmt(getParentStmt(binaryOp))))))->dump();
317 bool BuriedAssign::VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const* cxxOper
)
319 if (ignoreLocation(cxxOper
))
321 if (cxxOper
->getBeginLoc().isMacroID())
323 if (!isAssignmentOp(cxxOper
->getOperator()))
325 auto expr
= IgnoreImplicitAndConversionOperator(cxxOper
->getArg(1));
326 if (auto rhs
= dyn_cast
<BinaryOperator
>(expr
))
328 // Ignore chained assignment.
329 // TODO limit this to only ordinary assignment
330 if (isAssignmentOp(rhs
->getOpcode()))
331 m_handled
.insert(rhs
);
333 else if (auto rhs
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
335 // Ignore chained assignment.
336 // TODO limit this to only ordinary assignment
337 if (isAssignmentOp(rhs
->getOperator()))
338 m_handled
.insert(rhs
);
340 else if (auto cxxConstruct
= dyn_cast
<CXXConstructExpr
>(expr
))
342 if (cxxConstruct
->getNumArgs() == 1)
343 MarkIfAssignment(cxxConstruct
->getArg(0));
345 if (!m_handled
.insert(cxxOper
).second
)
347 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
348 cxxOper
->getBeginLoc())
349 << cxxOper
->getSourceRange();
350 //getParentStmt(getParentStmt(getParentStmt(getParentStmt(getParentStmt(cxxOper)))))->dump();
354 bool BuriedAssign::VisitCompoundStmt(CompoundStmt
const* compoundStmt
)
356 if (ignoreLocation(compoundStmt
))
358 for (auto i
= compoundStmt
->child_begin(); i
!= compoundStmt
->child_end(); ++i
)
360 if (auto expr
= dyn_cast
<Expr
>(*i
))
362 expr
= expr
->IgnoreImplicit();
363 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
365 // ignore comma-chained statements at this level
366 if (binaryOp
->getOpcode() == BO_Comma
)
368 MarkIfAssignment(binaryOp
->getLHS());
369 MarkIfAssignment(binaryOp
->getRHS());
373 MarkIfAssignment(expr
);
379 void BuriedAssign::MarkIfAssignment(Stmt
const* stmt
)
381 if (auto expr
= dyn_cast_or_null
<Expr
>(stmt
))
383 expr
= expr
->IgnoreImplicit();
384 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
386 if (isAssignmentOp(binaryOp
->getOpcode()))
388 m_handled
.insert(expr
);
389 MarkIfAssignment(binaryOp
->getRHS()); // in case it is chained
391 else if (binaryOp
->getOpcode() == BO_Comma
)
393 MarkIfAssignment(binaryOp
->getLHS());
394 MarkIfAssignment(binaryOp
->getRHS());
397 else if (auto cxxOper
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
399 if (isAssignmentOp(cxxOper
->getOperator()))
401 m_handled
.insert(expr
);
402 MarkIfAssignment(cxxOper
->getArg(1)); // in case it is chained
408 void BuriedAssign::MarkAll(Stmt
const* stmt
)
410 m_handled
.insert(stmt
);
411 for (auto it
= stmt
->child_begin(); it
!= stmt
->child_end(); ++it
)
416 * Restrict this to cases where the buried assignment is part of the first
417 * condition inside the if condition. Other cases tend to be too hard
418 * too extract (notably in sw/)
420 bool BuriedAssign::VisitIfStmt(IfStmt
const* ifStmt
)
422 if (ignoreLocation(ifStmt
))
424 MarkIfAssignment(ifStmt
->getThen());
425 MarkIfAssignment(ifStmt
->getElse());
427 auto expr
= ifStmt
->getCond();
428 expr
= IgnoreImplicitAndConversionOperator(expr
);
429 expr
= expr
->IgnoreParens();
430 expr
= IgnoreImplicitAndConversionOperator(expr
);
433 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
435 if (isAssignmentOp(binaryOp
->getOpcode()))
437 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
439 << expr
->getSourceRange();
441 else if (binaryOp
->isComparisonOp())
444 = dyn_cast
<BinaryOperator
>(binaryOp
->getLHS()->IgnoreParenImpCasts()))
446 if (!binaryOp
->getRHS()->isValueDependent()
447 && binaryOp
->getRHS()->isCXX11ConstantExpr(compiler
.getASTContext())
448 && isAssignmentOp(binaryOp2
->getOpcode()))
449 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
451 << expr
->getSourceRange();
454 = dyn_cast
<BinaryOperator
>(binaryOp
->getRHS()->IgnoreParenImpCasts()))
456 if (!binaryOp
->getLHS()->isValueDependent()
457 && binaryOp
->getLHS()->isCXX11ConstantExpr(compiler
.getASTContext())
458 && isAssignmentOp(binaryOp2
->getOpcode()))
459 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
461 << expr
->getSourceRange();
464 else if (binaryOp
->isLogicalOp())
467 = dyn_cast
<BinaryOperator
>(binaryOp
->getLHS()->IgnoreParenImpCasts()))
469 if (isAssignmentOp(binaryOp2
->getOpcode()))
470 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
472 << expr
->getSourceRange();
476 else if (auto operCall
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
478 // Ignore chained assignment.
479 // TODO limit this to only ordinary assignment
480 if (isAssignmentOp(operCall
->getOperator()))
482 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
484 << expr
->getSourceRange();
491 bool BuriedAssign::VisitCaseStmt(CaseStmt
const* stmt
)
493 if (ignoreLocation(stmt
))
495 MarkIfAssignment(stmt
->getSubStmt());
499 bool BuriedAssign::VisitDefaultStmt(DefaultStmt
const* stmt
)
501 if (ignoreLocation(stmt
))
503 MarkIfAssignment(stmt
->getSubStmt());
507 bool BuriedAssign::VisitWhileStmt(WhileStmt
const* stmt
)
509 if (ignoreLocation(stmt
))
511 MarkConditionForControlLoops(stmt
->getCond());
512 MarkIfAssignment(stmt
->getBody());
516 bool BuriedAssign::VisitDoStmt(DoStmt
const* stmt
)
518 if (ignoreLocation(stmt
))
520 MarkConditionForControlLoops(stmt
->getCond());
521 MarkIfAssignment(stmt
->getBody());
528 * while ((x = foo() < 0)
529 * is considered idiomatic.
531 void BuriedAssign::MarkConditionForControlLoops(Expr
const* expr
)
535 expr
= expr
->IgnoreImplicit();
537 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
539 // ignore comma-chained statements at this level
540 if (binaryOp
->getOpcode() == BO_Comma
)
542 MarkConditionForControlLoops(binaryOp
->getLHS());
543 MarkConditionForControlLoops(binaryOp
->getRHS());
548 // unwrap conversion to bool
549 if (auto memberCall
= dyn_cast
<CXXMemberCallExpr
>(expr
))
551 if (memberCall
->getMethodDecl() && isa
<CXXConversionDecl
>(memberCall
->getMethodDecl()))
553 // TODO check that the conversion is converting to bool
554 expr
= memberCall
->getImplicitObjectArgument()->IgnoreImplicit();
558 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
560 // handle: ((xxx = foo()) != error)
561 if (binaryOp
->isComparisonOp())
563 MarkIfAssignment(binaryOp
->getLHS()->IgnoreImplicit()->IgnoreParens());
564 MarkIfAssignment(binaryOp
->getRHS()->IgnoreImplicit()->IgnoreParens());
567 else if (auto cxxOper
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
569 // handle: ((xxx = foo()) != error)
570 if (cxxOper
->isComparisonOp())
572 MarkIfAssignment(cxxOper
->getArg(0)->IgnoreImplicit()->IgnoreParens());
573 MarkIfAssignment(cxxOper
->getArg(1)->IgnoreImplicit()->IgnoreParens());
575 // handle: (!(xxx = foo()))
576 else if (cxxOper
->getOperator() == OO_Exclaim
)
577 MarkIfAssignment(cxxOper
->getArg(0)->IgnoreImplicit()->IgnoreParens());
579 else if (auto parenExpr
= dyn_cast
<ParenExpr
>(expr
))
581 // handle: ((xxx = foo()))
582 MarkIfAssignment(parenExpr
->getSubExpr()->IgnoreImplicit());
584 else if (auto unaryOp
= dyn_cast
<UnaryOperator
>(expr
))
586 // handle: (!(xxx = foo()))
587 MarkIfAssignment(unaryOp
->getSubExpr()->IgnoreImplicit()->IgnoreParens());
590 MarkIfAssignment(expr
);
593 bool BuriedAssign::VisitForStmt(ForStmt
const* stmt
)
595 if (ignoreLocation(stmt
))
597 MarkIfAssignment(stmt
->getInit());
598 MarkConditionForControlLoops(stmt
->getCond());
599 MarkIfAssignment(stmt
->getInc());
600 MarkIfAssignment(stmt
->getBody());
604 bool BuriedAssign::VisitCXXForRangeStmt(CXXForRangeStmt
const* stmt
)
606 if (ignoreLocation(stmt
))
608 MarkIfAssignment(stmt
->getBody());
612 bool BuriedAssign::VisitLabelStmt(LabelStmt
const* stmt
)
614 if (ignoreLocation(stmt
))
616 MarkIfAssignment(stmt
->getSubStmt());
620 bool BuriedAssign::VisitVarDecl(VarDecl
const* stmt
)
622 if (ignoreLocation(stmt
))
626 auto expr
= IgnoreImplicitAndConversionOperator(stmt
->getInit());
627 MarkIfAssignment(expr
);
628 if (auto cxxConstruct
= dyn_cast
<CXXConstructExpr
>(expr
))
630 if (cxxConstruct
->getNumArgs() == 1)
631 MarkIfAssignment(cxxConstruct
->getArg(0));
637 bool BuriedAssign::VisitCXXFoldExpr(CXXFoldExpr
const* stmt
)
639 if (ignoreLocation(stmt
))
641 MarkConditionForControlLoops(stmt
->getLHS());
642 MarkConditionForControlLoops(stmt
->getRHS());
646 // off by default because it uses getParentStmt so it's more expensive to run
647 loplugin::Plugin::Registration
<BuriedAssign
> X("buriedassign", false);
650 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */