• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4 
5 // A general interface for filtering and only acting on classes in Chromium C++
6 // code.
7 
8 #include "ChromeClassTester.h"
9 
10 #include <sys/param.h>
11 
12 #include "clang/AST/AST.h"
13 #include "clang/Basic/FileManager.h"
14 #include "clang/Basic/SourceManager.h"
15 
16 using namespace clang;
17 
18 namespace {
19 
starts_with(const std::string & one,const std::string & two)20 bool starts_with(const std::string& one, const std::string& two) {
21   return one.compare(0, two.size(), two) == 0;
22 }
23 
lstrip(const std::string & one,const std::string & two)24 std::string lstrip(const std::string& one, const std::string& two) {
25   if (starts_with(one, two))
26     return one.substr(two.size());
27   return one;
28 }
29 
ends_with(const std::string & one,const std::string & two)30 bool ends_with(const std::string& one, const std::string& two) {
31   if (two.size() > one.size())
32     return false;
33 
34   return one.compare(one.size() - two.size(), two.size(), two) == 0;
35 }
36 
37 }  // namespace
38 
ChromeClassTester(CompilerInstance & instance)39 ChromeClassTester::ChromeClassTester(CompilerInstance& instance)
40     : instance_(instance),
41       diagnostic_(instance.getDiagnostics()) {
42   BuildBannedLists();
43 }
44 
~ChromeClassTester()45 ChromeClassTester::~ChromeClassTester() {}
46 
HandleTagDeclDefinition(TagDecl * tag)47 void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) {
48   pending_class_decls_.push_back(tag);
49 }
50 
HandleTopLevelDecl(DeclGroupRef group_ref)51 bool ChromeClassTester::HandleTopLevelDecl(DeclGroupRef group_ref) {
52   for (size_t i = 0; i < pending_class_decls_.size(); ++i)
53     CheckTag(pending_class_decls_[i]);
54   pending_class_decls_.clear();
55 
56   return true;  // true means continue parsing.
57 }
58 
CheckTag(TagDecl * tag)59 void ChromeClassTester::CheckTag(TagDecl* tag) {
60   // We handle class types here where we have semantic information. We can only
61   // check structs/classes/enums here, but we get a bunch of nice semantic
62   // information instead of just parsing information.
63 
64   if (CXXRecordDecl* record = dyn_cast<CXXRecordDecl>(tag)) {
65     // If this is a POD or a class template or a type dependent on a
66     // templated class, assume there's no ctor/dtor/virtual method
67     // optimization that we can do.
68     if (record->isPOD() ||
69         record->getDescribedClassTemplate() ||
70         record->getTemplateSpecializationKind() ||
71         record->isDependentType())
72       return;
73 
74     if (InBannedNamespace(record))
75       return;
76 
77     SourceLocation record_location = record->getInnerLocStart();
78     if (InBannedDirectory(record_location))
79       return;
80 
81     // We sadly need to maintain a blacklist of types that violate these
82     // rules, but do so for good reason or due to limitations of this
83     // checker (i.e., we don't handle extern templates very well).
84     std::string base_name = record->getNameAsString();
85     if (IsIgnoredType(base_name))
86       return;
87 
88     // We ignore all classes that end with "Matcher" because they're probably
89     // GMock artifacts.
90     if (ends_with(base_name, "Matcher"))
91         return;
92 
93     CheckChromeClass(record_location, record);
94   } else if (EnumDecl* enum_decl = dyn_cast<EnumDecl>(tag)) {
95     SourceLocation enum_location = enum_decl->getInnerLocStart();
96     if (InBannedDirectory(enum_location))
97       return;
98 
99     std::string base_name = enum_decl->getNameAsString();
100     if (IsIgnoredType(base_name))
101       return;
102 
103     CheckChromeEnum(enum_location, enum_decl);
104   }
105 }
106 
emitWarning(SourceLocation loc,const char * raw_error)107 void ChromeClassTester::emitWarning(SourceLocation loc,
108                                     const char* raw_error) {
109   FullSourceLoc full(loc, instance().getSourceManager());
110   std::string err;
111   err = "[chromium-style] ";
112   err += raw_error;
113   DiagnosticIDs::Level level =
114       diagnostic().getWarningsAsErrors() ?
115       DiagnosticIDs::Error :
116       DiagnosticIDs::Warning;
117   unsigned id = diagnostic().getDiagnosticIDs()->getCustomDiagID(level, err);
118   DiagnosticBuilder builder = diagnostic().Report(full, id);
119 }
120 
InBannedNamespace(const Decl * record)121 bool ChromeClassTester::InBannedNamespace(const Decl* record) {
122   std::string n = GetNamespace(record);
123   if (!n.empty()) {
124     return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n)
125         != banned_namespaces_.end();
126   }
127 
128   return false;
129 }
130 
GetNamespace(const Decl * record)131 std::string ChromeClassTester::GetNamespace(const Decl* record) {
132   return GetNamespaceImpl(record->getDeclContext(), "");
133 }
134 
InImplementationFile(SourceLocation record_location)135 bool ChromeClassTester::InImplementationFile(SourceLocation record_location) {
136   std::string filename;
137   if (!GetFilename(record_location, &filename))
138     return false;
139 
140   if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") ||
141       ends_with(filename, ".mm")) {
142     return true;
143   }
144 
145   return false;
146 }
147 
BuildBannedLists()148 void ChromeClassTester::BuildBannedLists() {
149   banned_namespaces_.push_back("std");
150   banned_namespaces_.push_back("__gnu_cxx");
151 
152   // We're in the process of renaming WebKit to blink.
153   // TODO(abarth): Remove WebKit once the rename is complete.
154   banned_namespaces_.push_back("WebKit");
155   banned_namespaces_.push_back("blink");
156 
157   banned_directories_.push_back("third_party/");
158   banned_directories_.push_back("native_client/");
159   banned_directories_.push_back("breakpad/");
160   banned_directories_.push_back("courgette/");
161   banned_directories_.push_back("pdf/");
162   banned_directories_.push_back("ppapi/");
163   banned_directories_.push_back("usr/");
164   banned_directories_.push_back("testing/");
165   banned_directories_.push_back("v8/");
166   banned_directories_.push_back("dart/");
167   banned_directories_.push_back("sdch/");
168   banned_directories_.push_back("icu4c/");
169   banned_directories_.push_back("frameworks/");
170 
171   // Don't check autogenerated headers.
172   // Make puts them below $(builddir_name)/.../gen and geni.
173   // Ninja puts them below OUTPUT_DIR/.../gen
174   // Xcode has a fixed output directory for everything.
175   banned_directories_.push_back("gen/");
176   banned_directories_.push_back("geni/");
177   banned_directories_.push_back("xcodebuild/");
178 
179   // You are standing in a mazy of twisty dependencies, all resolved by
180   // putting everything in the header.
181   banned_directories_.push_back("automation/");
182 
183   // Don't check system headers.
184   banned_directories_.push_back("/Developer/");
185 
186   // Used in really low level threading code that probably shouldn't be out of
187   // lined.
188   ignored_record_names_.insert("ThreadLocalBoolean");
189 
190   // A complicated pickle derived struct that is all packed integers.
191   ignored_record_names_.insert("Header");
192 
193   // Part of the GPU system that uses multiple included header
194   // weirdness. Never getting this right.
195   ignored_record_names_.insert("Validators");
196 
197   // Has a UNIT_TEST only constructor. Isn't *terribly* complex...
198   ignored_record_names_.insert("AutocompleteController");
199   ignored_record_names_.insert("HistoryURLProvider");
200 
201   // Because of chrome frame
202   ignored_record_names_.insert("ReliabilityTestSuite");
203 
204   // Used over in the net unittests. A large enough bundle of integers with 1
205   // non-pod class member. Probably harmless.
206   ignored_record_names_.insert("MockTransaction");
207 
208   // Enum type with _LAST members where _LAST doesn't mean last enum value.
209   ignored_record_names_.insert("ServerFieldType");
210 
211   // Used heavily in ui_unittests and once in views_unittests. Fixing this
212   // isn't worth the overhead of an additional library.
213   ignored_record_names_.insert("TestAnimationDelegate");
214 
215   // Part of our public interface that nacl and friends use. (Arguably, this
216   // should mean that this is a higher priority but fixing this looks hard.)
217   ignored_record_names_.insert("PluginVersionInfo");
218 
219   // Measured performance improvement on cc_perftests. See
220   // https://codereview.chromium.org/11299290/
221   ignored_record_names_.insert("QuadF");
222 
223   // Enum type with _LAST members where _LAST doesn't mean last enum value.
224   ignored_record_names_.insert("ViewID");
225 }
226 
GetNamespaceImpl(const DeclContext * context,const std::string & candidate)227 std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context,
228                                                 const std::string& candidate) {
229   switch (context->getDeclKind()) {
230     case Decl::TranslationUnit: {
231       return candidate;
232     }
233     case Decl::Namespace: {
234       const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context);
235       std::string name_str;
236       llvm::raw_string_ostream OS(name_str);
237       if (decl->isAnonymousNamespace())
238         OS << "<anonymous namespace>";
239       else
240         OS << *decl;
241       return GetNamespaceImpl(context->getParent(),
242                               OS.str());
243     }
244     default: {
245       return GetNamespaceImpl(context->getParent(), candidate);
246     }
247   }
248 }
249 
InBannedDirectory(SourceLocation loc)250 bool ChromeClassTester::InBannedDirectory(SourceLocation loc) {
251   std::string filename;
252   if (!GetFilename(loc, &filename)) {
253     // If the filename cannot be determined, simply treat this as a banned
254     // location, instead of going through the full lookup process.
255     return true;
256   }
257 
258   // We need to special case scratch space; which is where clang does its
259   // macro expansion. We explicitly want to allow people to do otherwise bad
260   // things through macros that were defined due to third party libraries.
261   if (filename == "<scratch space>")
262     return true;
263 
264   // Don't complain about autogenerated protobuf files.
265   if (ends_with(filename, ".pb.h")) {
266     return true;
267   }
268 
269   // We need to munge the paths so that they are relative to the repository
270   // srcroot. We first resolve the symlinktastic relative path and then
271   // remove our known srcroot from it if needed.
272   char resolvedPath[MAXPATHLEN];
273   if (realpath(filename.c_str(), resolvedPath)) {
274     filename = resolvedPath;
275   }
276 
277   // On linux, chrome is often checked out to /usr/local/google. Due to the
278   // "usr" rule in banned_directories_, all diagnostics would be suppressed
279   // in that case. As a workaround, strip that prefix.
280   filename = lstrip(filename, "/usr/local/google");
281 
282   for (std::vector<std::string>::const_iterator it =
283            banned_directories_.begin();
284        it != banned_directories_.end(); ++it) {
285     // If we can find any of the banned path components in this path, then
286     // this file is rejected.
287     size_t index = filename.find(*it);
288     if (index != std::string::npos) {
289       bool matches_full_dir_name = index == 0 || filename[index - 1] == '/';
290       if ((*it)[0] == '/')
291         matches_full_dir_name = true;
292       if (matches_full_dir_name)
293         return true;
294     }
295   }
296 
297   return false;
298 }
299 
IsIgnoredType(const std::string & base_name)300 bool ChromeClassTester::IsIgnoredType(const std::string& base_name) {
301   return ignored_record_names_.find(base_name) != ignored_record_names_.end();
302 }
303 
GetFilename(SourceLocation loc,std::string * filename)304 bool ChromeClassTester::GetFilename(SourceLocation loc,
305                                     std::string* filename) {
306   const SourceManager& source_manager = instance_.getSourceManager();
307   SourceLocation spelling_location = source_manager.getSpellingLoc(loc);
308   PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location);
309   if (ploc.isInvalid()) {
310     // If we're in an invalid location, we're looking at things that aren't
311     // actually stated in the source.
312     return false;
313   }
314 
315   *filename = ploc.getFilename();
316   return true;
317 }
318