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