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