bump product version to 6.4.0.3
[LibreOffice.git] / compilerplugins / clang / virtualdead.cxx
blob330085b18781a0c742ce69e9a1d4a76217349f1c
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 "plugin.hxx"
11 #include "check.hxx"
13 #include <cassert>
14 #include <string>
15 #include <iostream>
16 #include <set>
17 #include <fstream>
19 /**
20 Look for virtual methods where all of the overrides either
21 (a) do nothing
22 (b) all return the same value
24 The process goes something like this:
25 $ make check
26 $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='VirtualDead' check
27 $ ./compilerplugins/clang/VirtualDead.py
28 $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='removevirtuals' $dir; done
30 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
31 to get it to work :-)
34 namespace
36 struct MyFuncInfo
38 std::string name;
39 std::string sourceLocation;
40 std::string returnValue;
42 bool operator<(const MyFuncInfo& lhs, const MyFuncInfo& rhs)
44 return std::tie(lhs.name, lhs.returnValue) < std::tie(rhs.name, rhs.returnValue);
46 struct MyParamInfo
48 std::string funcName;
49 std::string paramBitField;
51 bool operator<(const MyParamInfo& lhs, const MyParamInfo& rhs)
53 return std::tie(lhs.funcName, lhs.paramBitField) < std::tie(rhs.funcName, rhs.paramBitField);
56 // try to limit the voluminous output a little
57 static std::set<MyFuncInfo> definitionSet;
58 static std::set<MyParamInfo> paramUsedSet;
60 class VirtualDead : public RecursiveASTVisitor<VirtualDead>, public loplugin::Plugin
62 public:
63 explicit VirtualDead(loplugin::InstantiationData const& data)
64 : Plugin(data)
68 virtual void run() override
70 TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
72 // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
73 // writing to the same logfile
74 std::string output;
75 for (const MyFuncInfo& s : definitionSet)
76 output += "virtual:\t" + s.name + "\t" + s.sourceLocation + "\t" + s.returnValue + "\n";
77 for (const MyParamInfo& s : paramUsedSet)
78 output += "param:\t" + s.funcName + "\t" + s.paramBitField + "\n";
79 std::ofstream myfile;
80 myfile.open(WORKDIR "/loplugin.virtualdead.log", std::ios::app | std::ios::out);
81 myfile << output;
82 myfile.close();
84 bool shouldVisitTemplateInstantiations() const { return true; }
85 bool shouldVisitImplicitCode() const { return true; }
87 bool VisitCXXMethodDecl(const CXXMethodDecl* decl);
89 private:
90 std::string getCallValue(const Expr* arg);
91 std::string toString(SourceLocation loc);
92 void markSuperclassMethods(const CXXMethodDecl* methodDecl, const std::string& returnValue,
93 const std::string& paramBitField);
96 std::string niceName(const CXXMethodDecl* cxxMethodDecl)
98 while (cxxMethodDecl->getTemplateInstantiationPattern())
99 cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getTemplateInstantiationPattern());
100 while (cxxMethodDecl->getInstantiatedFromMemberFunction())
101 cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getInstantiatedFromMemberFunction());
102 std::string s = cxxMethodDecl->getReturnType().getCanonicalType().getAsString() + " "
103 + cxxMethodDecl->getQualifiedNameAsString() + "(";
104 for (const ParmVarDecl* pParmVarDecl : cxxMethodDecl->parameters())
106 s += pParmVarDecl->getType().getCanonicalType().getAsString();
107 s += ",";
109 s += ")";
110 if (cxxMethodDecl->isConst())
112 s += "const";
114 return s;
117 bool VirtualDead::VisitCXXMethodDecl(const CXXMethodDecl* methodDecl)
119 if (ignoreLocation(methodDecl))
120 return true;
121 if (!methodDecl->isVirtual() || methodDecl->isDeleted())
122 return true;
123 if (isa<CXXDestructorDecl>(methodDecl))
124 return true;
125 // ignore stuff that forms part of the stable URE interface
126 if (isInUnoIncludeFile(methodDecl->getCanonicalDecl()))
127 return true;
129 if (!methodDecl->isThisDeclarationADefinition())
130 return true;
132 std::string returnValue;
134 auto body = methodDecl->getBody();
135 if (body)
137 auto compoundStmt = dyn_cast<CompoundStmt>(body);
138 if (!compoundStmt)
139 returnValue = "empty";
140 else if (compoundStmt->size() == 0)
141 returnValue = "empty";
142 else
144 if (auto returnStmt = dyn_cast<ReturnStmt>(*compoundStmt->body_begin()))
146 if (!returnStmt->getRetValue())
147 returnValue = "empty";
148 else
149 returnValue = getCallValue(returnStmt->getRetValue());
151 else
152 returnValue = "unknown-stmt";
155 else
156 returnValue = "empty";
158 std::string paramBitfield;
159 for (auto it = methodDecl->param_begin(); it != methodDecl->param_end(); ++it)
161 auto param = *it;
162 paramBitfield += param->getName().empty() ? "0" : "1";
165 markSuperclassMethods(methodDecl, returnValue, paramBitfield);
167 return true;
170 void VirtualDead::markSuperclassMethods(const CXXMethodDecl* methodDecl,
171 const std::string& returnValue,
172 std::string const& paramBitField)
174 if (methodDecl->size_overridden_methods() == 0)
176 std::string aNiceName = niceName(methodDecl);
177 definitionSet.insert(
178 { aNiceName, toString(methodDecl->getCanonicalDecl()->getLocation()), returnValue });
179 paramUsedSet.insert({ aNiceName, paramBitField });
180 return;
183 for (auto iter = methodDecl->begin_overridden_methods();
184 iter != methodDecl->end_overridden_methods(); ++iter)
186 const CXXMethodDecl* overriddenMethod = *iter;
187 markSuperclassMethods(overriddenMethod, returnValue, paramBitField);
191 std::string VirtualDead::getCallValue(const Expr* arg)
193 arg = arg->IgnoreParenCasts();
194 if (isa<CXXDefaultArgExpr>(arg))
196 arg = dyn_cast<CXXDefaultArgExpr>(arg)->getExpr();
198 arg = arg->IgnoreParenCasts();
199 // ignore this, it seems to trigger an infinite recursion
200 if (isa<UnaryExprOrTypeTraitExpr>(arg))
201 return "unknown1";
202 if (arg->isValueDependent())
203 return "unknown2";
204 APSInt x1;
205 if (compat::EvaluateAsInt(arg, x1, compiler.getASTContext()))
207 return x1.toString(10);
209 if (isa<CXXNullPtrLiteralExpr>(arg))
211 return "0";
213 if (isa<MaterializeTemporaryExpr>(arg))
215 const CXXBindTemporaryExpr* strippedArg
216 = dyn_cast_or_null<CXXBindTemporaryExpr>(arg->IgnoreParenCasts());
217 if (strippedArg)
219 auto temp = dyn_cast<CXXTemporaryObjectExpr>(strippedArg->getSubExpr());
220 if (temp->getNumArgs() == 0)
222 if (loplugin::TypeCheck(temp->getType())
223 .Class("OUString")
224 .Namespace("rtl")
225 .GlobalNamespace())
227 return "\"\"";
229 if (loplugin::TypeCheck(temp->getType())
230 .Class("OString")
231 .Namespace("rtl")
232 .GlobalNamespace())
234 return "\"\"";
236 return "defaultConstruct";
241 // Get the expression contents.
242 // This helps us find params which are always initialised with something like "OUString()".
243 SourceManager& SM = compiler.getSourceManager();
244 SourceLocation startLoc = compat::getBeginLoc(arg);
245 SourceLocation endLoc = compat::getEndLoc(arg);
246 const char* p1 = SM.getCharacterData(startLoc);
247 const char* p2 = SM.getCharacterData(endLoc);
248 if (!p1 || !p2 || (p2 - p1) < 0 || (p2 - p1) > 40)
250 return "unknown3";
252 unsigned n = Lexer::MeasureTokenLength(endLoc, SM, compiler.getLangOpts());
253 std::string s(p1, p2 - p1 + n);
254 // strip linefeed and tab characters so they don't interfere with the parsing of the log file
255 std::replace(s.begin(), s.end(), '\r', ' ');
256 std::replace(s.begin(), s.end(), '\n', ' ');
257 std::replace(s.begin(), s.end(), '\t', ' ');
259 // now normalize the value. For some params, like OUString, we can pass it as OUString() or "" and they are the same thing
260 if (s == "OUString()")
261 s = "\"\"";
262 else if (s == "OString()")
263 s = "\"\"";
264 else if (s == "aEmptyOUStr") //sw
265 s = "\"\"";
266 else if (s == "EMPTY_OUSTRING") //sc
267 s = "\"\"";
268 else if (s == "GetEmptyOUString()") //sc
269 s = "\"\"";
271 if (s[0] == '"' || s[0] == '\'')
272 return s;
273 return "unknown4";
276 std::string VirtualDead::toString(SourceLocation loc)
278 SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc(loc);
279 StringRef name = getFilenameOfLocation(expansionLoc);
280 std::string sourceLocation
281 = std::string(name.substr(strlen(SRCDIR) + 1)) + ":"
282 + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
283 loplugin::normalizeDotDotInFilePath(sourceLocation);
284 return sourceLocation;
287 loplugin::Plugin::Registration<VirtualDead> X("virtualdead", false);
290 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */