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
"/sfx2/source/dialog/templdlg.cxx")
92 if (fn
== SRCDIR
"/sfx2/source/view/viewfrm.cxx")
94 if (fn
== SRCDIR
"/connectivity/source/commontools/dbtools.cxx")
96 if (fn
== SRCDIR
"/xmloff/source/style/xmlnumfi.cxx")
98 if (fn
== SRCDIR
"/xmloff/source/style/xmlnumfe .cxx")
100 if (fn
== SRCDIR
"/editeng/source/items/textitem.cxx")
102 if (fn
== SRCDIR
"/editeng/source/rtf/rtfitem.cxx")
104 if (fn
== SRCDIR
"/editeng/source/rtf/svxrtf.cxx")
106 if (fn
== SRCDIR
"/editeng/source/misc/svxacorr.cxx")
108 if (fn
== SRCDIR
"/svx/source/items/numfmtsh.cxx")
110 if (fn
== SRCDIR
"/svx/source/dialog/hdft.cxx")
112 if (fn
== SRCDIR
"/cui/source/dialogs/insdlg.cxx")
114 if (fn
== SRCDIR
"/cui/source/tabpages/paragrph.cxx")
116 if (fn
== SRCDIR
"/cui/source/tabpages/page.cxx")
118 if (fn
== SRCDIR
"/cui/source/tabpages/border.cxx")
120 if (fn
== SRCDIR
"/cui/source/tabpages/chardlg.cxx")
122 if (fn
== SRCDIR
"/cui/source/tabpages/numpages.cxx")
124 if (fn
== SRCDIR
"/cui/source/dialogs/SpellDialog.cxx")
126 if (fn
== SRCDIR
"/oox/source/drawingml/diagram/diagramlayoutatoms.cxx")
128 if (fn
== SRCDIR
"/dbaccess/source/core/dataaccess/intercept.cxx")
130 if (fn
== SRCDIR
"/sw/writerfilter/dmapper/DomainMapper.cxx")
132 if (fn
== SRCDIR
"/sw/writerfilter/dmapper/DomainMapper_Impl.cxx")
134 if (fn
== SRCDIR
"/lotuswordpro/source/filter/lwptablelayout.cxx")
136 if (fn
== SRCDIR
"/i18npool/source/characterclassification/cclass_unicode_parser.cxx")
138 if (fn
== SRCDIR
"/sd/source/filter/eppt/pptx-animations.cxx")
140 if (fn
== SRCDIR
"/sc/source/core/tool/address.cxx")
142 if (fn
== SRCDIR
"/sc/source/core/tool/interpr1.cxx")
144 if (fn
== SRCDIR
"/sc/source/core/tool/interpr4.cxx")
146 if (fn
== SRCDIR
"/sc/source/core/tool/interpr5.cxx")
148 if (fn
== SRCDIR
"/sc/source/core/tool/compiler.cxx")
150 if (fn
== SRCDIR
"/sc/source/core/data/table4.cxx")
152 if (fn
== SRCDIR
"/sc/source/ui/drawfunc/fudraw.cxx")
154 if (fn
== SRCDIR
"/sc/source/filter/xml/xmlcelli.cxx")
156 if (fn
== SRCDIR
"/sc/source/ui/miscdlgs/crnrdlg.cxx")
158 if (fn
== SRCDIR
"/sc/source/ui/app/inputwin.cxx")
160 if (fn
== SRCDIR
"/sc/source/ui/view/viewfun2.cxx")
162 if (fn
== SRCDIR
"/sc/source/ui/unoobj/docuno.cxx")
164 if (fn
== SRCDIR
"/sc/source/ui/view/gridwin.cxx")
166 if (fn
== SRCDIR
"/sw/source/core/crsr/callnk.cxx")
168 if (fn
== SRCDIR
"/sw/source/core/crsr/findtxt.cxx")
170 if (fn
== SRCDIR
"/sw/source/core/crsr/crsrsh.cxx")
172 if (fn
== SRCDIR
"/sw/source/core/crsr/crstrvl.cxx")
174 if (fn
== SRCDIR
"/sw/source/core/doc/doccomp.cxx")
176 if (fn
== SRCDIR
"/sw/source/core/doc/docedt.cxx")
178 if (fn
== SRCDIR
"/sw/source/core/doc/docfly.cxx")
180 if (fn
== SRCDIR
"/sw/source/core/doc/DocumentRedlineManager.cxx")
182 if (fn
== SRCDIR
"/sw/source/core/doc/notxtfrm.cxx")
184 if (fn
== SRCDIR
"/sw/source/core/docnode/node.cxx")
186 if (fn
== SRCDIR
"/sw/source/core/layout/ftnfrm.cxx")
188 if (fn
== SRCDIR
"/sw/source/core/table/swtable.cxx")
190 if (fn
== SRCDIR
"/sw/source/core/unocore/unoframe.cxx")
192 if (fn
== SRCDIR
"/sw/source/filter/xml/xmlimp.cxx")
194 if (fn
== SRCDIR
"/sw/source/uibase/docvw/edtwin.cxx")
196 if (fn
== SRCDIR
"/sw/source/uibase/shells/langhelper.cxx")
198 if (fn
== SRCDIR
"/sw/source/uibase/shells/tabsh.cxx")
200 if (fn
== SRCDIR
"/sw/source/uibase/shells/textsh1.cxx")
202 if (fn
== SRCDIR
"/sw/source/uibase/uiview/view2.cxx")
204 if (fn
== SRCDIR
"/starmath/source/mathtype.cxx")
206 if (fn
== SRCDIR
"/starmath/source/mathmlexport.cxx")
208 if (fn
== SRCDIR
"/starmath/source/view.cxx")
210 if (fn
== SRCDIR
"/xmlhelp/source/treeview/tvread.cxx")
212 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
215 bool VisitBinaryOperator(BinaryOperator
const*);
216 bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const*);
217 bool VisitCompoundStmt(CompoundStmt
const*);
218 bool VisitIfStmt(IfStmt
const*);
219 bool VisitLabelStmt(LabelStmt
const*);
220 bool VisitForStmt(ForStmt
const*);
221 bool VisitCXXForRangeStmt(CXXForRangeStmt
const*);
222 bool VisitWhileStmt(WhileStmt
const*);
223 bool VisitDoStmt(DoStmt
const*);
224 bool VisitCaseStmt(CaseStmt
const*);
225 bool VisitDefaultStmt(DefaultStmt
const*);
226 bool VisitVarDecl(VarDecl
const*);
227 bool VisitCXXFoldExpr(CXXFoldExpr
const*);
230 void MarkIfAssignment(Stmt
const*);
231 void MarkAll(Stmt
const*);
232 void MarkConditionForControlLoops(Expr
const*);
234 std::unordered_set
<const Stmt
*> m_handled
;
237 static bool isAssignmentOp(clang::BinaryOperatorKind op
)
239 // We ignore BO_ShrAssign i.e. >>= because we use that everywhere for
240 // extracting data from css::uno::Any
241 return op
== BO_Assign
|| op
== BO_MulAssign
|| op
== BO_DivAssign
|| op
== BO_RemAssign
242 || op
== BO_AddAssign
|| op
== BO_SubAssign
|| op
== BO_ShlAssign
|| op
== BO_AndAssign
243 || op
== BO_XorAssign
|| op
== BO_OrAssign
;
246 static bool isAssignmentOp(clang::OverloadedOperatorKind Opc
)
248 // Same logic as CXXOperatorCallExpr::isAssignmentOp(), which our supported clang
250 // Except that we ignore OO_GreaterGreaterEqual i.e. >>= because we use that everywhere for
251 // extracting data from css::uno::Any
252 return Opc
== OO_Equal
|| Opc
== OO_StarEqual
|| Opc
== OO_SlashEqual
|| Opc
== OO_PercentEqual
253 || Opc
== OO_PlusEqual
|| Opc
== OO_MinusEqual
|| Opc
== OO_LessLessEqual
254 || Opc
== OO_AmpEqual
|| Opc
== OO_CaretEqual
|| Opc
== OO_PipeEqual
;
257 static const Expr
* IgnoreImplicitAndConversionOperator(const Expr
* expr
)
259 expr
= expr
->IgnoreImplicit();
260 if (auto memberCall
= dyn_cast
<CXXMemberCallExpr
>(expr
))
262 if (auto conversionDecl
= dyn_cast_or_null
<CXXConversionDecl
>(memberCall
->getMethodDecl()))
264 if (!conversionDecl
->isExplicit())
265 expr
= memberCall
->getImplicitObjectArgument()->IgnoreImplicit();
271 bool BuriedAssign::VisitBinaryOperator(BinaryOperator
const* binaryOp
)
273 if (ignoreLocation(binaryOp
))
275 if (binaryOp
->getBeginLoc().isMacroID())
277 if (!isAssignmentOp(binaryOp
->getOpcode()))
279 auto expr
= IgnoreImplicitAndConversionOperator(binaryOp
->getRHS());
280 if (auto rhs
= dyn_cast
<BinaryOperator
>(expr
))
282 // Ignore chained assignment.
283 // TODO limit this to only ordinary assignment
284 if (isAssignmentOp(rhs
->getOpcode()))
285 m_handled
.insert(rhs
);
287 else if (auto rhs
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
289 // Ignore chained assignment.
290 // TODO limit this to only ordinary assignment
291 if (isAssignmentOp(rhs
->getOperator()))
292 m_handled
.insert(rhs
);
294 else if (auto cxxConstruct
= dyn_cast
<CXXConstructExpr
>(expr
))
296 if (cxxConstruct
->getNumArgs() == 1)
297 MarkIfAssignment(cxxConstruct
->getArg(0));
299 if (!m_handled
.insert(binaryOp
).second
)
302 // assignment in constructor
303 StringRef aFileName
= getFilenameOfLocation(
304 compiler
.getSourceManager().getSpellingLoc(binaryOp
->getBeginLoc()));
305 if (loplugin::hasPathnamePrefix(aFileName
, SRCDIR
"/include/comphelper/flagguard.hxx"))
308 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
309 binaryOp
->getBeginLoc())
310 << binaryOp
->getSourceRange();
311 //getParentStmt(getParentStmt(getParentStmt(getParentStmt(getParentStmt(getParentStmt(binaryOp))))))->dump();
315 bool BuriedAssign::VisitCXXOperatorCallExpr(CXXOperatorCallExpr
const* cxxOper
)
317 if (ignoreLocation(cxxOper
))
319 if (cxxOper
->getBeginLoc().isMacroID())
321 if (!isAssignmentOp(cxxOper
->getOperator()))
323 auto expr
= IgnoreImplicitAndConversionOperator(cxxOper
->getArg(1));
324 if (auto rhs
= dyn_cast
<BinaryOperator
>(expr
))
326 // Ignore chained assignment.
327 // TODO limit this to only ordinary assignment
328 if (isAssignmentOp(rhs
->getOpcode()))
329 m_handled
.insert(rhs
);
331 else if (auto rhs
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
333 // Ignore chained assignment.
334 // TODO limit this to only ordinary assignment
335 if (isAssignmentOp(rhs
->getOperator()))
336 m_handled
.insert(rhs
);
338 else if (auto cxxConstruct
= dyn_cast
<CXXConstructExpr
>(expr
))
340 if (cxxConstruct
->getNumArgs() == 1)
341 MarkIfAssignment(cxxConstruct
->getArg(0));
343 if (!m_handled
.insert(cxxOper
).second
)
345 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
346 cxxOper
->getBeginLoc())
347 << cxxOper
->getSourceRange();
348 //getParentStmt(getParentStmt(getParentStmt(getParentStmt(getParentStmt(cxxOper)))))->dump();
352 bool BuriedAssign::VisitCompoundStmt(CompoundStmt
const* compoundStmt
)
354 if (ignoreLocation(compoundStmt
))
356 for (auto i
= compoundStmt
->child_begin(); i
!= compoundStmt
->child_end(); ++i
)
358 if (auto expr
= dyn_cast
<Expr
>(*i
))
360 expr
= expr
->IgnoreImplicit();
361 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
363 // ignore comma-chained statements at this level
364 if (binaryOp
->getOpcode() == BO_Comma
)
366 MarkIfAssignment(binaryOp
->getLHS());
367 MarkIfAssignment(binaryOp
->getRHS());
371 MarkIfAssignment(expr
);
377 void BuriedAssign::MarkIfAssignment(Stmt
const* stmt
)
379 if (auto expr
= dyn_cast_or_null
<Expr
>(stmt
))
381 expr
= expr
->IgnoreImplicit();
382 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
384 if (isAssignmentOp(binaryOp
->getOpcode()))
386 m_handled
.insert(expr
);
387 MarkIfAssignment(binaryOp
->getRHS()); // in case it is chained
389 else if (binaryOp
->getOpcode() == BO_Comma
)
391 MarkIfAssignment(binaryOp
->getLHS());
392 MarkIfAssignment(binaryOp
->getRHS());
395 else if (auto cxxOper
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
397 if (isAssignmentOp(cxxOper
->getOperator()))
399 m_handled
.insert(expr
);
400 MarkIfAssignment(cxxOper
->getArg(1)); // in case it is chained
406 void BuriedAssign::MarkAll(Stmt
const* stmt
)
408 m_handled
.insert(stmt
);
409 for (auto it
= stmt
->child_begin(); it
!= stmt
->child_end(); ++it
)
414 * Restrict this to cases where the buried assignment is part of the first
415 * condition inside the if condition. Other cases tend to be too hard
416 * too extract (notably in sw/)
418 bool BuriedAssign::VisitIfStmt(IfStmt
const* ifStmt
)
420 if (ignoreLocation(ifStmt
))
422 MarkIfAssignment(ifStmt
->getThen());
423 MarkIfAssignment(ifStmt
->getElse());
425 auto expr
= ifStmt
->getCond();
426 expr
= IgnoreImplicitAndConversionOperator(expr
);
427 expr
= expr
->IgnoreParens();
428 expr
= IgnoreImplicitAndConversionOperator(expr
);
431 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
433 if (isAssignmentOp(binaryOp
->getOpcode()))
435 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
437 << expr
->getSourceRange();
439 else if (binaryOp
->isComparisonOp())
442 = dyn_cast
<BinaryOperator
>(binaryOp
->getLHS()->IgnoreParenImpCasts()))
444 if (!binaryOp
->getRHS()->isValueDependent()
445 && binaryOp
->getRHS()->isCXX11ConstantExpr(compiler
.getASTContext())
446 && isAssignmentOp(binaryOp2
->getOpcode()))
447 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
449 << expr
->getSourceRange();
452 = dyn_cast
<BinaryOperator
>(binaryOp
->getRHS()->IgnoreParenImpCasts()))
454 if (!binaryOp
->getLHS()->isValueDependent()
455 && binaryOp
->getLHS()->isCXX11ConstantExpr(compiler
.getASTContext())
456 && isAssignmentOp(binaryOp2
->getOpcode()))
457 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
459 << expr
->getSourceRange();
462 else if (binaryOp
->isLogicalOp())
465 = dyn_cast
<BinaryOperator
>(binaryOp
->getLHS()->IgnoreParenImpCasts()))
467 if (isAssignmentOp(binaryOp2
->getOpcode()))
468 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
470 << expr
->getSourceRange();
474 else if (auto operCall
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
476 // Ignore chained assignment.
477 // TODO limit this to only ordinary assignment
478 if (isAssignmentOp(operCall
->getOperator()))
480 report(DiagnosticsEngine::Warning
, "buried assignment, rather put on own line",
482 << expr
->getSourceRange();
489 bool BuriedAssign::VisitCaseStmt(CaseStmt
const* stmt
)
491 if (ignoreLocation(stmt
))
493 MarkIfAssignment(stmt
->getSubStmt());
497 bool BuriedAssign::VisitDefaultStmt(DefaultStmt
const* stmt
)
499 if (ignoreLocation(stmt
))
501 MarkIfAssignment(stmt
->getSubStmt());
505 bool BuriedAssign::VisitWhileStmt(WhileStmt
const* stmt
)
507 if (ignoreLocation(stmt
))
509 MarkConditionForControlLoops(stmt
->getCond());
510 MarkIfAssignment(stmt
->getBody());
514 bool BuriedAssign::VisitDoStmt(DoStmt
const* stmt
)
516 if (ignoreLocation(stmt
))
518 MarkConditionForControlLoops(stmt
->getCond());
519 MarkIfAssignment(stmt
->getBody());
526 * while ((x = foo() < 0)
527 * is considered idiomatic.
529 void BuriedAssign::MarkConditionForControlLoops(Expr
const* expr
)
533 expr
= expr
->IgnoreImplicit();
535 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
537 // ignore comma-chained statements at this level
538 if (binaryOp
->getOpcode() == BO_Comma
)
540 MarkConditionForControlLoops(binaryOp
->getLHS());
541 MarkConditionForControlLoops(binaryOp
->getRHS());
546 // unwrap conversion to bool
547 if (auto memberCall
= dyn_cast
<CXXMemberCallExpr
>(expr
))
549 if (memberCall
->getMethodDecl() && isa
<CXXConversionDecl
>(memberCall
->getMethodDecl()))
551 // TODO check that the conversion is converting to bool
552 expr
= memberCall
->getImplicitObjectArgument()->IgnoreImplicit();
556 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(expr
))
558 // handle: ((xxx = foo()) != error)
559 if (binaryOp
->isComparisonOp())
561 MarkIfAssignment(binaryOp
->getLHS()->IgnoreImplicit()->IgnoreParens());
562 MarkIfAssignment(binaryOp
->getRHS()->IgnoreImplicit()->IgnoreParens());
565 else if (auto cxxOper
= dyn_cast
<CXXOperatorCallExpr
>(expr
))
567 // handle: ((xxx = foo()) != error)
568 if (cxxOper
->isComparisonOp())
570 MarkIfAssignment(cxxOper
->getArg(0)->IgnoreImplicit()->IgnoreParens());
571 MarkIfAssignment(cxxOper
->getArg(1)->IgnoreImplicit()->IgnoreParens());
573 // handle: (!(xxx = foo()))
574 else if (cxxOper
->getOperator() == OO_Exclaim
)
575 MarkIfAssignment(cxxOper
->getArg(0)->IgnoreImplicit()->IgnoreParens());
577 else if (auto parenExpr
= dyn_cast
<ParenExpr
>(expr
))
579 // handle: ((xxx = foo()))
580 MarkIfAssignment(parenExpr
->getSubExpr()->IgnoreImplicit());
582 else if (auto unaryOp
= dyn_cast
<UnaryOperator
>(expr
))
584 // handle: (!(xxx = foo()))
585 MarkIfAssignment(unaryOp
->getSubExpr()->IgnoreImplicit()->IgnoreParens());
588 MarkIfAssignment(expr
);
591 bool BuriedAssign::VisitForStmt(ForStmt
const* stmt
)
593 if (ignoreLocation(stmt
))
595 MarkIfAssignment(stmt
->getInit());
596 MarkConditionForControlLoops(stmt
->getCond());
597 MarkIfAssignment(stmt
->getInc());
598 MarkIfAssignment(stmt
->getBody());
602 bool BuriedAssign::VisitCXXForRangeStmt(CXXForRangeStmt
const* stmt
)
604 if (ignoreLocation(stmt
))
606 MarkIfAssignment(stmt
->getBody());
610 bool BuriedAssign::VisitLabelStmt(LabelStmt
const* stmt
)
612 if (ignoreLocation(stmt
))
614 MarkIfAssignment(stmt
->getSubStmt());
618 bool BuriedAssign::VisitVarDecl(VarDecl
const* stmt
)
620 if (ignoreLocation(stmt
))
624 auto expr
= IgnoreImplicitAndConversionOperator(stmt
->getInit());
625 MarkIfAssignment(expr
);
626 if (auto cxxConstruct
= dyn_cast
<CXXConstructExpr
>(expr
))
628 if (cxxConstruct
->getNumArgs() == 1)
629 MarkIfAssignment(cxxConstruct
->getArg(0));
635 bool BuriedAssign::VisitCXXFoldExpr(CXXFoldExpr
const* stmt
)
637 if (ignoreLocation(stmt
))
639 MarkConditionForControlLoops(stmt
->getLHS());
640 MarkConditionForControlLoops(stmt
->getRHS());
644 // off by default because it uses getParentStmt so it's more expensive to run
645 loplugin::Plugin::Registration
<BuriedAssign
> X("buriedassign", false);
648 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */