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 <cassert>
11 #include <string>
12 #include <iostream>
13 #include <set>
14 #include <unordered_set>
15 #include "plugin.hxx"
16 #include <fstream>
17 
18 /**
19 Dump a list of virtual methods and a list of methods overriding virtual methods.
20 Then we will post-process the 2 lists and find the set of virtual methods which don't need to be virtual.
21 
22 Also, we look for virtual methods where the bodies of all the overrides are empty i.e. this is leftover code
23 that no longer has a purpose.
24 
25 The process goes something like this:
26   $ make check
27   $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='unnecessaryvirtual' check
28   $ ./compilerplugins/clang/unnecessaryvirtual.py
29   $ for dir in *; do make FORCE_COMPILE_ALL=1 UPDATE_FILES=$dir COMPILER_PLUGIN_TOOL='removevirtuals' $dir; done
30 
31 Note that the actual process may involve a fair amount of undoing, hand editing, and general messing around
32 to get it to work :-)
33 
34 TODO some boost bind stuff appears to confuse it, notably in the xmloff module
35 */
36 
37 namespace {
38 
39 struct MyFuncInfo
40 {
41     std::string name;
42     std::string sourceLocation;
43 
44 };
operator <(const MyFuncInfo & lhs,const MyFuncInfo & rhs)45 bool operator < (const MyFuncInfo &lhs, const MyFuncInfo &rhs)
46 {
47     return lhs.name < rhs.name;
48 }
49 
50 // try to limit the voluminous output a little
51 static std::set<MyFuncInfo> definitionSet;
52 static std::unordered_set<std::string> overridingSet;
53 static std::unordered_set<std::string> nonEmptySet;
54 
55 class UnnecessaryVirtual:
56     public RecursiveASTVisitor<UnnecessaryVirtual>, public loplugin::Plugin
57 {
58 public:
UnnecessaryVirtual(loplugin::InstantiationData const & data)59     explicit UnnecessaryVirtual(loplugin::InstantiationData const & data):
60         Plugin(data) {}
61 
run()62     virtual void run() override
63     {
64         TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
65 
66         // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
67         // writing to the same logfile
68         std::string output;
69         for (const MyFuncInfo & s : definitionSet)
70             output += "definition:\t" + s.name + "\t" + s.sourceLocation + "\n";
71         for (const std::string & s : overridingSet)
72             output += "overriding:\t" + s + "\n";
73         for (const std::string & s : nonEmptySet)
74             output += "nonempty:\t" + s + "\n";
75         std::ofstream myfile;
76         myfile.open( WORKDIR "/loplugin.unnecessaryvirtual.log", std::ios::app | std::ios::out);
77         myfile << output;
78         myfile.close();
79     }
shouldVisitTemplateInstantiations() const80     bool shouldVisitTemplateInstantiations () const { return true; }
shouldVisitImplicitCode() const81     bool shouldVisitImplicitCode() const { return true; }
82 
83     bool VisitCXXMethodDecl( const CXXMethodDecl* decl );
84 private:
85     void MarkRootOverridesNonEmpty( const CXXMethodDecl* methodDecl );
86     std::string toString(SourceLocation loc);
87 };
88 
niceName(const CXXMethodDecl * cxxMethodDecl)89 std::string niceName(const CXXMethodDecl* cxxMethodDecl)
90 {
91     while (cxxMethodDecl->getTemplateInstantiationPattern())
92         cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getTemplateInstantiationPattern());
93     while (cxxMethodDecl->getInstantiatedFromMemberFunction())
94         cxxMethodDecl = dyn_cast<CXXMethodDecl>(cxxMethodDecl->getInstantiatedFromMemberFunction());
95     std::string s = cxxMethodDecl->getReturnType().getCanonicalType().getAsString()
96         + " " + cxxMethodDecl->getQualifiedNameAsString() + "(";
97     for (const ParmVarDecl *pParmVarDecl : cxxMethodDecl->parameters()) {
98         s += pParmVarDecl->getType().getCanonicalType().getAsString();
99         s += ",";
100     }
101     s += ")";
102     if (cxxMethodDecl->isConst()) {
103         s += "const";
104     }
105     return s;
106 }
107 
VisitCXXMethodDecl(const CXXMethodDecl * methodDecl)108 bool UnnecessaryVirtual::VisitCXXMethodDecl( const CXXMethodDecl* methodDecl )
109 {
110     if (ignoreLocation(methodDecl)) {
111         return true;
112     }
113     if (!methodDecl->isVirtual() || methodDecl->isDeleted()) {
114         return true;
115     }
116     // ignore stuff that forms part of the stable URE interface
117     if (isInUnoIncludeFile(methodDecl->getCanonicalDecl())) {
118         return true;
119     }
120 
121     auto body = methodDecl->getBody();
122     if (body) {
123         auto compoundStmt = dyn_cast<CompoundStmt>(body);
124         if (!compoundStmt)
125             MarkRootOverridesNonEmpty(methodDecl->getCanonicalDecl());
126         else if (compoundStmt->size() > 0)
127             MarkRootOverridesNonEmpty(methodDecl->getCanonicalDecl());
128     }
129 
130     if (!methodDecl->isThisDeclarationADefinition())
131         return true;
132 
133     methodDecl = methodDecl->getCanonicalDecl();
134     std::string aNiceName = niceName(methodDecl);
135 
136     // for destructors, we need to check if any of the superclass' destructors are virtual
137     if (isa<CXXDestructorDecl>(methodDecl)) {
138         const CXXRecordDecl* cxxRecordDecl = methodDecl->getParent();
139         if (cxxRecordDecl->getNumBases() == 0) {
140             definitionSet.insert( { aNiceName, toString( methodDecl->getLocation() ) } );
141             return true;
142         }
143         for(auto baseSpecifier = cxxRecordDecl->bases_begin();
144             baseSpecifier != cxxRecordDecl->bases_end(); ++baseSpecifier)
145         {
146             if (baseSpecifier->getType()->isRecordType())
147             {
148                 const CXXRecordDecl* superclassCXXRecordDecl = baseSpecifier->getType()->getAsCXXRecordDecl();
149                 std::string aOverriddenNiceName = niceName(superclassCXXRecordDecl->getDestructor());
150                 overridingSet.insert(aOverriddenNiceName);
151             }
152         }
153         return true;
154     }
155 
156     if (methodDecl->size_overridden_methods() == 0) {
157         definitionSet.insert( { aNiceName, toString( methodDecl->getLocation() ) } );
158     } else {
159        for (auto iter = methodDecl->begin_overridden_methods();
160             iter != methodDecl->end_overridden_methods(); ++iter)
161        {
162            const CXXMethodDecl *overriddenMethod = *iter;
163            // we only care about the first level override to establish that a virtual qualifier was useful.
164            if (overriddenMethod->isPure() || overriddenMethod->size_overridden_methods() == 0)
165            {
166                std::string aOverriddenNiceName = niceName(overriddenMethod);
167                overridingSet.insert(aOverriddenNiceName);
168            }
169         }
170     }
171     return true;
172 }
173 
MarkRootOverridesNonEmpty(const CXXMethodDecl * methodDecl)174 void UnnecessaryVirtual::MarkRootOverridesNonEmpty( const CXXMethodDecl* methodDecl )
175 {
176     if (methodDecl->size_overridden_methods() == 0) {
177         nonEmptySet.insert(niceName(methodDecl));
178         return;
179     }
180     for (auto iter = methodDecl->begin_overridden_methods();
181           iter != methodDecl->end_overridden_methods(); ++iter)
182     {
183         MarkRootOverridesNonEmpty(*iter);
184     }
185 }
186 
toString(SourceLocation loc)187 std::string UnnecessaryVirtual::toString(SourceLocation loc)
188 {
189     SourceLocation expansionLoc = compiler.getSourceManager().getExpansionLoc( loc );
190     StringRef name = getFilenameOfLocation(expansionLoc);
191     std::string sourceLocation = std::string(name.substr(strlen(SRCDIR)+1)) + ":" + std::to_string(compiler.getSourceManager().getSpellingLineNumber(expansionLoc));
192     loplugin::normalizeDotDotInFilePath(sourceLocation);
193     return sourceLocation;
194 }
195 
196 
197 loplugin::Plugin::Registration< UnnecessaryVirtual > X("unnecessaryvirtual", false);
198 
199 }
200 
201 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
202