bump product version to 6.3.0.0.beta1
[LibreOffice.git] / compilerplugins / clang / oncevar.cxx
blob0f809b9ebfacb586e54dd902ac893cd77968caf9
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
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/.
8 */
10 #include <cassert>
11 #include <string>
12 #include <iostream>
13 #include <unordered_map>
14 #include <unordered_set>
16 #include "plugin.hxx"
17 #include "check.hxx"
18 #include "clang/AST/CXXInheritance.h"
19 #include "clang/AST/StmtVisitor.h"
21 // Original idea from tml.
22 // Look for variables that are (a) initialised from zero or one constants. (b) only used in one spot.
23 // In which case, we might as well inline it.
25 namespace
28 Expr const * lookThroughInitListExpr(Expr const * expr) {
29 if (auto const ile = dyn_cast<InitListExpr>(expr->IgnoreParenImpCasts())) {
30 if (ile->getNumInits() == 1) {
31 return ile->getInit(0);
34 return expr;
37 class ConstantValueDependentExpressionVisitor:
38 public ConstStmtVisitor<ConstantValueDependentExpressionVisitor, bool>
40 ASTContext const & context_;
42 public:
43 ConstantValueDependentExpressionVisitor(ASTContext const & context):
44 context_(context) {}
46 bool Visit(Stmt const * stmt) {
47 assert(isa<Expr>(stmt));
48 auto const expr = cast<Expr>(stmt);
49 if (!expr->isValueDependent()) {
50 return expr->isEvaluatable(context_);
52 return ConstStmtVisitor::Visit(stmt);
55 bool VisitParenExpr(ParenExpr const * expr)
56 { return Visit(expr->getSubExpr()); }
58 bool VisitCastExpr(CastExpr const * expr) {
59 return Visit(expr->getSubExpr());
62 bool VisitUnaryOperator(UnaryOperator const * expr)
63 { return Visit(expr->getSubExpr()); }
65 bool VisitBinaryOperator(BinaryOperator const * expr) {
66 return Visit(expr->getLHS()) && Visit(expr->getRHS());
69 bool VisitUnaryExprOrTypeTraitExpr(UnaryExprOrTypeTraitExpr const *) {
70 return true;
74 class OnceVar:
75 public loplugin::FilteringPlugin<OnceVar>
77 public:
78 explicit OnceVar(loplugin::InstantiationData const & data): FilteringPlugin(data) {}
80 virtual void run() override {
81 // ignore some files with problematic macros
82 std::string fn(handler.getMainFileName());
83 loplugin::normalizeDotDotInFilePath(fn);
84 // platform-specific stuff
85 if (fn == SRCDIR "/sal/osl/unx/thread.cxx"
86 || fn == SRCDIR "/sot/source/base/formats.cxx"
87 || fn == SRCDIR "/svl/source/config/languageoptions.cxx"
88 || fn == SRCDIR "/sfx2/source/appl/appdde.cxx"
89 || fn == SRCDIR "/configmgr/source/components.cxx"
90 || fn == SRCDIR "/embeddedobj/source/msole/oleembed.cxx")
91 return;
92 // some of this is necessary
93 if (loplugin::hasPathnamePrefix( fn, SRCDIR "/sal/qa/"))
94 return;
95 if (loplugin::hasPathnamePrefix( fn, SRCDIR "/comphelper/qa/"))
96 return;
97 // TODO need to check calls via function pointer
98 if (fn == SRCDIR "/i18npool/source/textconversion/textconversion_zh.cxx"
99 || fn == SRCDIR "/i18npool/source/localedata/localedata.cxx")
100 return;
101 // debugging stuff
102 if (fn == SRCDIR "/sc/source/core/data/dpcache.cxx"
103 || fn == SRCDIR "/sw/source/core/layout/dbg_lay.cxx"
104 || fn == SRCDIR "/sw/source/core/layout/ftnfrm.cxx")
105 return;
106 // TODO taking local reference to variable
107 if (fn == SRCDIR "/sc/source/filter/excel/xechart.cxx")
108 return;
109 // macros managing to generate to a valid warning
110 if (fn == SRCDIR "/solenv/bin/concat-deps.c")
111 return;
112 // TODO bug in the plugin
113 if (fn == SRCDIR "/vcl/unx/generic/app/saldisp.cxx")
114 return;
116 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
118 for (auto const & varDecl : maVarDeclSet)
120 if (maVarDeclToIgnoreSet.find(varDecl) != maVarDeclToIgnoreSet.end())
121 continue;
122 int noUses = 0;
123 auto it = maVarUsesMap.find(varDecl);
124 if (it != maVarUsesMap.end())
125 noUses = it->second;
126 if (noUses > 1)
127 continue;
128 report(DiagnosticsEngine::Warning,
129 "var used only once, should be inlined or declared const",
130 varDecl->getLocation())
131 << varDecl->getSourceRange();
132 if (it != maVarUsesMap.end())
133 report(DiagnosticsEngine::Note,
134 "used here",
135 maVarUseSourceRangeMap[varDecl].getBegin())
136 << maVarUseSourceRangeMap[varDecl];
140 bool VisitMemberExpr(MemberExpr const * expr) {
141 // ignore cases like:
142 // const OUString("xxx") xxx;
143 // rtl_something(xxx.pData);
144 // where we cannot inline the declaration.
145 if (isa<FieldDecl>(expr->getMemberDecl())) {
146 recordIgnore(expr);
148 return true;
151 bool VisitUnaryOperator(UnaryOperator const * expr) {
152 // if we take the address of it, or we modify it, ignore it
153 UnaryOperator::Opcode op = expr->getOpcode();
154 if (op == UO_AddrOf || op == UO_PreInc || op == UO_PostInc
155 || op == UO_PreDec || op == UO_PostDec)
157 recordIgnore(expr->getSubExpr());
159 return true;
162 bool VisitBinaryOperator(BinaryOperator const * expr) {
163 // if we assign it another value, or modify it, ignore it
164 BinaryOperator::Opcode op = expr->getOpcode();
165 if (op == BO_Assign || op == BO_PtrMemD || op == BO_PtrMemI || op == BO_MulAssign
166 || op == BO_DivAssign || op == BO_RemAssign || op == BO_AddAssign
167 || op == BO_SubAssign || op == BO_ShlAssign || op == BO_ShrAssign
168 || op == BO_AndAssign || op == BO_XorAssign || op == BO_OrAssign)
170 recordIgnore(expr->getLHS());
172 return true;
175 bool VisitCallExpr(CallExpr const * expr) {
176 unsigned firstArg = 0;
177 if (auto const cmce = dyn_cast<CXXMemberCallExpr>(expr)) {
178 if (auto const e1 = cmce->getMethodDecl()) {
179 if (!(e1->isConst() || e1->isStatic())) {
180 recordIgnore(cmce->getImplicitObjectArgument());
182 } else if (auto const e2 = dyn_cast<BinaryOperator>(
183 cmce->getCallee()->IgnoreParenImpCasts()))
185 switch (e2->getOpcode()) {
186 case BO_PtrMemD:
187 case BO_PtrMemI:
188 if (!e2->getRHS()->getType()->getAs<MemberPointerType>()
189 ->getPointeeType()->getAs<FunctionProtoType>()
190 ->isConst())
192 recordIgnore(e2->getLHS());
194 break;
195 default:
196 break;
199 } else if (auto const coce = dyn_cast<CXXOperatorCallExpr>(expr)) {
200 if (auto const cmd = dyn_cast_or_null<CXXMethodDecl>(
201 coce->getDirectCallee()))
203 if (!cmd->isStatic()) {
204 assert(coce->getNumArgs() != 0);
205 if (!cmd->isConst()) {
206 recordIgnore(coce->getArg(0));
208 firstArg = 1;
212 // ignore those ones we are passing by reference
213 const FunctionDecl* calleeFunctionDecl = expr->getDirectCallee();
214 if (calleeFunctionDecl) {
215 for (unsigned i = firstArg; i < expr->getNumArgs(); ++i) {
216 if (i < calleeFunctionDecl->getNumParams()) {
217 QualType qt { calleeFunctionDecl->getParamDecl(i)->getType() };
218 if (loplugin::TypeCheck(qt).LvalueReference().NonConst()) {
219 recordIgnore(expr->getArg(i));
221 if (loplugin::TypeCheck(qt).Pointer().NonConst()) {
222 recordIgnore(expr->getArg(i));
227 return true;
230 bool VisitCXXConstructExpr(CXXConstructExpr const * expr) {
231 // ignore those ones we are passing by reference
232 const CXXConstructorDecl* cxxConstructorDecl = expr->getConstructor();
233 for (unsigned i = 0; i < expr->getNumArgs(); ++i) {
234 if (i < cxxConstructorDecl->getNumParams()) {
235 QualType qt { cxxConstructorDecl->getParamDecl(i)->getType() };
236 if (loplugin::TypeCheck(qt).LvalueReference().NonConst()) {
237 recordIgnore(expr->getArg(i));
239 if (loplugin::TypeCheck(qt).Pointer().NonConst()) {
240 recordIgnore(expr->getArg(i));
244 return true;
247 bool VisitDeclRefExpr( const DeclRefExpr* );
248 bool VisitVarDecl( const VarDecl* );
249 bool TraverseFunctionDecl( FunctionDecl* functionDecl );
251 private:
252 std::unordered_set<VarDecl const *> maVarDeclSet;
253 std::unordered_set<VarDecl const *> maVarDeclToIgnoreSet;
254 std::unordered_map<VarDecl const *, int> maVarUsesMap;
255 std::unordered_map<VarDecl const *, SourceRange> maVarUseSourceRangeMap;
257 bool isConstantValueDependentExpression(Expr const * expr) {
258 return ConstantValueDependentExpressionVisitor(compiler.getASTContext())
259 .Visit(expr);
262 void recordIgnore(Expr const * expr) {
263 for (;;) {
264 expr = expr->IgnoreParenImpCasts();
265 if (auto const e = dyn_cast<MemberExpr>(expr)) {
266 if (isa<FieldDecl>(e->getMemberDecl())) {
267 expr = e->getBase();
268 continue;
271 if (auto const e = dyn_cast<ArraySubscriptExpr>(expr)) {
272 expr = e->getBase();
273 continue;
275 if (auto const e = dyn_cast<BinaryOperator>(expr)) {
276 if (e->getOpcode() == BO_PtrMemD) {
277 expr = e->getLHS();
278 continue;
281 break;
283 auto const dre = dyn_cast<DeclRefExpr>(expr);
284 if (dre == nullptr) {
285 return;
287 auto const var = dyn_cast<VarDecl>(dre->getDecl());
288 if (var == nullptr) {
289 return;
291 maVarDeclToIgnoreSet.insert(var);
295 bool OnceVar::TraverseFunctionDecl( FunctionDecl* functionDecl )
297 // Ignore functions that contains #ifdef-ery, can be quite tricky
298 // to make useful changes when this plugin fires in such functions
299 if (containsPreprocessingConditionalInclusion(
300 functionDecl->getSourceRange()))
301 return true;
302 return RecursiveASTVisitor::TraverseFunctionDecl(functionDecl);
305 bool OnceVar::VisitVarDecl( const VarDecl* varDecl )
307 if (ignoreLocation(varDecl)) {
308 return true;
310 if (auto const init = varDecl->getInit()) {
311 recordIgnore(lookThroughInitListExpr(init));
313 if (varDecl->isExceptionVariable() || isa<ParmVarDecl>(varDecl)) {
314 return true;
316 // ignore stuff in header files (which should really not be there, but anyhow)
317 if (!compiler.getSourceManager().isInMainFile(varDecl->getLocation())) {
318 return true;
320 // Ignore macros like FD_ZERO
321 if (compiler.getSourceManager().isMacroBodyExpansion(compat::getBeginLoc(varDecl))) {
322 return true;
324 if (varDecl->hasGlobalStorage()) {
325 return true;
327 auto const tc = loplugin::TypeCheck(varDecl->getType());
328 if (!varDecl->getType().isCXX11PODType(compiler.getASTContext())
329 && !tc.Class("OString").Namespace("rtl").GlobalNamespace()
330 && !tc.Class("OUString").Namespace("rtl").GlobalNamespace()
331 && !tc.Class("OStringBuffer").Namespace("rtl").GlobalNamespace()
332 && !tc.Class("OUStringBuffer").Namespace("rtl").GlobalNamespace()
333 && !tc.Class("Color").GlobalNamespace()
334 && !tc.Class("Pair").GlobalNamespace()
335 && !tc.Class("Point").GlobalNamespace()
336 && !tc.Class("Size").GlobalNamespace()
337 && !tc.Class("Range").GlobalNamespace()
338 && !tc.Class("Selection").GlobalNamespace()
339 && !tc.Class("Rectangle").Namespace("tools").GlobalNamespace())
341 return true;
343 if (varDecl->getType()->isPointerType())
344 return true;
345 // if it's declared const, ignore it, it's there to make the code easier to read
346 if (tc.Const())
347 return true;
349 if (!varDecl->hasInit())
350 return true;
352 // check for string or scalar literals
353 bool foundStringLiteral = false;
354 const Expr * initExpr = varDecl->getInit();
355 if (auto e = dyn_cast<ExprWithCleanups>(initExpr)) {
356 initExpr = e->getSubExpr();
358 if (isa<clang::StringLiteral>(initExpr)) {
359 foundStringLiteral = true;
360 } else if (auto constructExpr = dyn_cast<CXXConstructExpr>(initExpr)) {
361 if (constructExpr->getNumArgs() == 0) {
362 foundStringLiteral = true; // i.e., empty string
363 } else {
364 auto stringLit2 = dyn_cast<clang::StringLiteral>(constructExpr->getArg(0));
365 foundStringLiteral = stringLit2 != nullptr;
368 if (!foundStringLiteral) {
369 auto const init = varDecl->getInit();
370 if (!(init->isValueDependent()
371 ? isConstantValueDependentExpression(init)
372 : init->isConstantInitializer(
373 compiler.getASTContext(), false/*ForRef*/)))
375 return true;
379 maVarDeclSet.insert(varDecl);
381 return true;
384 bool OnceVar::VisitDeclRefExpr( const DeclRefExpr* declRefExpr )
386 if (ignoreLocation(declRefExpr)) {
387 return true;
389 const Decl* decl = declRefExpr->getDecl();
390 if (!isa<VarDecl>(decl) || isa<ParmVarDecl>(decl)) {
391 return true;
393 const VarDecl * varDecl = dyn_cast<VarDecl>(decl)->getCanonicalDecl();
394 // ignore stuff in header files (which should really not be there, but anyhow)
395 if (!compiler.getSourceManager().isInMainFile(varDecl->getLocation())) {
396 return true;
399 if (maVarUsesMap.find(varDecl) == maVarUsesMap.end()) {
400 maVarUsesMap[varDecl] = 1;
401 maVarUseSourceRangeMap[varDecl] = declRefExpr->getSourceRange();
402 } else {
403 maVarUsesMap[varDecl]++;
406 return true;
409 loplugin::Plugin::Registration< OnceVar > X("oncevar", false);
413 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */