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_map>
14 #include <unordered_set>
18 #include "clang/AST/CXXInheritance.h"
19 #include "clang/AST/StmtVisitor.h"
22 This is a kind of simplified dead-store analysis.
24 We are looking for patterns like:
27 which can be simplified to
33 which can be simplified to
36 TODO Improve this plugin to make it safer. We should really be checking the following
37 conditions inside the RHS of the second statement:
38 If the variable is having it's address taken, or a non-const method called on it,
39 on passed by non-const-ref.
44 //static bool startswith(const std::string& rStr, const char* pSubStr) {
45 // return rStr.compare(0, strlen(pSubStr), pSubStr) == 0;
47 class SequentialAssign
: public loplugin::FilteringPlugin
<SequentialAssign
>
50 explicit SequentialAssign(loplugin::InstantiationData
const& data
)
51 : FilteringPlugin(data
)
55 virtual void run() override
57 std::string
fn(handler
.getMainFileName());
58 loplugin::normalizeDotDotInFilePath(fn
);
59 // places where the existing code style just looks better
60 // TODO lots of these would be unnecessary if I taught the plugin
61 // to ignore vars which are assigned to repeatedly
62 if (fn
== SRCDIR
"/vcl/source/helper/commandinfoprovider.cxx"
63 || fn
== SRCDIR
"/basegfx/source/polygon/b2dpolygonclipper.cxx"
64 || fn
== SRCDIR
"/i18nlangtag/source/isolang/insys.cxx"
65 || fn
== SRCDIR
"/vcl/unx/generic/fontmanager/fontconfig.cxx"
66 || fn
== SRCDIR
"/svtools/source/filter/exportdialog.cxx"
67 || fn
== SRCDIR
"/svtools/source/control/ruler.cxx"
68 || fn
== SRCDIR
"/basic/qa/cppunit/test_scanner.cxx"
69 || fn
== SRCDIR
"/basic/source/uno/namecont.cxx"
70 || fn
== SRCDIR
"/test/source/sheet/xnamedrange.cxx"
71 || fn
== SRCDIR
"/i18npool/qa/cppunit/test_breakiterator.cxx"
72 || fn
== SRCDIR
"/i18npool/source/localedata/LocaleNode.cxx"
73 || fn
== SRCDIR
"/i18npool/source/transliteration/transliteration_Ignore.cxx"
74 || fn
== SRCDIR
"/i18npool/qa/cppunit/test_textsearch.cxx"
75 || fn
== SRCDIR
"/framework/source/jobs/jobdata.cxx"
76 || fn
== SRCDIR
"/framework/source/services/pathsettings.cxx"
77 || fn
== SRCDIR
"/xmloff/source/chart/SchXMLTools.cxx"
78 || fn
== SRCDIR
"/svx/source/tbxctrls/Palette.cxx"
79 || fn
== SRCDIR
"/svx/source/sdr/contact/objectcontactofpageview.cxx"
80 || fn
== SRCDIR
"/svx/source/form/fmservs.cxx"
81 || fn
== SRCDIR
"/svx/source/svdraw/svdograf.cxx"
82 || fn
== SRCDIR
"/svx/source/accessibility/AccessibleShape.cxx"
83 || fn
== SRCDIR
"/svx/source/fmcomp/fmgridcl.cxx"
84 || fn
== SRCDIR
"/chart2/source/tools/CharacterProperties.cxx"
85 || fn
== SRCDIR
"/svx/source/dialog/dialcontrol.cxx"
86 || fn
== SRCDIR
"/connectivity/source/commontools/TTableHelper.cxx"
87 || fn
== SRCDIR
"/svx/source/dialog/_bmpmask.cxx"
88 || fn
== SRCDIR
"/media/noel/disk2/libo4/cui/source/dialogs/SignSignatureLineDialog.cxx"
89 || fn
== SRCDIR
"/filter/source/msfilter/msdffimp.cxx"
90 || fn
== SRCDIR
"/filter/source/flash/swffilter.cxx"
91 || fn
== SRCDIR
"/cui/source/dialogs/SignSignatureLineDialog.cxx"
92 || fn
== SRCDIR
"/cui/source/dialogs/screenshotannotationdlg.cxx"
93 || fn
== SRCDIR
"/cui/source/options/optupdt.cxx"
94 || fn
== SRCDIR
"/dbaccess/source/ui/querydesign/querycontroller.cxx"
95 || fn
== SRCDIR
"/dbaccess/source/ui/dlg/DbAdminImpl.cxx"
96 || fn
== SRCDIR
"/dbaccess/source/ui/querydesign/JoinController.cxx"
97 || fn
== SRCDIR
"/dbaccess/source/ui/misc/TokenWriter.cxx"
98 || fn
== SRCDIR
"/dbaccess/source/ui/misc/TokenWriter.cxx"
99 || fn
== SRCDIR
"/dbaccess/source/ui/misc/dbsubcomponentcontroller.cxx"
100 || fn
== SRCDIR
"/dbaccess/source/core/api/querycomposer.cxx"
101 || fn
== SRCDIR
"/desktop/source/lib/init.cxx"
102 || fn
== SRCDIR
"/lotuswordpro/source/filter/lwpfribmark.cxx"
103 || fn
== SRCDIR
"/tools/qa/cppunit/test_color.cxx"
104 || fn
== SRCDIR
"/sc/qa/unit/ucalc.cxx"
105 || fn
== SRCDIR
"/sc/source/ui/view/printfun.cxx"
106 || fn
== SRCDIR
"/sc/source/ui/view/preview.cxx"
107 || fn
== SRCDIR
"/sw/source/core/doc/tblafmt.cxx"
108 || fn
== SRCDIR
"/sw/source/core/draw/dflyobj.cxx"
109 || fn
== SRCDIR
"/sw/source/core/doc/DocumentDrawModelManager.cxx"
110 || fn
== SRCDIR
"/sw/source/core/edit/edfcol.cxx"
111 || fn
== SRCDIR
"/sw/source/filter/ww8/ww8toolbar.cxx"
112 || fn
== SRCDIR
"/sw/source/ui/fldui/fldvar.cxx"
113 || fn
== SRCDIR
"/sw/source/filter/ww8/ww8atr.cxx"
114 || fn
== SRCDIR
"/sd/source/ui/accessibility/AccessiblePageShape.cxx"
115 || fn
== SRCDIR
"/xmlsecurity/source/xmlsec/nss/nssinitializer.cxx"
116 || fn
== SRCDIR
"/sd/source/ui/slidesorter/cache/SlsRequestFactory.cxx"
117 || fn
== SRCDIR
"/sd/source/ui/framework/configuration/ResourceId.cxx"
118 || fn
== SRCDIR
"/sd/source/filter/html/htmlex.cxx"
119 || fn
== SRCDIR
"/starmath/source/cfgitem.cxx"
120 || fn
== SRCDIR
"/ucb/source/ucp/ftp/ftpurl.cxx"
121 || fn
== SRCDIR
"/starmath/source/node.cxx"
122 || fn
== SRCDIR
"/ucb/source/ucp/cmis/certvalidation_handler.cxx"
123 || fn
== SRCDIR
"/reportdesign/source/ui/inspection/GeometryHandler.cxx"
124 || fn
== SRCDIR
"/reportdesign/source/core/api/ReportDefinition.cxx"
125 || fn
== SRCDIR
"/test/source/table/tablerow.cxx"
126 || fn
== SRCDIR
"/basegfx/test/B2DHomMatrixTest.cxx"
127 || fn
== SRCDIR
"/comphelper/qa/unit/base64_test.cxx"
128 || fn
== SRCDIR
"/testtools/source/bridgetest/bridgetest.cxx"
129 || fn
== SRCDIR
"/comphelper/qa/string/test_string.cxx"
130 || fn
== SRCDIR
"/cppu/qa/test_unotype.cxx"
131 || fn
== SRCDIR
"/cppu/qa/cppumaker/test_cppumaker.cxx"
132 || fn
== SRCDIR
"/o3tl/qa/test-lru_map.cxx" || fn
== SRCDIR
"/svl/qa/unit/svl.cxx"
133 || fn
== SRCDIR
"/chart2/qa/extras/PivotChartTest.cxx"
134 || fn
== SRCDIR
"/chart2/qa/extras/chart2export.cxx"
135 || fn
== SRCDIR
"/writerfilter/qa/cppunittests/misc/misc.cxx"
136 || fn
== SRCDIR
"/sw/qa/extras/ww8export/ww8export.cxx"
137 || fn
== SRCDIR
"/sw/qa/extras/uiwriter/uiwriter.cxx")
140 if (fn
== SRCDIR
"/vcl/source/filter/png/pngread.cxx"
141 || fn
== SRCDIR
"/vcl/source/window/syschild.cxx"
142 || fn
== SRCDIR
"/sal/osl/unx/security.cxx"
143 || fn
== SRCDIR
"/vcl/source/filter/png/pngwrite.cxx"
144 || fn
== SRCDIR
"/svtools/source/control/inettbc.cxx"
145 || fn
== SRCDIR
"/canvas/source/cairo/cairo_textlayout.cxx"
146 || fn
== SRCDIR
"/sal/qa/osl/file/osl_File.cxx")
148 // taking address of variable
149 if (fn
== SRCDIR
"/vcl/unx/generic/dtrans/X11_selection.cxx")
152 if (fn
== SRCDIR
"/sc/source/core/tool/scmatrix.cxx"
153 || fn
== SRCDIR
"/sal/qa/rtl/oustringbuffer/test_oustringbuffer_assign.cxx")
156 TraverseDecl(compiler
.getASTContext().getTranslationUnitDecl());
159 bool VisitCompoundStmt(CompoundStmt
const*);
162 const VarDecl
* findSimpleAssign(Stmt
const*);
163 bool isSimpleRHS(Expr
const*);
164 Expr
const* ignore(Expr
const*);
165 void checkForSecondAssign(Stmt
const* stmt
, VarDecl
const* varDecl
);
168 bool SequentialAssign::VisitCompoundStmt(CompoundStmt
const* compoundStmt
)
170 if (ignoreLocation(compoundStmt
))
173 auto it
= compoundStmt
->body_begin();
176 if (it
== compoundStmt
->body_end())
178 auto firstStmt
= *it
;
179 const VarDecl
* foundVars
= findSimpleAssign(firstStmt
);
180 // reference types have slightly weird behaviour
181 if (!foundVars
|| foundVars
->getType()->isReferenceType())
187 if (it
== compoundStmt
->body_end())
189 checkForSecondAssign(*it
, foundVars
);
195 void SequentialAssign::checkForSecondAssign(Stmt
const* stmt
, VarDecl
const* varDecl
)
197 if (auto exprCleanup
= dyn_cast
<ExprWithCleanups
>(stmt
))
198 stmt
= exprCleanup
->getSubExpr();
200 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(stmt
))
202 if (operatorCall
->getOperator() == OO_Equal
)
204 if (auto declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(operatorCall
->getArg(0))))
205 if (declRefExprLHS
->getDecl() == varDecl
)
207 report(DiagnosticsEngine::Warning
,
208 "simplify by merging with the preceding assignment",
209 compat::getBeginLoc(stmt
))
210 << stmt
->getSourceRange();
216 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(stmt
))
218 if (binaryOp
->getOpcode() == BO_Assign
)
220 if (auto declRefExpr
= dyn_cast
<DeclRefExpr
>(ignore(binaryOp
->getLHS())))
221 if (declRefExpr
->getDecl() == varDecl
)
223 report(DiagnosticsEngine::Warning
,
224 "simplify by merging with the preceding assignment",
225 compat::getBeginLoc(stmt
))
226 << stmt
->getSourceRange();
232 const VarDecl
* SequentialAssign::findSimpleAssign(Stmt
const* stmt
)
234 if (auto declStmt
= dyn_cast
<DeclStmt
>(stmt
))
235 if (declStmt
->isSingleDecl())
236 if (auto varDeclLHS
= dyn_cast_or_null
<VarDecl
>(declStmt
->getSingleDecl()))
238 if (varDeclLHS
->getStorageDuration() == SD_Static
)
240 // if it's call-style init (e.g. OUString s("xxx")), we only treat
241 // it as simple if it only contains a variable in the call
242 // (e.g. OUString s(x))
243 if (varDeclLHS
->getInitStyle() == VarDecl::InitializationStyle::CallInit
)
245 auto cxxConstructExpr
246 = dyn_cast
<CXXConstructExpr
>(ignore(varDeclLHS
->getInit()));
247 if (cxxConstructExpr
)
249 if (cxxConstructExpr
->getNumArgs() == 0)
251 if (cxxConstructExpr
->getNumArgs() > 1)
253 if (!isa
<DeclRefExpr
>(ignore(cxxConstructExpr
->getArg(0))))
257 if (auto init
= varDeclLHS
->getInit())
258 if (isSimpleRHS(init
))
261 if (auto operatorCall
= dyn_cast
<CXXOperatorCallExpr
>(stmt
))
262 if (operatorCall
->getOperator() == OO_Equal
)
263 if (auto declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(operatorCall
->getArg(0))))
264 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
265 if (isSimpleRHS(operatorCall
->getArg(1)))
267 if (auto binaryOp
= dyn_cast
<BinaryOperator
>(stmt
))
268 if (binaryOp
->getOpcode() == BO_Assign
)
269 if (auto declRefExprLHS
= dyn_cast
<DeclRefExpr
>(ignore(binaryOp
->getLHS())))
270 if (auto varDeclLHS
= dyn_cast
<VarDecl
>(declRefExprLHS
->getDecl()))
271 if (isSimpleRHS(binaryOp
->getRHS()))
277 Does the first statement have a relatively simply RHS we can inline into the second statement?
279 bool SequentialAssign::isSimpleRHS(Expr
const* expr
)
284 // Point aCurPos = rGlyphs
285 // always has a CXXConstructExpr wrapping the RHS
286 if (auto cxxConstructExpr
= dyn_cast
<CXXConstructExpr
>(expr
))
287 if (cxxConstructExpr
->getNumArgs() == 1)
288 expr
= ignore(cxxConstructExpr
->getArg(0));
290 if (!expr
->isValueDependent()
291 && expr
->isConstantInitializer(compiler
.getASTContext(), false /*ForRef*/))
293 if (isa
<CXXMemberCallExpr
>(expr
))
295 if (isa
<CXXOperatorCallExpr
>(expr
))
297 if (isa
<UserDefinedLiteral
>(expr
))
299 if (isa
<CallExpr
>(expr
))
301 if (isa
<CastExpr
>(expr
))
303 if (isa
<ArraySubscriptExpr
>(expr
))
305 if (isa
<BinaryOperator
>(expr
))
307 if (isa
<ConditionalOperator
>(expr
))
309 if (isa
<UnaryOperator
>(expr
))
311 if (isa
<CXXNewExpr
>(expr
))
313 if (auto memberExpr
= dyn_cast
<MemberExpr
>(expr
))
314 return isSimpleRHS(memberExpr
->getBase());
319 Expr
const* SequentialAssign::ignore(Expr
const* expr
)
321 return expr
->IgnoreImplicit()->IgnoreParens()->IgnoreImplicit();
324 // Off by default because of safety concerns, see TODO at top
325 loplugin::Plugin::Registration
<SequentialAssign
> X("sequentialassign", false);
328 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */