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 <set>
12 #include <string>
13 #include <iostream>
14 #include "plugin.hxx"
15 #include <fstream>
16 
17 /**
18 Look for classes that are final i.e. nothing extends them, and have either
19 (a) protected fields or members.
20 or
21 (b) virtual members
22 
23 In the case of (a), those members/fields can be made private.
24 In the case of (b), making the class final means the compiler can devirtualise
25 some method class.
26 
27 The process goes something like this:
28   $ make check
29   $ make FORCE_COMPILE_ALL=1 COMPILER_PLUGIN_TOOL='finalclasses' check
30   $ ./compilerplugins/clang/finalclasses.py
31 
32 */
33 
34 namespace {
35 
36 // try to limit the voluminous output a little
37 static std::set<std::string> inheritedFromSet;
38 static std::map<std::string,std::string> definitionMap;  // className -> filename
39 
40 class FinalClasses:
41     public RecursiveASTVisitor<FinalClasses>, public loplugin::Plugin
42 {
43 public:
FinalClasses(loplugin::InstantiationData const & data)44     explicit FinalClasses(loplugin::InstantiationData const & data):
45         Plugin(data) {}
46 
run()47     virtual void run() override
48     {
49         TraverseDecl(compiler.getASTContext().getTranslationUnitDecl());
50 
51         // dump all our output in one write call - this is to try and limit IO "crosstalk" between multiple processes
52         // writing to the same logfile
53         std::string output;
54         for (const std::string & s : inheritedFromSet)
55             output += "inherited-from:\t" + s + "\n";
56         for (const auto & s : definitionMap)
57             output += "definition:\t" + s.first + "\t" + s.second + "\n";
58         std::ofstream myfile;
59         myfile.open( WORKDIR "/loplugin.finalclasses.log", std::ios::app | std::ios::out);
60         myfile << output;
61         myfile.close();
62     }
63 
shouldVisitTemplateInstantiations() const64     bool shouldVisitTemplateInstantiations () const { return true; }
65 
shouldVisitImplicitCode() const66     bool shouldVisitImplicitCode() const { return true; }
67 
68     bool VisitCXXRecordDecl( const CXXRecordDecl* decl);
69 private:
70     void checkBase(QualType qt);
71 };
72 
ignoreClass(StringRef s)73 bool ignoreClass(StringRef s)
74 {
75     // ignore stuff in the standard library, and UNO stuff we can't touch.
76     if (s.startswith("rtl::") || s.startswith("sal::") || s.startswith("com::sun::")
77         || s.startswith("std::") || s.startswith("boost::")
78         || s == "OString" || s == "OUString" || s == "bad_alloc")
79     {
80         return true;
81     }
82     return false;
83 }
84 
VisitCXXRecordDecl(const CXXRecordDecl * decl)85 bool FinalClasses::VisitCXXRecordDecl(const CXXRecordDecl* decl)
86 {
87     if (ignoreLocation(decl))
88         return true;
89     decl = decl->getCanonicalDecl();
90     if (!decl->hasDefinition())
91         return true;
92 
93     for (auto it = decl->bases_begin(); it != decl->bases_end(); ++it)
94     {
95         const CXXBaseSpecifier spec = *it;
96         checkBase(spec.getType());
97     }
98     for (auto it = decl->vbases_begin(); it != decl->vbases_end(); ++it)
99     {
100         const CXXBaseSpecifier spec = *it;
101         checkBase(spec.getType());
102     }
103 
104     if (decl->hasAttr<FinalAttr>())
105         return true;
106     bool bFoundVirtual = false;
107     bool bFoundProtected = false;
108     for (auto it = decl->method_begin(); it != decl->method_end(); ++it) {
109         auto i = *it;
110         // ignore methods that are overriding base-class methods, making them private
111         // isn't useful
112         if ( !i->hasAttr<OverrideAttr>() && i->getAccess() == AS_protected )
113             bFoundProtected = true;
114         if ( i->isVirtual() )
115             bFoundVirtual = true;
116     }
117     if (!bFoundProtected)
118     {
119         for (auto it = decl->field_begin(); it != decl->field_end(); ++it) {
120             auto i = *it;
121             if ( i->getAccess() == AS_protected ) {
122                 bFoundProtected = true;
123                 break;
124             }
125         }
126     }
127     if (!bFoundProtected && !bFoundVirtual)
128         return true;
129 
130     std::string s = decl->getQualifiedNameAsString();
131     if (ignoreClass(s))
132         return true;
133 
134     SourceLocation spellingLocation = compiler.getSourceManager().getSpellingLoc(compat::getBeginLoc(decl));
135     auto const filename = getFilenameOfLocation(spellingLocation);
136     auto sourceLocation = filename.substr(strlen(SRCDIR)).str() + ":"
137         + std::to_string(compiler.getSourceManager().getSpellingLineNumber(spellingLocation));
138     definitionMap.insert( std::pair<std::string,std::string>(s, sourceLocation) );
139     return true;
140 }
141 
checkBase(QualType baseType)142 void FinalClasses::checkBase(QualType baseType)
143 {
144     // need to look through typedefs, hence the getUnqualifiedDesugaredType
145     baseType = baseType.getDesugaredType(compiler.getASTContext());
146     std::string x;
147     // so that we get just the template name, excluding the template parameters
148     if (baseType->isRecordType())
149         x = baseType->getAsCXXRecordDecl()->getQualifiedNameAsString();
150     else if (auto templateType = baseType->getAs<TemplateSpecializationType>())
151         x = templateType->getTemplateName().getAsTemplateDecl()->getQualifiedNameAsString();
152     else
153         x = baseType.getAsString();
154     inheritedFromSet.insert( x );
155 }
156 
157 loplugin::Plugin::Registration< FinalClasses > X("finalclasses", false);
158 
159 }
160 
161 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
162