1 //===--- iwyu_path_util.cc - file-path utilities for include-what-you-use -===//
2 //
3 //                     The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "iwyu_path_util.h"
11 
12 #include <algorithm>                    // for std::replace
13 #include <cstddef>
14 #include <cstring>                      // for strlen
15 #include <system_error>
16 
17 #include "iwyu_stl_util.h"
18 
19 #include "llvm/ADT/SmallString.h"
20 #include "llvm/ADT/StringRef.h"
21 #include "llvm/ADT/STLExtras.h"
22 #include "llvm/Support/FileSystem.h"
23 #include "llvm/Support/Path.h"
24 
25 namespace include_what_you_use {
26 
27 namespace {
28 
29 vector<HeaderSearchPath>* header_search_paths;
30 
31 // Please keep this in sync with _SOURCE_EXTENSIONS in fix_includes.py.
32 const char* source_extensions[] = {
33   ".c",
34   ".C",
35   ".cc",
36   ".CC",
37   ".cxx",
38   ".CXX",
39   ".cpp",
40   ".CPP",
41   ".c++",
42   ".C++",
43   ".cp"
44 };
45 
46 }  // anonymous namespace
47 
SetHeaderSearchPaths(const vector<HeaderSearchPath> & search_paths)48 void SetHeaderSearchPaths(const vector<HeaderSearchPath>& search_paths) {
49   if (header_search_paths != nullptr) {
50     delete header_search_paths;
51   }
52   header_search_paths = new vector<HeaderSearchPath>(search_paths);
53 }
54 
HeaderSearchPaths()55 const vector<HeaderSearchPath>& HeaderSearchPaths() {
56   if (header_search_paths == nullptr) {
57     header_search_paths = new vector<HeaderSearchPath>();
58   }
59   return *header_search_paths;
60 }
61 
IsHeaderFile(string path)62 bool IsHeaderFile(string path) {
63   if (EndsWith(path, "\"") || EndsWith(path, ">"))
64     path = path.substr(0, path.length() - 1);
65 
66   // Some headers don't have an extension (e.g. <string>), or have an
67   // unusual one (the compiler doesn't care), so it's safer to
68   // enumerate non-header extensions instead.
69   //  for (size_t i = 0; i < llvm::array_lengthof(source_extensions); ++i) {
70   for (const char* source_extension : source_extensions) {
71     if (EndsWith(path, source_extension))
72       return false;
73   }
74 
75   return true;
76 }
77 
Basename(const string & path)78 string Basename(const string& path) {
79   string::size_type last_slash = path.rfind('/');
80   if (last_slash != string::npos) {
81     return path.substr(last_slash + 1);
82   }
83   return path;
84 }
85 
GetCanonicalName(string file_path)86 string GetCanonicalName(string file_path) {
87   // For this special 'path' we just return it.
88   // Note that we leave the 'quotes' to make it different from regular paths.
89   if (file_path == "<built-in>")
90     return file_path;
91 
92   CHECK_(!IsQuotedInclude(file_path));
93 
94   file_path = NormalizeFilePath(file_path);
95 
96   bool stripped_ext = StripRight(&file_path, ".h")
97       || StripRight(&file_path, ".H")
98       || StripRight(&file_path, ".hpp")
99       || StripRight(&file_path, ".hxx")
100       || StripRight(&file_path, ".hh")
101       || StripRight(&file_path, ".inl");
102   if (!stripped_ext) {
103     for (const char* source_extension : source_extensions) {
104       if (StripRight(&file_path, source_extension))
105         break;
106     }
107   }
108 
109   StripRight(&file_path, "_unittest")
110       || StripRight(&file_path, "_regtest")
111       || StripRight(&file_path, "_test")
112       || StripLeft(&file_path, "test_headercompile_");
113   StripRight(&file_path, "-inl");
114   // .h files in /public/ match .cc files in /internal/
115   const string::size_type internal_pos = file_path.find("/internal/");
116   if (internal_pos != string::npos)
117     file_path = (file_path.substr(0, internal_pos) + "/public/" +
118                  file_path.substr(internal_pos + strlen("/internal/")));
119 
120   // .h files in /include/ match .cc files in /src/
121   const string::size_type include_pos = file_path.find("/include/");
122   if (include_pos != string::npos)
123     file_path = (file_path.substr(0, include_pos) + "/src/" +
124                  file_path.substr(include_pos + strlen("/include/")));
125   return file_path;
126 }
127 
NormalizeFilePath(const string & path)128 string NormalizeFilePath(const string& path) {
129   llvm::SmallString<128> normalized(path);
130   llvm::sys::path::remove_dots(normalized, /*remove_dot_dot=*/true);
131 
132 #ifdef _WIN32
133   // Canonicalize directory separators (forward slashes considered canonical.)
134   std::replace(normalized.begin(), normalized.end(), '\\', '/');
135 #endif
136 
137   return normalized.str().str();
138 }
139 
NormalizeDirPath(const string & path)140 string NormalizeDirPath(const string& path) {
141   string result = NormalizeFilePath(path);
142   // Ensure trailing slash.
143   if (!result.empty() && result.back() != '/')
144       result += '/';
145   return result;
146 }
147 
IsAbsolutePath(const string & path)148 bool IsAbsolutePath(const string& path) {
149   return llvm::sys::path::is_absolute(path);
150 }
151 
MakeAbsolutePath(const string & path)152 string MakeAbsolutePath(const string& path) {
153   llvm::SmallString<128> absolute_path(path);
154   std::error_code error = llvm::sys::fs::make_absolute(absolute_path);
155   CHECK_(!error);
156 
157   return absolute_path.str().str();
158 }
159 
MakeAbsolutePath(const string & base_path,const string & relative_path)160 string MakeAbsolutePath(const string& base_path, const string& relative_path) {
161   llvm::SmallString<128> absolute_path(base_path);
162   llvm::sys::path::append(absolute_path, relative_path);
163 
164   return absolute_path.str().str();
165 }
166 
GetParentPath(const string & path)167 string GetParentPath(const string& path) {
168   llvm::StringRef parent = llvm::sys::path::parent_path(path);
169   return parent.str();
170 }
171 
StripPathPrefix(string * path,const string & prefix_path)172 bool StripPathPrefix(string* path, const string& prefix_path) {
173   // Only makes sense if both are absolute or both are relative (to same dir).
174   CHECK_(IsAbsolutePath(*path) == IsAbsolutePath(prefix_path));
175   return StripLeft(path, prefix_path);
176 }
177 
178 // Converts a file-path, such as /usr/include/stdio.h, to a
179 // quoted include, such as <stdio.h>.
ConvertToQuotedInclude(const string & filepath,const string & includer_path)180 string ConvertToQuotedInclude(const string& filepath,
181                               const string& includer_path) {
182   // includer_path must be given as an absolute path.
183   CHECK_(includer_path.empty() || IsAbsolutePath(includer_path));
184 
185   if (filepath == "<built-in>")
186     return filepath;
187 
188   // Get path into same format as header search paths: Absolute and normalized.
189   string path = NormalizeFilePath(MakeAbsolutePath(filepath));
190 
191   // Case 1: Uses an explicit entry on the search path (-I) list.
192   const vector<HeaderSearchPath>& search_paths = HeaderSearchPaths();
193   // HeaderSearchPaths is sorted to be longest-first, so this
194   // loop will prefer the longest prefix: /usr/include/c++/4.4/foo
195   // will be mapped to <foo>, not <c++/4.4/foo>.
196   for (const HeaderSearchPath& entry : search_paths) {
197     // All header search paths have a trailing "/", so we'll get a perfect
198     // quoted include by just stripping the prefix.
199 
200     if (StripPathPrefix(&path, entry.path)) {
201       if (entry.path_type == HeaderSearchPath::kSystemPath)
202         return "<" + path + ">";
203       else
204         return "\"" + path + "\"";
205     }
206   }
207 
208   // Case 2:
209   // Uses the implicit "-I <basename current file>" entry on the search path.
210   if (!includer_path.empty())
211     StripPathPrefix(&path, NormalizeDirPath(includer_path));
212   return "\"" + path + "\"";
213 }
214 
IsQuotedInclude(const string & s)215 bool IsQuotedInclude(const string& s) {
216   if (s.size() < 2)
217     return false;
218   return ((StartsWith(s, "<") && EndsWith(s, ">")) ||
219           (StartsWith(s, "\"") && EndsWith(s, "\"")));
220 }
221 
222 // Returns whether this is a system (as opposed to user) include file,
223 // based on where it lives.
IsSystemIncludeFile(const string & filepath)224 bool IsSystemIncludeFile(const string& filepath) {
225   return ConvertToQuotedInclude(filepath)[0] == '<';
226 }
227 
228 }  // namespace include_what_you_use
229