1 | //===-- FileSpecList.cpp --------------------------------------------------===// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "lldb/Utility/FileSpecList.h" |
10 | #include "lldb/Target/Statistics.h" |
11 | #include "lldb/Target/Target.h" |
12 | #include "lldb/Utility/ConstString.h" |
13 | #include "lldb/Utility/LLDBLog.h" |
14 | #include "lldb/Utility/Log.h" |
15 | #include "lldb/Utility/RealpathPrefixes.h" |
16 | #include "lldb/Utility/Stream.h" |
17 | |
18 | #include <cstdint> |
19 | #include <utility> |
20 | |
21 | using namespace lldb_private; |
22 | |
23 | FileSpecList::FileSpecList() : m_files() {} |
24 | |
25 | FileSpecList::~FileSpecList() = default; |
26 | |
27 | // Append the "file_spec" to the end of the file spec list. |
28 | void FileSpecList::Append(const FileSpec &file_spec) { |
29 | m_files.push_back(x: file_spec); |
30 | } |
31 | |
32 | // Only append the "file_spec" if this list doesn't already contain it. |
33 | // |
34 | // Returns true if "file_spec" was added, false if this list already contained |
35 | // a copy of "file_spec". |
36 | bool FileSpecList::AppendIfUnique(const FileSpec &file_spec) { |
37 | collection::iterator end = m_files.end(); |
38 | if (find(first: m_files.begin(), last: end, val: file_spec) == end) { |
39 | m_files.push_back(x: file_spec); |
40 | return true; |
41 | } |
42 | return false; |
43 | } |
44 | |
45 | // FIXME: Replace this with a DenseSet at the call site. It is inefficient. |
46 | bool SupportFileList::AppendIfUnique(const FileSpec &file_spec) { |
47 | collection::iterator end = m_files.end(); |
48 | if (find_if(first: m_files.begin(), last: end, |
49 | pred: [&](const std::shared_ptr<SupportFile> &support_file) { |
50 | return support_file->GetSpecOnly() == file_spec; |
51 | }) == end) { |
52 | Append(file: file_spec); |
53 | return true; |
54 | } |
55 | return false; |
56 | } |
57 | |
58 | // Clears the file list. |
59 | void FileSpecList::Clear() { m_files.clear(); } |
60 | |
61 | // Dumps the file list to the supplied stream pointer "s". |
62 | void FileSpecList::Dump(Stream *s, const char *separator_cstr) const { |
63 | collection::const_iterator pos, end = m_files.end(); |
64 | for (pos = m_files.begin(); pos != end; ++pos) { |
65 | pos->Dump(s&: s->AsRawOstream()); |
66 | if (separator_cstr && ((pos + 1) != end)) |
67 | s->PutCString(cstr: separator_cstr); |
68 | } |
69 | } |
70 | |
71 | // Find the index of the file in the file spec list that matches "file_spec" |
72 | // starting "start_idx" entries into the file spec list. |
73 | // |
74 | // Returns the valid index of the file that matches "file_spec" if it is found, |
75 | // else std::numeric_limits<uint32_t>::max() is returned. |
76 | static size_t FindFileIndex(size_t start_idx, const FileSpec &file_spec, |
77 | bool full, size_t num_files, |
78 | std::function<const FileSpec &(size_t)> get_ith) { |
79 | // When looking for files, we will compare only the filename if the FILE_SPEC |
80 | // argument is empty |
81 | bool compare_filename_only = file_spec.GetDirectory().IsEmpty(); |
82 | |
83 | for (size_t idx = start_idx; idx < num_files; ++idx) { |
84 | const FileSpec &ith = get_ith(idx); |
85 | if (compare_filename_only) { |
86 | if (ConstString::Equals(lhs: ith.GetFilename(), rhs: file_spec.GetFilename(), |
87 | case_sensitive: file_spec.IsCaseSensitive() || |
88 | ith.IsCaseSensitive())) |
89 | return idx; |
90 | } else { |
91 | if (FileSpec::Equal(a: ith, b: file_spec, full)) |
92 | return idx; |
93 | } |
94 | } |
95 | |
96 | // We didn't find the file, return an invalid index |
97 | return UINT32_MAX; |
98 | } |
99 | |
100 | size_t FileSpecList::FindFileIndex(size_t start_idx, const FileSpec &file_spec, |
101 | bool full) const { |
102 | return ::FindFileIndex( |
103 | start_idx, file_spec, full, num_files: m_files.size(), |
104 | get_ith: [&](size_t idx) -> const FileSpec & { return m_files[idx]; }); |
105 | } |
106 | |
107 | size_t SupportFileList::FindFileIndex(size_t start_idx, |
108 | const FileSpec &file_spec, |
109 | bool full) const { |
110 | return ::FindFileIndex(start_idx, file_spec, full, num_files: m_files.size(), |
111 | get_ith: [&](size_t idx) -> const FileSpec & { |
112 | return m_files[idx]->GetSpecOnly(); |
113 | }); |
114 | } |
115 | |
116 | enum IsCompatibleResult { |
117 | kNoMatch = 0, |
118 | kOnlyFileMatch = 1, |
119 | kBothDirectoryAndFileMatch = 2, |
120 | }; |
121 | |
122 | IsCompatibleResult IsCompatible(const FileSpec &curr_file, |
123 | const FileSpec &file_spec) { |
124 | const bool file_spec_relative = file_spec.IsRelative(); |
125 | const bool file_spec_case_sensitive = file_spec.IsCaseSensitive(); |
126 | // When looking for files, we will compare only the filename if the directory |
127 | // argument is empty in file_spec |
128 | const bool full = !file_spec.GetDirectory().IsEmpty(); |
129 | |
130 | // Always start by matching the filename first |
131 | if (!curr_file.FileEquals(other: file_spec)) |
132 | return IsCompatibleResult::kNoMatch; |
133 | |
134 | // Only compare the full name if the we were asked to and if the current |
135 | // file entry has a directory. If it doesn't have a directory then we only |
136 | // compare the filename. |
137 | if (FileSpec::Equal(a: curr_file, b: file_spec, full)) { |
138 | return IsCompatibleResult::kBothDirectoryAndFileMatch; |
139 | } else if (curr_file.IsRelative() || file_spec_relative) { |
140 | llvm::StringRef curr_file_dir = curr_file.GetDirectory().GetStringRef(); |
141 | if (curr_file_dir.empty()) |
142 | // Basename match only for this file in the list |
143 | return IsCompatibleResult::kBothDirectoryAndFileMatch; |
144 | |
145 | // Check if we have a relative path in our file list, or if "file_spec" is |
146 | // relative, if so, check if either ends with the other. |
147 | llvm::StringRef file_spec_dir = file_spec.GetDirectory().GetStringRef(); |
148 | // We have a relative path in our file list, it matches if the |
149 | // specified path ends with this path, but we must ensure the full |
150 | // component matches (we don't want "foo/bar.cpp" to match "oo/bar.cpp"). |
151 | auto is_suffix = [](llvm::StringRef a, llvm::StringRef b, |
152 | bool case_sensitive) -> bool { |
153 | if (case_sensitive ? a.consume_back(Suffix: b) : a.consume_back_insensitive(Suffix: b)) |
154 | return a.empty() || a.ends_with(Suffix: "/" ); |
155 | return false; |
156 | }; |
157 | const bool case_sensitive = |
158 | file_spec_case_sensitive || curr_file.IsCaseSensitive(); |
159 | if (is_suffix(curr_file_dir, file_spec_dir, case_sensitive) || |
160 | is_suffix(file_spec_dir, curr_file_dir, case_sensitive)) |
161 | return IsCompatibleResult::kBothDirectoryAndFileMatch; |
162 | } |
163 | return IsCompatibleResult::kOnlyFileMatch; |
164 | } |
165 | |
166 | size_t SupportFileList::FindCompatibleIndex( |
167 | size_t start_idx, const FileSpec &file_spec, |
168 | RealpathPrefixes *realpath_prefixes) const { |
169 | const size_t num_files = m_files.size(); |
170 | if (start_idx >= num_files) |
171 | return UINT32_MAX; |
172 | |
173 | for (size_t idx = start_idx; idx < num_files; ++idx) { |
174 | const FileSpec &curr_file = m_files[idx]->GetSpecOnly(); |
175 | |
176 | IsCompatibleResult result = IsCompatible(curr_file, file_spec); |
177 | if (result == IsCompatibleResult::kBothDirectoryAndFileMatch) |
178 | return idx; |
179 | |
180 | if (realpath_prefixes && result == IsCompatibleResult::kOnlyFileMatch) { |
181 | if (std::optional<FileSpec> resolved_curr_file = |
182 | realpath_prefixes->ResolveSymlinks(file_spec: curr_file)) { |
183 | if (IsCompatible(curr_file: *resolved_curr_file, file_spec) == |
184 | IsCompatibleResult::kBothDirectoryAndFileMatch) { |
185 | // Stats and logging. |
186 | realpath_prefixes->IncreaseSourceRealpathCompatibleCount(); |
187 | Log *log = GetLog(mask: LLDBLog::Source); |
188 | LLDB_LOGF(log, |
189 | "Realpath'ed support file %s is compatible to input file" , |
190 | resolved_curr_file->GetPath().c_str()); |
191 | // We found a match |
192 | return idx; |
193 | } |
194 | } |
195 | } |
196 | } |
197 | |
198 | // We didn't find the file, return an invalid index |
199 | return UINT32_MAX; |
200 | } |
201 | // Returns the FileSpec object at index "idx". If "idx" is out of range, then |
202 | // an empty FileSpec object will be returned. |
203 | const FileSpec &FileSpecList::GetFileSpecAtIndex(size_t idx) const { |
204 | if (idx < m_files.size()) |
205 | return m_files[idx]; |
206 | static FileSpec g_empty_file_spec; |
207 | return g_empty_file_spec; |
208 | } |
209 | |
210 | const FileSpec &SupportFileList::GetFileSpecAtIndex(size_t idx) const { |
211 | if (idx < m_files.size()) |
212 | return m_files[idx]->Materialize(); |
213 | static FileSpec g_empty_file_spec; |
214 | return g_empty_file_spec; |
215 | } |
216 | |
217 | std::shared_ptr<SupportFile> |
218 | SupportFileList::GetSupportFileAtIndex(size_t idx) const { |
219 | if (idx < m_files.size()) |
220 | return m_files[idx]; |
221 | return {}; |
222 | } |
223 | |
224 | // Return the size in bytes that this object takes in memory. This returns the |
225 | // size in bytes of this object's member variables and any FileSpec objects its |
226 | // member variables contain, the result doesn't not include the string values |
227 | // for the directories any filenames as those are in shared string pools. |
228 | size_t FileSpecList::MemorySize() const { |
229 | size_t mem_size = sizeof(FileSpecList); |
230 | collection::const_iterator pos, end = m_files.end(); |
231 | for (pos = m_files.begin(); pos != end; ++pos) { |
232 | mem_size += pos->MemorySize(); |
233 | } |
234 | |
235 | return mem_size; |
236 | } |
237 | |
238 | // Return the number of files in the file spec list. |
239 | size_t FileSpecList::GetSize() const { return m_files.size(); } |
240 | |