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 <algorithm>
11
12 #include "Util.h"
13 #include "clang/AST/AST.h"
14 #include "clang/Basic/FileManager.h"
15 #include "clang/Basic/SourceManager.h"
16
17 #ifdef LLVM_ON_UNIX
18 #include <sys/param.h>
19 #endif
20 #if defined(_WIN32)
21 #include <windows.h>
22 #endif
23
24 using namespace clang;
25 using chrome_checker::Options;
26
27 namespace {
28
ends_with(const std::string & one,const std::string & two)29 bool ends_with(const std::string& one, const std::string& two) {
30 if (two.size() > one.size())
31 return false;
32
33 return one.compare(one.size() - two.size(), two.size(), two) == 0;
34 }
35
36 } // namespace
37
ChromeClassTester(CompilerInstance & instance,const Options & options)38 ChromeClassTester::ChromeClassTester(CompilerInstance& instance,
39 const Options& options)
40 : options_(options),
41 instance_(instance),
42 diagnostic_(instance.getDiagnostics()) {
43 BuildBannedLists();
44 }
45
~ChromeClassTester()46 ChromeClassTester::~ChromeClassTester() {}
47
CheckTag(TagDecl * tag)48 void ChromeClassTester::CheckTag(TagDecl* tag) {
49 // We handle class types here where we have semantic information. We can only
50 // check structs/classes/enums here, but we get a bunch of nice semantic
51 // information instead of just parsing information.
52 SourceLocation location = tag->getInnerLocStart();
53 LocationType location_type = ClassifyLocation(location);
54 if (location_type == LocationType::kThirdParty)
55 return;
56
57 if (CXXRecordDecl* record = dyn_cast<CXXRecordDecl>(tag)) {
58 // We sadly need to maintain a blacklist of types that violate these
59 // rules, but do so for good reason or due to limitations of this
60 // checker (i.e., we don't handle extern templates very well).
61 std::string base_name = record->getNameAsString();
62 if (IsIgnoredType(base_name))
63 return;
64
65 // We ignore all classes that end with "Matcher" because they're probably
66 // GMock artifacts.
67 if (!options_.check_gmock_objects && ends_with(base_name, "Matcher"))
68 return;
69
70 CheckChromeClass(location_type, location, record);
71 }
72 }
73
ClassifyLocation(SourceLocation loc)74 ChromeClassTester::LocationType ChromeClassTester::ClassifyLocation(
75 SourceLocation loc) {
76 if (instance().getSourceManager().isInSystemHeader(loc))
77 return LocationType::kThirdParty;
78
79 std::string filename;
80 if (!GetFilename(loc, &filename)) {
81 // If the filename cannot be determined, simply treat this as a banned
82 // location, instead of going through the full lookup process.
83 return LocationType::kThirdParty;
84 }
85
86 // We need to special case scratch space; which is where clang does its
87 // macro expansion. We explicitly want to allow people to do otherwise bad
88 // things through macros that were defined due to third party libraries.
89 if (filename == "<scratch space>")
90 return LocationType::kThirdParty;
91
92 // Ensure that we can search for patterns of the form "/foo/" even
93 // if we have a relative path like "foo/bar.cc". We don't expect
94 // this transformed path to exist necessarily.
95 if (filename.front() != '/') {
96 filename.insert(0, 1, '/');
97 }
98
99 // When using distributed cross compilation build tools, file paths can have
100 // separators which differ from ones at this platform. Make them consistent.
101 std::replace(filename.begin(), filename.end(), '\\', '/');
102
103 // Don't check autogenerated files. ninja puts them in $OUT_DIR/gen.
104 if (filename.find("/gen/") != std::string::npos)
105 return LocationType::kThirdParty;
106
107 if (filename.find("/third_party/blink/") != std::string::npos &&
108 // Browser-side code should always use the full range of checks.
109 filename.find("/third_party/blink/browser/") == std::string::npos) {
110 return LocationType::kBlink;
111 }
112
113 for (const std::string& banned_dir : banned_directories_) {
114 // If any of the banned directories occur as a component in filename,
115 // this file is rejected.
116 assert(banned_dir.front() == '/' && "Banned dir must start with '/'");
117 assert(banned_dir.back() == '/' && "Banned dir must end with '/'");
118
119 if (filename.find(banned_dir) != std::string::npos)
120 return LocationType::kThirdParty;
121 }
122
123 return LocationType::kChrome;
124 }
125
HasIgnoredBases(const CXXRecordDecl * record)126 bool ChromeClassTester::HasIgnoredBases(const CXXRecordDecl* record) {
127 for (const auto& base : record->bases()) {
128 CXXRecordDecl* base_record = base.getType()->getAsCXXRecordDecl();
129 if (!base_record)
130 continue;
131
132 const std::string& base_name = base_record->getQualifiedNameAsString();
133 if (ignored_base_classes_.count(base_name) > 0)
134 return true;
135 if (HasIgnoredBases(base_record))
136 return true;
137 }
138 return false;
139 }
140
InImplementationFile(SourceLocation record_location)141 bool ChromeClassTester::InImplementationFile(SourceLocation record_location) {
142 std::string filename;
143
144 // If |record_location| is a macro, check the whole chain of expansions.
145 const SourceManager& source_manager = instance_.getSourceManager();
146 while (true) {
147 if (GetFilename(record_location, &filename)) {
148 if (ends_with(filename, ".cc") || ends_with(filename, ".cpp") ||
149 ends_with(filename, ".mm")) {
150 return true;
151 }
152 }
153 if (!record_location.isMacroID()) {
154 break;
155 }
156 record_location =
157 source_manager.getImmediateExpansionRange(record_location).getBegin();
158 }
159
160 return false;
161 }
162
BuildBannedLists()163 void ChromeClassTester::BuildBannedLists() {
164 banned_directories_.emplace("/third_party/");
165 banned_directories_.emplace("/native_client/");
166 banned_directories_.emplace("/breakpad/");
167 banned_directories_.emplace("/courgette/");
168 banned_directories_.emplace("/ppapi/");
169 banned_directories_.emplace("/testing/");
170 banned_directories_.emplace("/v8/");
171 banned_directories_.emplace("/frameworks/");
172
173 // Used in really low level threading code that probably shouldn't be out of
174 // lined.
175 ignored_record_names_.emplace("ThreadLocalBoolean");
176
177 // A complicated pickle derived struct that is all packed integers.
178 ignored_record_names_.emplace("Header");
179
180 // Part of the GPU system that uses multiple included header
181 // weirdness. Never getting this right.
182 ignored_record_names_.emplace("Validators");
183
184 // Has a UNIT_TEST only constructor. Isn't *terribly* complex...
185 ignored_record_names_.emplace("AutocompleteController");
186 ignored_record_names_.emplace("HistoryURLProvider");
187
188 // Used over in the net unittests. A large enough bundle of integers with 1
189 // non-pod class member. Probably harmless.
190 ignored_record_names_.emplace("MockTransaction");
191
192 // Used heavily in ui_base_unittests and once in views_unittests. Fixing this
193 // isn't worth the overhead of an additional library.
194 ignored_record_names_.emplace("TestAnimationDelegate");
195
196 // Part of our public interface that nacl and friends use. (Arguably, this
197 // should mean that this is a higher priority but fixing this looks hard.)
198 ignored_record_names_.emplace("PluginVersionInfo");
199
200 // Measured performance improvement on cc_perftests. See
201 // https://codereview.chromium.org/11299290/
202 ignored_record_names_.emplace("QuadF");
203
204 // Ignore IPC::NoParams bases, since these structs are generated via
205 // macros and it makes it difficult to add explicit ctors.
206 ignored_base_classes_.emplace("IPC::NoParams");
207 }
208
IsIgnoredType(const std::string & base_name)209 bool ChromeClassTester::IsIgnoredType(const std::string& base_name) {
210 return ignored_record_names_.find(base_name) != ignored_record_names_.end();
211 }
212
GetFilename(SourceLocation loc,std::string * filename)213 bool ChromeClassTester::GetFilename(SourceLocation loc,
214 std::string* filename) {
215 const SourceManager& source_manager = instance_.getSourceManager();
216 SourceLocation spelling_location = source_manager.getSpellingLoc(loc);
217 PresumedLoc ploc = source_manager.getPresumedLoc(spelling_location);
218 if (ploc.isInvalid()) {
219 // If we're in an invalid location, we're looking at things that aren't
220 // actually stated in the source.
221 return false;
222 }
223
224 *filename = ploc.getFilename();
225 return true;
226 }
227
getErrorLevel()228 DiagnosticsEngine::Level ChromeClassTester::getErrorLevel() {
229 return diagnostic().getWarningsAsErrors() ? DiagnosticsEngine::Error
230 : DiagnosticsEngine::Warning;
231 }
232