Cppcheck
filelister.cpp
Go to the documentation of this file.
1 /*
2  * Cppcheck - A tool for static C/C++ code analysis
3  * Copyright (C) 2007-2023 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 #include "filelister.h"
20 
21 #include "filesettings.h"
22 #include "path.h"
23 #include "pathmatch.h"
24 #include "utils.h"
25 
26 #include <cstddef>
27 #include <cstring>
28 #include <iterator>
29 #include <memory>
30 
31 #ifdef _WIN32
32 
33 ///////////////////////////////////////////////////////////////////////////////
34 ////// This code is WIN32 systems /////////////////////////////////////////////
35 ///////////////////////////////////////////////////////////////////////////////
36 
37 #include <windows.h>
38 #include <shlwapi.h>
39 
40 // Here is the catch: cppcheck core is Ansi code (using char type).
41 // When compiling Unicode targets WinAPI automatically uses *W Unicode versions
42 // of called functions. Thus, we explicitly call *A versions of the functions.
43 
44 static std::string addFiles2(std::list<FileWithDetails>&files, const std::string &path, const std::set<std::string> &extra, bool recursive, const PathMatch& ignored)
45 {
46  const std::string cleanedPath = Path::toNativeSeparators(path);
47 
48  // basedir is the base directory which is used to form pathnames.
49  // It always has a trailing backslash available for concatenation.
50  std::string basedir;
51 
52  // searchPattern is the search string passed into FindFirst and FindNext.
53  std::string searchPattern = cleanedPath;
54 
55  // The user wants to check all files in a dir
56  const bool checkAllFilesInDir = Path::isDirectory(cleanedPath);
57 
58  if (checkAllFilesInDir) {
59  const char c = cleanedPath.back();
60  switch (c) {
61  case '\\':
62  searchPattern += '*';
63  basedir = cleanedPath;
64  break;
65  case '*':
66  basedir = cleanedPath.substr(0, cleanedPath.length() - 1);
67  break;
68  default:
69  searchPattern += "\\*";
70  if (cleanedPath != ".")
71  basedir = cleanedPath + '\\';
72  }
73  } else {
74  const std::string::size_type pos = cleanedPath.find_last_of('\\');
75  if (std::string::npos != pos) {
76  basedir = cleanedPath.substr(0, pos + 1);
77  }
78  }
79 
80  WIN32_FIND_DATAA ffd;
81  HANDLE hFind = FindFirstFileA(searchPattern.c_str(), &ffd);
82  if (INVALID_HANDLE_VALUE == hFind) {
83  const DWORD err = GetLastError();
84  if (err == ERROR_FILE_NOT_FOUND) {
85  // no files matched
86  return "";
87  }
88  return "finding files failed. Search pattern: '" + searchPattern + "'. (error: " + std::to_string(err) + ")";
89  }
90  std::unique_ptr<void, decltype(&FindClose)> hFind_deleter(hFind, FindClose);
91 
92  do {
93  if (ffd.cFileName[0] != '.' && ffd.cFileName[0] != '\0')
94  {
95  const char* ansiFfd = ffd.cFileName;
96  if (std::strchr(ansiFfd,'?')) {
97  ansiFfd = ffd.cAlternateFileName;
98  }
99 
100  const std::string fname(basedir + ansiFfd);
101 
102  if ((ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) {
103  // File
104  if ((!checkAllFilesInDir || Path::acceptFile(fname, extra)) && !ignored.match(fname)) {
105  std::string nativename = Path::fromNativeSeparators(fname);
106 
107  // Limitation: file sizes are assumed to fit in a 'size_t'
108 #ifdef _WIN64
109  const std::size_t filesize = (static_cast<std::size_t>(ffd.nFileSizeHigh) << 32) | ffd.nFileSizeLow;
110 #else
111  const std::size_t filesize = ffd.nFileSizeLow;
112 
113 #endif
114  files.emplace_back(std::move(nativename), filesize);
115  }
116  } else {
117  // Directory
118  if (recursive) {
119  if (!ignored.match(fname)) {
120  std::list<FileWithDetails> filesSorted;
121 
122  std::string err = addFiles2(filesSorted, fname, extra, recursive, ignored);
123  if (!err.empty())
124  return err;
125 
126  // files inside directories need to be sorted as the filesystem doesn't provide a stable order
127  filesSorted.sort([](const FileWithDetails& a, const FileWithDetails& b) {
128  return a.path() < b.path();
129  });
130 
131  files.insert(files.end(), std::make_move_iterator(filesSorted.begin()), std::make_move_iterator(filesSorted.end()));
132  }
133  }
134  }
135  }
136 
137  if (!FindNextFileA(hFind, &ffd)) {
138  const DWORD err = GetLastError();
139  // no more files matched
140  if (err != ERROR_NO_MORE_FILES)
141  return "failed to get next file (error: " + std::to_string(err) + ")";
142  break;
143  }
144  } while (true);
145 
146  return "";
147 }
148 
149 std::string FileLister::addFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, bool recursive, const PathMatch& ignored)
150 {
151  if (path.empty())
152  return "no path specified";
153 
154  std::list<FileWithDetails> filesSorted;
155 
156  std::string err = addFiles2(filesSorted, path, extra, recursive, ignored);
157 
158  // files need to be sorted as the filesystems dosn't provide a stable order
159  filesSorted.sort([](const FileWithDetails& a, const FileWithDetails& b) {
160  return a.path() < b.path();
161  });
162  files.insert(files.end(), std::make_move_iterator(filesSorted.begin()), std::make_move_iterator(filesSorted.end()));
163 
164  return err;
165 }
166 
167 #else
168 
169 ///////////////////////////////////////////////////////////////////////////////
170 ////// This code is POSIX-style systems ///////////////////////////////////////
171 ///////////////////////////////////////////////////////////////////////////////
172 
173 #if defined(__CYGWIN__)
174 #undef __STRICT_ANSI__
175 #endif
176 
177 #include <dirent.h>
178 #include <sys/stat.h>
179 #include <cerrno>
180 
181 static std::string addFiles2(std::list<FileWithDetails> &files,
182  const std::string &path,
183  const std::set<std::string> &extra,
184  bool recursive,
185  const PathMatch& ignored
186  )
187 {
188  if (ignored.match(path))
189  return "";
190 
191  struct stat file_stat;
192  if (stat(path.c_str(), &file_stat) == -1)
193  return ""; // TODO: return error?
194  if ((file_stat.st_mode & S_IFMT) != S_IFDIR)
195  {
196  files.emplace_back(path, file_stat.st_size);
197  return "";
198  }
199 
200  // process directory entry
201 
202  DIR * dir = opendir(path.c_str());
203  if (!dir) {
204  const int err = errno;
205  return "could not open directory '" + path + "' (errno: " + std::to_string(err) + ")";
206  }
207  std::unique_ptr<DIR, decltype(&closedir)> dir_deleter(dir, closedir);
208 
209  std::string new_path = path;
210  new_path += '/';
211 
212  while (const dirent* dir_result = readdir(dir)) {
213  if ((std::strcmp(dir_result->d_name, ".") == 0) ||
214  (std::strcmp(dir_result->d_name, "..") == 0))
215  continue;
216 
217  new_path.erase(path.length() + 1);
218  new_path += dir_result->d_name;
219 
220 #if defined(_DIRENT_HAVE_D_TYPE) || defined(_BSD_SOURCE)
221  const bool path_is_directory = (dir_result->d_type == DT_DIR || (dir_result->d_type == DT_UNKNOWN && Path::isDirectory(new_path)));
222 #else
223  const bool path_is_directory = Path::isDirectory(new_path);
224 #endif
225  if (path_is_directory) {
226  if (recursive && !ignored.match(new_path)) {
227  std::string err = addFiles2(files, new_path, extra, recursive, ignored);
228  if (!err.empty()) {
229  return err;
230  }
231  }
232  } else {
233  if (Path::acceptFile(new_path, extra) && !ignored.match(new_path)) {
234  if (stat(new_path.c_str(), &file_stat) == -1) {
235  const int err = errno;
236  return "could not stat file '" + new_path + "' (errno: " + std::to_string(err) + ")";
237  }
238  files.emplace_back(new_path, file_stat.st_size);
239  }
240  }
241  }
242 
243  return "";
244 }
245 
246 std::string FileLister::addFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, bool recursive, const PathMatch& ignored)
247 {
248  if (path.empty())
249  return "no path specified";
250 
251  std::string corrected_path = path;
252  if (endsWith(corrected_path, '/'))
253  corrected_path.erase(corrected_path.end() - 1);
254 
255  std::list<FileWithDetails> filesSorted;
256 
257  std::string err = addFiles2(filesSorted, corrected_path, extra, recursive, ignored);
258 
259  // files need to be sorted as the filesystems dosn't provide a stable order
260  filesSorted.sort([](const FileWithDetails& a, const FileWithDetails& b) {
261  return a.path() < b.path();
262  });
263  files.insert(files.end(), std::make_move_iterator(filesSorted.begin()), std::make_move_iterator(filesSorted.end()));
264 
265  return err;
266 }
267 
268 #endif
269 
270 std::string FileLister::recursiveAddFiles(std::list<FileWithDetails> &files, const std::string &path, const std::set<std::string> &extra, const PathMatch& ignored)
271 {
272  return addFiles(files, path, extra, true, ignored);
273 }
static std::string recursiveAddFiles(std::list< FileWithDetails > &files, const std::string &path, const PathMatch &ignored)
Recursively add source files to a map.
Definition: filelister.h:47
static std::string addFiles(std::list< FileWithDetails > &files, const std::string &path, const std::set< std::string > &extra, bool recursive, const PathMatch &ignored)
(Recursively) add source files to a map.
Definition: filelister.cpp:246
const std::string & path() const
Definition: filesettings.h:42
Simple path matching for ignoring paths in CLI.
Definition: pathmatch.h:33
bool match(const std::string &path) const
Match path against list of masks.
Definition: pathmatch.cpp:36
static std::string fromNativeSeparators(std::string path)
Convert path to use internal path separators.
Definition: path.cpp:75
static std::string toNativeSeparators(std::string path)
Convert path to use native separators.
Definition: path.cpp:62
static bool acceptFile(const std::string &filename)
Check if the file extension indicates that it's a C/C++ source file.
Definition: path.h:142
static bool isDirectory(const std::string &path)
Checks if a given path is a directory.
Definition: path.cpp:334
static std::string addFiles2(std::list< FileWithDetails > &files, const std::string &path, const std::set< std::string > &extra, bool recursive, const PathMatch &ignored)
Definition: filelister.cpp:181
bool endsWith(const std::string &str, char c)
Definition: utils.h:110