Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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.
5 // A general interface for filtering and only acting on classes in Chromium C++
6 // code.
8 #include "ChromeClassTester.h"
10 #include <sys/param.h>
12 #include "clang/AST/AST.h"
13 #include "clang/Basic/FileManager.h"
14 #include "clang/Basic/SourceManager.h"
16 using namespace clang;
18 namespace {
20 bool starts_with(const std::string& one, const std::string& two) {
21 return one.compare(0, two.size(), two) == 0;
22 }
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 }
30 bool ends_with(const std::string& one, const std::string& two) {
31 if (two.size() > one.size())
32 return false;
34 return one.compare(one.size() - two.size(), two.size(), two) == 0;
35 }
37 } // namespace
39 ChromeClassTester::ChromeClassTester(CompilerInstance& instance)
40 : instance_(instance),
41 diagnostic_(instance.getDiagnostics()) {
42 BuildBannedLists();
43 }
45 ChromeClassTester::~ChromeClassTester() {}
47 void ChromeClassTester::HandleTagDeclDefinition(TagDecl* tag) {
48 pending_class_decls_.push_back(tag);
49 }
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();
56 return true; // true means continue parsing.
57 }
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.
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;
74 if (InBannedNamespace(record))
75 return;
77 SourceLocation record_location = record->getInnerLocStart();
78 if (InBannedDirectory(record_location))
79 return;
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;
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;
93 CheckChromeClass(record_location, record);
94 }
95 }
97 void ChromeClassTester::emitWarning(SourceLocation loc,
98 const char* raw_error) {
99 FullSourceLoc full(loc, instance().getSourceManager());
100 std::string err;
101 err = "[chromium-style] ";
102 err += raw_error;
103 DiagnosticsEngine::Level level =
104 diagnostic().getWarningsAsErrors() ?
105 DiagnosticsEngine::Error :
106 DiagnosticsEngine::Warning;
107 unsigned id = diagnostic().getCustomDiagID(level, err);
108 DiagnosticBuilder builder = diagnostic().Report(full, id);
109 }
111 bool ChromeClassTester::InBannedNamespace(const Decl* record) {
112 std::string n = GetNamespace(record);
113 if (!n.empty()) {
114 return std::find(banned_namespaces_.begin(), banned_namespaces_.end(), n)
115 != banned_namespaces_.end();
116 }
118 return false;
119 }
121 std::string ChromeClassTester::GetNamespace(const Decl* record) {
122 return GetNamespaceImpl(record->getDeclContext(), "");
123 }
125 bool ChromeClassTester::InImplementationFile(SourceLocation record_location) {
126 std::string filename;
127 if (!GetFilename(record_location, &filename))
128 return false;
130 if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") ||
131 ends_with(filename, ".mm")) {
132 return true;
133 }
135 return false;
136 }
138 void ChromeClassTester::BuildBannedLists() {
139 banned_namespaces_.push_back("std");
140 banned_namespaces_.push_back("__gnu_cxx");
141 banned_namespaces_.push_back("WebKit");
143 banned_directories_.push_back("third_party/");
144 banned_directories_.push_back("native_client/");
145 banned_directories_.push_back("breakpad/");
146 banned_directories_.push_back("courgette/");
147 banned_directories_.push_back("pdf/");
148 banned_directories_.push_back("ppapi/");
149 banned_directories_.push_back("usr/");
150 banned_directories_.push_back("testing/");
151 banned_directories_.push_back("googleurl/");
152 banned_directories_.push_back("v8/");
153 banned_directories_.push_back("dart/");
154 banned_directories_.push_back("sdch/");
155 banned_directories_.push_back("icu4c/");
156 banned_directories_.push_back("frameworks/");
158 // Don't check autogenerated headers.
159 // Make puts them below $(builddir_name)/.../gen and geni.
160 // Ninja puts them below OUTPUT_DIR/.../gen
161 // Xcode has a fixed output directory for everything.
162 banned_directories_.push_back("gen/");
163 banned_directories_.push_back("geni/");
164 banned_directories_.push_back("xcodebuild/");
166 // You are standing in a mazy of twisty dependencies, all resolved by
167 // putting everything in the header.
168 banned_directories_.push_back("automation/");
170 // Don't check system headers.
171 banned_directories_.push_back("/Developer/");
173 // Used in really low level threading code that probably shouldn't be out of
174 // lined.
175 ignored_record_names_.insert("ThreadLocalBoolean");
177 // A complicated pickle derived struct that is all packed integers.
178 ignored_record_names_.insert("Header");
180 // Part of the GPU system that uses multiple included header
181 // weirdness. Never getting this right.
182 ignored_record_names_.insert("Validators");
184 // Has a UNIT_TEST only constructor. Isn't *terribly* complex...
185 ignored_record_names_.insert("AutocompleteController");
186 ignored_record_names_.insert("HistoryURLProvider");
188 // Because of chrome frame
189 ignored_record_names_.insert("ReliabilityTestSuite");
191 // Used over in the net unittests. A large enough bundle of integers with 1
192 // non-pod class member. Probably harmless.
193 ignored_record_names_.insert("MockTransaction");
195 // Used heavily in ui_unittests and once in views_unittests. Fixing this
196 // isn't worth the overhead of an additional library.
197 ignored_record_names_.insert("TestAnimationDelegate");
199 // Part of our public interface that nacl and friends use. (Arguably, this
200 // should mean that this is a higher priority but fixing this looks hard.)
201 ignored_record_names_.insert("PluginVersionInfo");
202 }
204 std::string ChromeClassTester::GetNamespaceImpl(const DeclContext* context,
205 const std::string& candidate) {
206 switch (context->getDeclKind()) {
207 case Decl::TranslationUnit: {
208 return candidate;
209 }
210 case Decl::Namespace: {
211 const NamespaceDecl* decl = dyn_cast<NamespaceDecl>(context);
212 std::string name_str;
213 llvm::raw_string_ostream OS(name_str);
214 if (decl->isAnonymousNamespace())
215 OS << "<anonymous namespace>";
216 else
217 OS << *decl;
218 return GetNamespaceImpl(context->getParent(),
219 OS.str());
220 }
221 default: {
222 return GetNamespaceImpl(context->getParent(), candidate);
223 }
224 }
225 }
227 bool ChromeClassTester::InBannedDirectory(SourceLocation loc) {
228 std::string filename;
229 if (!GetFilename(loc, &filename)) {
230 // If the filename cannot be determined, simply treat this as a banned
231 // location, instead of going through the full lookup process.
232 return true;
233 }
235 // We need to special case scratch space; which is where clang does its
236 // macro expansion. We explicitly want to allow people to do otherwise bad
237 // things through macros that were defined due to third party libraries.
238 if (filename == "<scratch space>")
239 return true;
241 // Don't complain about autogenerated protobuf files.
242 if (ends_with(filename, ".pb.h")) {
243 return true;
244 }
246 // We need to munge the paths so that they are relative to the repository
247 // srcroot. We first resolve the symlinktastic relative path and then
248 // remove our known srcroot from it if needed.
249 char resolvedPath[MAXPATHLEN];
250 if (realpath(filename.c_str(), resolvedPath)) {
251 filename = resolvedPath;
252 }
254 // On linux, chrome is often checked out to /usr/local/google. Due to the
255 // "usr" rule in banned_directories_, all diagnostics would be suppressed
256 // in that case. As a workaround, strip that prefix.
257 filename = lstrip(filename, "/usr/local/google");
259 for (std::vector<std::string>::const_iterator it =
260 banned_directories_.begin();
261 it != banned_directories_.end(); ++it) {
262 // If we can find any of the banned path components in this path, then
263 // this file is rejected.
264 size_t index = filename.find(*it);
265 if (index != std::string::npos) {
266 bool matches_full_dir_name = index == 0 || filename[index - 1] == '/';
267 if ((*it)[0] == '/')
268 matches_full_dir_name = true;
269 if (matches_full_dir_name)
270 return true;
271 }
272 }
274 return false;
275 }
277 bool ChromeClassTester::IsIgnoredType(const std::string& base_name) {
278 return ignored_record_names_.find(base_name) != ignored_record_names_.end();
279 }
281 bool ChromeClassTester::GetFilename(SourceLocation loc,
282 std::string* filename) {
283 const SourceManager& source_manager = instance_.getSourceManager();
284 SourceLocation spelling_location = source_manager.getSpellingLoc(loc);
285 PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location);
286 if (ploc.isInvalid()) {
287 // If we're in an invalid location, we're looking at things that aren't
288 // actually stated in the source.
289 return false;
290 }
292 *filename = ploc.getFilename();
293 return true;
294 }