Line data Source code
1 : /*
2 : * Cppcheck - A tool for static C/C++ code analysis
3 : * Copyright (C) 2007-2024 Cppcheck team.
4 : *
5 : * This program is free software: you can redistribute it and/or modify
6 : * it under the terms of the GNU General Public License as published by
7 : * the Free Software Foundation, either version 3 of the License, or
8 : * (at your option) any later version.
9 : *
10 : * This program is distributed in the hope that it will be useful,
11 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 : * GNU General Public License for more details.
14 : *
15 : * You should have received a copy of the GNU General Public License
16 : * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 : */
18 :
19 : #if defined(__GNUC__) && (defined(_WIN32) || defined(__CYGWIN__))
20 : #undef __STRICT_ANSI__
21 : #endif
22 :
23 : #include "path.h"
24 : #include "utils.h"
25 :
26 : #include <algorithm>
27 : #include <cstdlib>
28 : #include <sys/stat.h>
29 : #include <unordered_set>
30 : #include <utility>
31 :
32 : #include <simplecpp.h>
33 :
34 : #ifndef _WIN32
35 : #include <sys/types.h>
36 : #include <unistd.h>
37 : #else
38 : #include <direct.h>
39 : #include <windows.h>
40 : #endif
41 : #if defined(__CYGWIN__)
42 : #include <strings.h>
43 : #endif
44 : #if defined(__APPLE__)
45 : #include <mach-o/dyld.h>
46 : #endif
47 :
48 :
49 : /** Is the filesystem case insensitive? */
50 59423 : static constexpr bool caseInsensitiveFilesystem()
51 : {
52 : #if defined(_WIN32) || (defined(__APPLE__) && defined(__MACH__))
53 : // Windows is case insensitive
54 : // MacOS is case insensitive by default (also supports case sensitivity)
55 : return true;
56 : #else
57 : // TODO: Non-windows filesystems might be case insensitive
58 59423 : return false;
59 : #endif
60 : }
61 :
62 21717 : std::string Path::toNativeSeparators(std::string path)
63 : {
64 : #if defined(_WIN32)
65 : constexpr char separ = '/';
66 : constexpr char native = '\\';
67 : #else
68 21717 : constexpr char separ = '\\';
69 21717 : constexpr char native = '/';
70 : #endif
71 21717 : std::replace(path.begin(), path.end(), separ, native);
72 21715 : return path;
73 : }
74 :
75 1646 : std::string Path::fromNativeSeparators(std::string path)
76 : {
77 1646 : constexpr char nonnative = '\\';
78 1646 : constexpr char newsepar = '/';
79 1646 : std::replace(path.begin(), path.end(), nonnative, newsepar);
80 1646 : return path;
81 : }
82 :
83 28293 : std::string Path::simplifyPath(std::string originalPath)
84 : {
85 28293 : return simplecpp::simplifyPath(std::move(originalPath));
86 : }
87 :
88 839 : std::string Path::getPathFromFilename(const std::string &filename)
89 : {
90 839 : const std::size_t pos = filename.find_last_of("\\/");
91 :
92 839 : if (pos != std::string::npos)
93 812 : return filename.substr(0, 1 + pos);
94 :
95 27 : return "";
96 : }
97 :
98 7 : bool Path::sameFileName(const std::string &fname1, const std::string &fname2)
99 : {
100 7 : return caseInsensitiveFilesystem() ? (caseInsensitiveStringCompare(fname1, fname2) == 0) : (fname1 == fname2);
101 : }
102 :
103 205 : std::string Path::removeQuotationMarks(std::string path)
104 : {
105 205 : path.erase(std::remove(path.begin(), path.end(), '\"'), path.end());
106 205 : return path;
107 : }
108 :
109 163945 : std::string Path::getFilenameExtension(const std::string &path, bool lowercase)
110 : {
111 163945 : const std::string::size_type dotLocation = path.find_last_of('.');
112 163946 : if (dotLocation == std::string::npos)
113 177 : return "";
114 :
115 327529 : std::string extension = path.substr(dotLocation);
116 163768 : if (lowercase || caseInsensitiveFilesystem()) {
117 : // on a case insensitive filesystem the case doesn't matter so
118 : // let's return the extension in lowercase
119 129363 : strTolower(extension);
120 : }
121 163761 : return extension;
122 : }
123 :
124 129486 : std::string Path::getFilenameExtensionInLowerCase(const std::string &path)
125 : {
126 129486 : return getFilenameExtension(path, true);
127 : }
128 :
129 100 : std::string Path::getCurrentPath()
130 : {
131 : char currentPath[4096];
132 :
133 : #ifndef _WIN32
134 100 : if (getcwd(currentPath, 4096) != nullptr)
135 : #else
136 : if (_getcwd(currentPath, 4096) != nullptr)
137 : #endif
138 100 : return std::string(currentPath);
139 :
140 0 : return "";
141 : }
142 :
143 884 : std::string Path::getCurrentExecutablePath(const char* fallback)
144 : {
145 884 : char buf[4096] = {};
146 884 : bool success{};
147 : #ifdef _WIN32
148 : success = (GetModuleFileNameA(nullptr, buf, sizeof(buf)) < sizeof(buf));
149 : #elif defined(__APPLE__)
150 : uint32_t size = sizeof(buf);
151 : success = (_NSGetExecutablePath(buf, &size) == 0);
152 : #else
153 884 : const char* procPath =
154 : #ifdef __SVR4 // Solaris
155 : "/proc/self/path/a.out";
156 : #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__DragonFly__)
157 : "/proc/curproc/file";
158 : #else // Linux
159 : "/proc/self/exe";
160 : #endif
161 884 : success = (readlink(procPath, buf, sizeof(buf)) != -1);
162 : #endif
163 1768 : return success ? std::string(buf) : std::string(fallback);
164 : }
165 :
166 81 : bool Path::isAbsolute(const std::string& path)
167 : {
168 81 : const std::string& nativePath = toNativeSeparators(path);
169 :
170 : #ifdef _WIN32
171 : if (path.length() < 2)
172 : return false;
173 :
174 : // On Windows, 'C:\foo\bar' is an absolute path, while 'C:foo\bar' is not
175 : return startsWith(nativePath, "\\\\") || (std::isalpha(nativePath[0]) != 0 && nativePath.compare(1, 2, ":\\") == 0);
176 : #else
177 162 : return !nativePath.empty() && nativePath[0] == '/';
178 : #endif
179 : }
180 :
181 31 : std::string Path::getRelativePath(const std::string& absolutePath, const std::vector<std::string>& basePaths)
182 : {
183 44 : for (const std::string &bp : basePaths) {
184 19 : if (absolutePath == bp || bp.empty()) // Seems to be a file, or path is empty
185 6 : continue;
186 :
187 13 : if (absolutePath.compare(0, bp.length(), bp) != 0)
188 6 : continue;
189 :
190 7 : if (endsWith(bp,'/'))
191 8 : return absolutePath.substr(bp.length());
192 5 : if (absolutePath.size() > bp.size() && absolutePath[bp.length()] == '/')
193 4 : return absolutePath.substr(bp.length() + 1);
194 : }
195 25 : return absolutePath;
196 : }
197 :
198 : static const std::unordered_set<std::string> cpp_src_exts = {
199 : ".cpp", ".cxx", ".cc", ".c++", ".tpp", ".txx", ".ipp", ".ixx"
200 : };
201 :
202 : static const std::unordered_set<std::string> c_src_exts = {
203 : ".c", ".cl"
204 : };
205 :
206 : static const std::unordered_set<std::string> header_exts = {
207 : ".h", ".hpp", ".h++", ".hxx", ".hh"
208 : };
209 :
210 9 : bool Path::isC(const std::string &path)
211 : {
212 : // In unix, ".C" is considered C++ file
213 9 : const std::string extension = getFilenameExtension(path);
214 15 : return extension == ".c" ||
215 24 : extension == ".cl";
216 : }
217 :
218 16 : bool Path::isCPP(const std::string &path)
219 : {
220 16 : const std::string extension = getFilenameExtensionInLowerCase(path);
221 11 : return extension == ".cpp" ||
222 21 : extension == ".cxx" ||
223 19 : extension == ".cc" ||
224 17 : extension == ".c++" ||
225 15 : extension == ".hpp" ||
226 14 : extension == ".hxx" ||
227 14 : extension == ".hh" ||
228 13 : extension == ".tpp" ||
229 11 : extension == ".txx" ||
230 9 : extension == ".ipp" ||
231 31 : extension == ".ixx" ||
232 35 : getFilenameExtension(path) == ".C"; // In unix, ".C" is considered C++ file
233 : }
234 :
235 2719 : bool Path::acceptFile(const std::string &path, const std::set<std::string> &extra)
236 : {
237 2719 : bool header = false;
238 2719 : return (identify(path, &header) != Standards::Language::None && !header) || extra.find(getFilenameExtension(path)) != extra.end();
239 : }
240 :
241 : // cppcheck-suppress unusedFunction
242 10 : bool Path::isHeader(const std::string &path)
243 : {
244 20 : const std::string extension = getFilenameExtensionInLowerCase(path);
245 20 : return startsWith(extension, ".h");
246 : }
247 :
248 31449 : Standards::Language Path::identify(const std::string &path, bool *header)
249 : {
250 : // cppcheck-suppress uninitvar - TODO: FP
251 31449 : if (header)
252 2736 : *header = false;
253 :
254 62898 : std::string ext = getFilenameExtension(path);
255 31449 : if (ext == ".C")
256 2 : return Standards::Language::CPP;
257 31447 : if (c_src_exts.find(ext) != c_src_exts.end())
258 6432 : return Standards::Language::C;
259 : // cppcheck-suppress knownConditionTrueFalse - TODO: FP
260 25012 : if (!caseInsensitiveFilesystem())
261 25012 : strTolower(ext);
262 25015 : if (ext == ".h") {
263 162 : if (header)
264 157 : *header = true;
265 162 : return Standards::Language::C; // treat as C for now
266 : }
267 24853 : if (cpp_src_exts.find(ext) != cpp_src_exts.end())
268 22593 : return Standards::Language::CPP;
269 2260 : if (header_exts.find(ext) != header_exts.end()) {
270 12 : if (header)
271 11 : *header = true;
272 12 : return Standards::Language::CPP;
273 : }
274 2248 : return Standards::Language::None;
275 : }
276 :
277 10 : bool Path::isHeader2(const std::string &path)
278 : {
279 : bool header;
280 10 : (void)Path::identify(path, &header);
281 10 : return header;
282 : }
283 :
284 600 : std::string Path::getAbsoluteFilePath(const std::string& filePath)
285 : {
286 600 : std::string absolute_path;
287 : #ifdef _WIN32
288 : char absolute[_MAX_PATH];
289 : if (_fullpath(absolute, filePath.c_str(), _MAX_PATH))
290 : absolute_path = absolute;
291 : #elif defined(__linux__) || defined(__sun) || defined(__hpux) || defined(__GNUC__) || defined(__CPPCHECK__)
292 600 : char * absolute = realpath(filePath.c_str(), nullptr);
293 600 : if (absolute)
294 600 : absolute_path = absolute;
295 600 : free(absolute);
296 : #else
297 : #error Platform absolute path function needed
298 : #endif
299 600 : return absolute_path;
300 : }
301 :
302 1210823 : std::string Path::stripDirectoryPart(const std::string &file)
303 : {
304 : #if defined(_WIN32) && !defined(__MINGW32__)
305 : constexpr char native = '\\';
306 : #else
307 1210823 : constexpr char native = '/';
308 : #endif
309 :
310 1210823 : const std::string::size_type p = file.rfind(native);
311 1210823 : if (p != std::string::npos) {
312 1210465 : return file.substr(p + 1);
313 : }
314 358 : return file;
315 : }
316 :
317 : #ifdef _WIN32
318 : using mode_t = unsigned short;
319 : #endif
320 :
321 2674 : static mode_t file_type(const std::string &path)
322 : {
323 : struct stat file_stat;
324 2674 : if (stat(path.c_str(), &file_stat) == -1)
325 2638 : return 0;
326 36 : return file_stat.st_mode & S_IFMT;
327 : }
328 :
329 2640 : bool Path::isFile(const std::string &path)
330 : {
331 2640 : return file_type(path) == S_IFREG;
332 : }
333 :
334 34 : bool Path::isDirectory(const std::string &path)
335 : {
336 34 : return file_type(path) == S_IFDIR;
337 : }
338 :
339 2432 : std::string Path::join(const std::string& path1, const std::string& path2) {
340 2432 : if (path1.empty() || path2.empty())
341 2399 : return path1 + path2;
342 33 : if (path2.front() == '/')
343 1 : return path2;
344 64 : return ((path1.back() == '/') ? path1 : (path1 + "/")) + path2;
345 : }
|