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