1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
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  */
9 
10 #include "plugin.hxx"
11 #include "check.hxx"
12 #include "compat.hxx"
13 #include <iostream>
14 #include <unordered_map>
15 #include <unordered_set>
16 #include <vector>
17 
18 /**
19  Look for fields which are const, which can be made static const
20 */
21 namespace
22 {
23 class StaticConstField : public loplugin::FilteringPlugin<StaticConstField>
24 {
25 public:
StaticConstField(loplugin::InstantiationData const & data)26     explicit StaticConstField(loplugin::InstantiationData const& data)
27         : loplugin::FilteringPlugin<StaticConstField>(data)
28     {
29     }
30 
31     void run() override;
32 
33     bool TraverseConstructorInitializer(CXXCtorInitializer* init);
34     bool TraverseCXXConstructorDecl(CXXConstructorDecl* decl);
35 
36 private:
37     struct Data
38     {
39         std::vector<CXXCtorInitializer const*> inits;
40         std::string value;
41     };
42     std::unordered_map<FieldDecl const*, Data> m_potentials;
43     std::unordered_set<FieldDecl const*> m_excluded;
44     CXXConstructorDecl* m_currentConstructor = nullptr;
45 };
46 
run()47 void StaticConstField::run()
48 {
49     std::string fn = handler.getMainFileName();
50     loplugin::normalizeDotDotInFilePath(fn);
51 
52     // unusual case where a user constructor sets a field to one value, and a copy constructor sets it to a different value
53     if (fn == SRCDIR "/sw/source/core/attr/hints.cxx")
54         return;
55     if (fn == SRCDIR "/oox/source/core/contexthandler2.cxx")
56         return;
57 
58     TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
59 
60     for (auto const& pair : m_potentials)
61     {
62         report(DiagnosticsEngine::Error, "const field can be static", pair.first->getLocation())
63             << pair.first->getSourceRange();
64         for (CXXCtorInitializer const* init : pair.second.inits)
65             if (pair.first->getLocation() != init->getSourceLocation())
66                 report(DiagnosticsEngine::Note, "init here", init->getSourceLocation())
67                     << init->getSourceRange();
68     }
69 }
70 
TraverseCXXConstructorDecl(CXXConstructorDecl * decl)71 bool StaticConstField::TraverseCXXConstructorDecl(CXXConstructorDecl* decl)
72 {
73     auto prev = m_currentConstructor;
74     m_currentConstructor = decl;
75     bool ret = FilteringPlugin::TraverseCXXConstructorDecl(decl);
76     m_currentConstructor = prev;
77     return ret;
78 }
79 
TraverseConstructorInitializer(CXXCtorInitializer * init)80 bool StaticConstField::TraverseConstructorInitializer(CXXCtorInitializer* init)
81 {
82     if (!init->getSourceLocation().isValid() || ignoreLocation(init->getSourceLocation()))
83         return true;
84     if (!init->getMember())
85         return true;
86     if (!init->getInit())
87         return true;
88     if (!m_currentConstructor || m_currentConstructor->isCopyOrMoveConstructor())
89         return true;
90     if (!m_currentConstructor->getParent()->isCompleteDefinition())
91         return true;
92     if (m_excluded.find(init->getMember()) != m_excluded.end())
93         return true;
94     auto type = init->getMember()->getType();
95     auto tc = loplugin::TypeCheck(type);
96     if (!tc.Const())
97         return true;
98 
99     bool found = false;
100     std::string value;
101     auto const initexpr = compat::IgnoreImplicit(init->getInit());
102     if (tc.Const().Class("OUString").Namespace("rtl").GlobalNamespace()
103         || tc.Const().Class("OString").Namespace("rtl").GlobalNamespace())
104     {
105         if (auto constructExpr = dyn_cast<CXXConstructExpr>(initexpr))
106         {
107             if (constructExpr->getNumArgs() >= 1
108                 && isa<clang::StringLiteral>(constructExpr->getArg(0)))
109             {
110                 value = dyn_cast<clang::StringLiteral>(constructExpr->getArg(0))->getString();
111                 found = true;
112             }
113         }
114     }
115     else if (type->isFloatingType())
116     {
117         APFloat x1(0.0f);
118         if (initexpr->EvaluateAsFloat(x1, compiler.getASTContext()))
119         {
120             std::string s;
121             llvm::raw_string_ostream os(s);
122             x1.print(os);
123             value = os.str();
124             found = true;
125         }
126     }
127     // ignore this, it seems to trigger an infinite recursion
128     else if (isa<UnaryExprOrTypeTraitExpr>(initexpr))
129         ;
130     // ignore this, calling EvaluateAsInt on it will crash clang
131     else if (initexpr->isValueDependent())
132         ;
133     else
134     {
135         APSInt x1;
136         if (compat::EvaluateAsInt(initexpr, x1, compiler.getASTContext()))
137         {
138             value = x1.toString(10);
139             found = true;
140         }
141     }
142 
143     if (!found)
144     {
145         m_potentials.erase(init->getMember());
146         m_excluded.insert(init->getMember());
147         return true;
148     }
149 
150     auto findIt = m_potentials.find(init->getMember());
151     if (findIt != m_potentials.end())
152     {
153         if (findIt->second.value != value)
154         {
155             m_potentials.erase(findIt);
156             m_excluded.insert(init->getMember());
157         }
158         else
159             findIt->second.inits.push_back(init);
160     }
161     else
162     {
163         Data& data = m_potentials[init->getMember()];
164         data.inits.push_back(init);
165         data.value = value;
166     }
167 
168     return true;
169 }
170 
171 loplugin::Plugin::Registration<StaticConstField> X("staticconstfield", true);
172 }
173 
174 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
175