1 | //===-- SourceManager.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/Core/SourceManager.h" |
10 | |
11 | #include "lldb/Core/Address.h" |
12 | #include "lldb/Core/AddressRange.h" |
13 | #include "lldb/Core/Debugger.h" |
14 | #include "lldb/Core/FormatEntity.h" |
15 | #include "lldb/Core/Highlighter.h" |
16 | #include "lldb/Core/Module.h" |
17 | #include "lldb/Core/ModuleList.h" |
18 | #include "lldb/Host/FileSystem.h" |
19 | #include "lldb/Symbol/CompileUnit.h" |
20 | #include "lldb/Symbol/Function.h" |
21 | #include "lldb/Symbol/LineEntry.h" |
22 | #include "lldb/Symbol/SymbolContext.h" |
23 | #include "lldb/Target/PathMappingList.h" |
24 | #include "lldb/Target/Process.h" |
25 | #include "lldb/Target/Target.h" |
26 | #include "lldb/Utility/AnsiTerminal.h" |
27 | #include "lldb/Utility/ConstString.h" |
28 | #include "lldb/Utility/DataBuffer.h" |
29 | #include "lldb/Utility/LLDBLog.h" |
30 | #include "lldb/Utility/Log.h" |
31 | #include "lldb/Utility/RegularExpression.h" |
32 | #include "lldb/Utility/Stream.h" |
33 | #include "lldb/lldb-enumerations.h" |
34 | |
35 | #include "llvm/ADT/Twine.h" |
36 | |
37 | #include <memory> |
38 | #include <optional> |
39 | #include <utility> |
40 | |
41 | #include <cassert> |
42 | #include <cstdio> |
43 | |
44 | namespace lldb_private { |
45 | class ExecutionContext; |
46 | } |
47 | namespace lldb_private { |
48 | class ValueObject; |
49 | } |
50 | |
51 | using namespace lldb; |
52 | using namespace lldb_private; |
53 | |
54 | static inline bool is_newline_char(char ch) { return ch == '\n' || ch == '\r'; } |
55 | |
56 | static void resolve_tilde(FileSpec &file_spec) { |
57 | if (!FileSystem::Instance().Exists(file_spec) && |
58 | file_spec.GetDirectory() && |
59 | file_spec.GetDirectory().GetCString()[0] == '~') { |
60 | FileSystem::Instance().Resolve(file_spec); |
61 | } |
62 | } |
63 | |
64 | // SourceManager constructor |
65 | SourceManager::SourceManager(const TargetSP &target_sp) |
66 | : m_last_line(0), m_last_count(0), m_default_set(false), |
67 | m_target_wp(target_sp), |
68 | m_debugger_wp(target_sp->GetDebugger().shared_from_this()) {} |
69 | |
70 | SourceManager::SourceManager(const DebuggerSP &debugger_sp) |
71 | : m_last_line(0), m_last_count(0), m_default_set(false), m_target_wp(), |
72 | m_debugger_wp(debugger_sp) {} |
73 | |
74 | // Destructor |
75 | SourceManager::~SourceManager() = default; |
76 | |
77 | SourceManager::FileSP SourceManager::GetFile(const FileSpec &file_spec) { |
78 | if (!file_spec) |
79 | return {}; |
80 | |
81 | Log *log = GetLog(mask: LLDBLog::Source); |
82 | |
83 | DebuggerSP debugger_sp(m_debugger_wp.lock()); |
84 | TargetSP target_sp(m_target_wp.lock()); |
85 | |
86 | if (!debugger_sp || !debugger_sp->GetUseSourceCache()) { |
87 | LLDB_LOG(log, "Source file caching disabled: creating new source file: {0}" , |
88 | file_spec); |
89 | if (target_sp) |
90 | return std::make_shared<File>(args: file_spec, args&: target_sp); |
91 | return std::make_shared<File>(args: file_spec, args&: debugger_sp); |
92 | } |
93 | |
94 | ProcessSP process_sp = target_sp ? target_sp->GetProcessSP() : ProcessSP(); |
95 | |
96 | // Check the process source cache first. This is the fast path which avoids |
97 | // touching the file system unless the path remapping has changed. |
98 | if (process_sp) { |
99 | if (FileSP file_sp = |
100 | process_sp->GetSourceFileCache().FindSourceFile(file_spec)) { |
101 | LLDB_LOG(log, "Found source file in the process cache: {0}" , file_spec); |
102 | if (file_sp->PathRemappingIsStale()) { |
103 | LLDB_LOG(log, "Path remapping is stale: removing file from caches: {0}" , |
104 | file_spec); |
105 | |
106 | // Remove the file from the debugger and process cache. Otherwise we'll |
107 | // hit the same issue again below when querying the debugger cache. |
108 | debugger_sp->GetSourceFileCache().RemoveSourceFile(file_sp); |
109 | process_sp->GetSourceFileCache().RemoveSourceFile(file_sp); |
110 | |
111 | file_sp.reset(); |
112 | } else { |
113 | return file_sp; |
114 | } |
115 | } |
116 | } |
117 | |
118 | // Cache miss in the process cache. Check the debugger source cache. |
119 | FileSP file_sp = debugger_sp->GetSourceFileCache().FindSourceFile(file_spec); |
120 | |
121 | // We found the file in the debugger cache. Check if anything invalidated our |
122 | // cache result. |
123 | if (file_sp) |
124 | LLDB_LOG(log, "Found source file in the debugger cache: {0}" , file_spec); |
125 | |
126 | // Check if the path remapping has changed. |
127 | if (file_sp && file_sp->PathRemappingIsStale()) { |
128 | LLDB_LOG(log, "Path remapping is stale: {0}" , file_spec); |
129 | file_sp.reset(); |
130 | } |
131 | |
132 | // Check if the modification time has changed. |
133 | if (file_sp && file_sp->ModificationTimeIsStale()) { |
134 | LLDB_LOG(log, "Modification time is stale: {0}" , file_spec); |
135 | file_sp.reset(); |
136 | } |
137 | |
138 | // Check if the file exists on disk. |
139 | if (file_sp && !FileSystem::Instance().Exists(file_spec: file_sp->GetFileSpec())) { |
140 | LLDB_LOG(log, "File doesn't exist on disk: {0}" , file_spec); |
141 | file_sp.reset(); |
142 | } |
143 | |
144 | // If at this point we don't have a valid file, it means we either didn't find |
145 | // it in the debugger cache or something caused it to be invalidated. |
146 | if (!file_sp) { |
147 | LLDB_LOG(log, "Creating and caching new source file: {0}" , file_spec); |
148 | |
149 | // (Re)create the file. |
150 | if (target_sp) |
151 | file_sp = std::make_shared<File>(args: file_spec, args&: target_sp); |
152 | else |
153 | file_sp = std::make_shared<File>(args: file_spec, args&: debugger_sp); |
154 | |
155 | // Add the file to the debugger and process cache. If the file was |
156 | // invalidated, this will overwrite it. |
157 | debugger_sp->GetSourceFileCache().AddSourceFile(file_spec, file_sp); |
158 | if (process_sp) |
159 | process_sp->GetSourceFileCache().AddSourceFile(file_spec, file_sp); |
160 | } |
161 | |
162 | return file_sp; |
163 | } |
164 | |
165 | static bool should_highlight_source(DebuggerSP debugger_sp) { |
166 | if (!debugger_sp) |
167 | return false; |
168 | |
169 | // We don't use ANSI stop column formatting if the debugger doesn't think it |
170 | // should be using color. |
171 | if (!debugger_sp->GetUseColor()) |
172 | return false; |
173 | |
174 | return debugger_sp->GetHighlightSource(); |
175 | } |
176 | |
177 | static bool should_show_stop_column_with_ansi(DebuggerSP debugger_sp) { |
178 | // We don't use ANSI stop column formatting if we can't lookup values from |
179 | // the debugger. |
180 | if (!debugger_sp) |
181 | return false; |
182 | |
183 | // We don't use ANSI stop column formatting if the debugger doesn't think it |
184 | // should be using color. |
185 | if (!debugger_sp->GetUseColor()) |
186 | return false; |
187 | |
188 | // We only use ANSI stop column formatting if we're either supposed to show |
189 | // ANSI where available (which we know we have when we get to this point), or |
190 | // if we're only supposed to use ANSI. |
191 | const auto value = debugger_sp->GetStopShowColumn(); |
192 | return ((value == eStopShowColumnAnsiOrCaret) || |
193 | (value == eStopShowColumnAnsi)); |
194 | } |
195 | |
196 | static bool should_show_stop_column_with_caret(DebuggerSP debugger_sp) { |
197 | // We don't use text-based stop column formatting if we can't lookup values |
198 | // from the debugger. |
199 | if (!debugger_sp) |
200 | return false; |
201 | |
202 | // If we're asked to show the first available of ANSI or caret, then we do |
203 | // show the caret when ANSI is not available. |
204 | const auto value = debugger_sp->GetStopShowColumn(); |
205 | if ((value == eStopShowColumnAnsiOrCaret) && !debugger_sp->GetUseColor()) |
206 | return true; |
207 | |
208 | // The only other time we use caret is if we're explicitly asked to show |
209 | // caret. |
210 | return value == eStopShowColumnCaret; |
211 | } |
212 | |
213 | static bool should_show_stop_line_with_ansi(DebuggerSP debugger_sp) { |
214 | return debugger_sp && debugger_sp->GetUseColor(); |
215 | } |
216 | |
217 | size_t SourceManager::DisplaySourceLinesWithLineNumbersUsingLastFile( |
218 | uint32_t start_line, uint32_t count, uint32_t curr_line, uint32_t column, |
219 | const char *current_line_cstr, Stream *s, |
220 | const SymbolContextList *bp_locs) { |
221 | if (count == 0) |
222 | return 0; |
223 | |
224 | Stream::ByteDelta delta(*s); |
225 | |
226 | if (start_line == 0) { |
227 | if (m_last_line != 0 && m_last_line != UINT32_MAX) |
228 | start_line = m_last_line + m_last_count; |
229 | else |
230 | start_line = 1; |
231 | } |
232 | |
233 | if (!m_default_set) { |
234 | FileSpec tmp_spec; |
235 | uint32_t tmp_line; |
236 | GetDefaultFileAndLine(file_spec&: tmp_spec, line&: tmp_line); |
237 | } |
238 | |
239 | m_last_line = start_line; |
240 | m_last_count = count; |
241 | |
242 | if (FileSP last_file_sp = GetLastFile()) { |
243 | const uint32_t end_line = start_line + count - 1; |
244 | for (uint32_t line = start_line; line <= end_line; ++line) { |
245 | if (!last_file_sp->LineIsValid(line)) { |
246 | m_last_line = UINT32_MAX; |
247 | break; |
248 | } |
249 | |
250 | std::string prefix; |
251 | if (bp_locs) { |
252 | uint32_t bp_count = bp_locs->NumLineEntriesWithLine(line); |
253 | |
254 | if (bp_count > 0) |
255 | prefix = llvm::formatv(Fmt: "[{0}]" , Vals&: bp_count); |
256 | else |
257 | prefix = " " ; |
258 | } |
259 | |
260 | char buffer[3]; |
261 | snprintf(s: buffer, maxlen: sizeof(buffer), format: "%2.2s" , |
262 | (line == curr_line) ? current_line_cstr : "" ); |
263 | std::string current_line_highlight(buffer); |
264 | |
265 | auto debugger_sp = m_debugger_wp.lock(); |
266 | if (should_show_stop_line_with_ansi(debugger_sp)) { |
267 | current_line_highlight = ansi::FormatAnsiTerminalCodes( |
268 | format: (debugger_sp->GetStopShowLineMarkerAnsiPrefix() + |
269 | current_line_highlight + |
270 | debugger_sp->GetStopShowLineMarkerAnsiSuffix()) |
271 | .str()); |
272 | } |
273 | |
274 | s->Printf(format: "%s%s %-4u\t" , prefix.c_str(), current_line_highlight.c_str(), |
275 | line); |
276 | |
277 | // So far we treated column 0 as a special 'no column value', but |
278 | // DisplaySourceLines starts counting columns from 0 (and no column is |
279 | // expressed by passing an empty optional). |
280 | std::optional<size_t> columnToHighlight; |
281 | if (line == curr_line && column) |
282 | columnToHighlight = column - 1; |
283 | |
284 | size_t this_line_size = |
285 | last_file_sp->DisplaySourceLines(line, column: columnToHighlight, context_before: 0, context_after: 0, s); |
286 | if (column != 0 && line == curr_line && |
287 | should_show_stop_column_with_caret(debugger_sp)) { |
288 | // Display caret cursor. |
289 | std::string src_line; |
290 | last_file_sp->GetLine(line_no: line, buffer&: src_line); |
291 | s->Printf(format: " \t" ); |
292 | // Insert a space for every non-tab character in the source line. |
293 | for (size_t i = 0; i + 1 < column && i < src_line.length(); ++i) |
294 | s->PutChar(ch: src_line[i] == '\t' ? '\t' : ' '); |
295 | // Now add the caret. |
296 | s->Printf(format: "^\n" ); |
297 | } |
298 | if (this_line_size == 0) { |
299 | m_last_line = UINT32_MAX; |
300 | break; |
301 | } |
302 | } |
303 | } |
304 | return *delta; |
305 | } |
306 | |
307 | size_t SourceManager::DisplaySourceLinesWithLineNumbers( |
308 | const FileSpec &file_spec, uint32_t line, uint32_t column, |
309 | uint32_t context_before, uint32_t context_after, |
310 | const char *current_line_cstr, Stream *s, |
311 | const SymbolContextList *bp_locs) { |
312 | FileSP file_sp(GetFile(file_spec)); |
313 | |
314 | uint32_t start_line; |
315 | uint32_t count = context_before + context_after + 1; |
316 | if (line > context_before) |
317 | start_line = line - context_before; |
318 | else |
319 | start_line = 1; |
320 | |
321 | FileSP last_file_sp(GetLastFile()); |
322 | if (last_file_sp.get() != file_sp.get()) { |
323 | if (line == 0) |
324 | m_last_line = 0; |
325 | m_last_file_spec = file_spec; |
326 | } |
327 | return DisplaySourceLinesWithLineNumbersUsingLastFile( |
328 | start_line, count, curr_line: line, column, current_line_cstr, s, bp_locs); |
329 | } |
330 | |
331 | size_t SourceManager::DisplayMoreWithLineNumbers( |
332 | Stream *s, uint32_t count, bool reverse, const SymbolContextList *bp_locs) { |
333 | // If we get called before anybody has set a default file and line, then try |
334 | // to figure it out here. |
335 | FileSP last_file_sp(GetLastFile()); |
336 | const bool have_default_file_line = last_file_sp && m_last_line > 0; |
337 | if (!m_default_set) { |
338 | FileSpec tmp_spec; |
339 | uint32_t tmp_line; |
340 | GetDefaultFileAndLine(file_spec&: tmp_spec, line&: tmp_line); |
341 | } |
342 | |
343 | if (last_file_sp) { |
344 | if (m_last_line == UINT32_MAX) |
345 | return 0; |
346 | |
347 | if (reverse && m_last_line == 1) |
348 | return 0; |
349 | |
350 | if (count > 0) |
351 | m_last_count = count; |
352 | else if (m_last_count == 0) |
353 | m_last_count = 10; |
354 | |
355 | if (m_last_line > 0) { |
356 | if (reverse) { |
357 | // If this is the first time we've done a reverse, then back up one |
358 | // more time so we end up showing the chunk before the last one we've |
359 | // shown: |
360 | if (m_last_line > m_last_count) |
361 | m_last_line -= m_last_count; |
362 | else |
363 | m_last_line = 1; |
364 | } else if (have_default_file_line) |
365 | m_last_line += m_last_count; |
366 | } else |
367 | m_last_line = 1; |
368 | |
369 | const uint32_t column = 0; |
370 | return DisplaySourceLinesWithLineNumbersUsingLastFile( |
371 | start_line: m_last_line, count: m_last_count, UINT32_MAX, column, current_line_cstr: "" , s, bp_locs); |
372 | } |
373 | return 0; |
374 | } |
375 | |
376 | bool SourceManager::SetDefaultFileAndLine(const FileSpec &file_spec, |
377 | uint32_t line) { |
378 | m_default_set = true; |
379 | FileSP file_sp(GetFile(file_spec)); |
380 | |
381 | if (file_sp) { |
382 | m_last_line = line; |
383 | m_last_file_spec = file_spec; |
384 | return true; |
385 | } else { |
386 | return false; |
387 | } |
388 | } |
389 | |
390 | bool SourceManager::GetDefaultFileAndLine(FileSpec &file_spec, uint32_t &line) { |
391 | if (FileSP last_file_sp = GetLastFile()) { |
392 | file_spec = m_last_file_spec; |
393 | line = m_last_line; |
394 | return true; |
395 | } else if (!m_default_set) { |
396 | TargetSP target_sp(m_target_wp.lock()); |
397 | |
398 | if (target_sp) { |
399 | // If nobody has set the default file and line then try here. If there's |
400 | // no executable, then we will try again later when there is one. |
401 | // Otherwise, if we can't find it we won't look again, somebody will have |
402 | // to set it (for instance when we stop somewhere...) |
403 | Module *executable_ptr = target_sp->GetExecutableModulePointer(); |
404 | if (executable_ptr) { |
405 | SymbolContextList sc_list; |
406 | ConstString main_name("main" ); |
407 | |
408 | ModuleFunctionSearchOptions function_options; |
409 | function_options.include_symbols = |
410 | false; // Force it to be a debug symbol. |
411 | function_options.include_inlines = true; |
412 | executable_ptr->FindFunctions(name: main_name, parent_decl_ctx: CompilerDeclContext(), |
413 | name_type_mask: lldb::eFunctionNameTypeBase, |
414 | options: function_options, sc_list); |
415 | for (const SymbolContext &sc : sc_list) { |
416 | if (sc.function) { |
417 | lldb_private::LineEntry line_entry; |
418 | if (sc.function->GetAddressRange() |
419 | .GetBaseAddress() |
420 | .CalculateSymbolContextLineEntry(line_entry)) { |
421 | SetDefaultFileAndLine(file_spec: line_entry.GetFile(), line: line_entry.line); |
422 | file_spec = m_last_file_spec; |
423 | line = m_last_line; |
424 | return true; |
425 | } |
426 | } |
427 | } |
428 | } |
429 | } |
430 | } |
431 | return false; |
432 | } |
433 | |
434 | void SourceManager::FindLinesMatchingRegex(FileSpec &file_spec, |
435 | RegularExpression ®ex, |
436 | uint32_t start_line, |
437 | uint32_t end_line, |
438 | std::vector<uint32_t> &match_lines) { |
439 | match_lines.clear(); |
440 | FileSP file_sp = GetFile(file_spec); |
441 | if (!file_sp) |
442 | return; |
443 | return file_sp->FindLinesMatchingRegex(regex, start_line, end_line, |
444 | match_lines); |
445 | } |
446 | |
447 | SourceManager::File::File(const FileSpec &file_spec, |
448 | lldb::DebuggerSP debugger_sp) |
449 | : m_file_spec_orig(file_spec), m_file_spec(), m_mod_time(), |
450 | m_debugger_wp(debugger_sp), m_target_wp(TargetSP()) { |
451 | CommonInitializer(file_spec, target_sp: {}); |
452 | } |
453 | |
454 | SourceManager::File::File(const FileSpec &file_spec, TargetSP target_sp) |
455 | : m_file_spec_orig(file_spec), m_file_spec(), m_mod_time(), |
456 | m_debugger_wp(target_sp ? target_sp->GetDebugger().shared_from_this() |
457 | : DebuggerSP()), |
458 | m_target_wp(target_sp) { |
459 | CommonInitializer(file_spec, target_sp); |
460 | } |
461 | |
462 | void SourceManager::File::CommonInitializer(const FileSpec &file_spec, |
463 | TargetSP target_sp) { |
464 | // Set the file and update the modification time. |
465 | SetFileSpec(file_spec); |
466 | |
467 | // Always update the source map modification ID if we have a target. |
468 | if (target_sp) |
469 | m_source_map_mod_id = target_sp->GetSourcePathMap().GetModificationID(); |
470 | |
471 | // File doesn't exist. |
472 | if (m_mod_time == llvm::sys::TimePoint<>()) { |
473 | if (target_sp) { |
474 | // If this is just a file name, try finding it in the target. |
475 | if (!file_spec.GetDirectory() && file_spec.GetFilename()) { |
476 | bool check_inlines = false; |
477 | SymbolContextList sc_list; |
478 | size_t num_matches = |
479 | target_sp->GetImages().ResolveSymbolContextForFilePath( |
480 | file_path: file_spec.GetFilename().AsCString(), line: 0, check_inlines, |
481 | resolve_scope: SymbolContextItem(eSymbolContextModule | |
482 | eSymbolContextCompUnit), |
483 | sc_list); |
484 | bool got_multiple = false; |
485 | if (num_matches != 0) { |
486 | if (num_matches > 1) { |
487 | CompileUnit *test_cu = nullptr; |
488 | for (const SymbolContext &sc : sc_list) { |
489 | if (sc.comp_unit) { |
490 | if (test_cu) { |
491 | if (test_cu != sc.comp_unit) |
492 | got_multiple = true; |
493 | break; |
494 | } else |
495 | test_cu = sc.comp_unit; |
496 | } |
497 | } |
498 | } |
499 | if (!got_multiple) { |
500 | SymbolContext sc; |
501 | sc_list.GetContextAtIndex(idx: 0, sc); |
502 | if (sc.comp_unit) |
503 | SetFileSpec(sc.comp_unit->GetPrimaryFile()); |
504 | } |
505 | } |
506 | } |
507 | |
508 | // Try remapping the file if it doesn't exist. |
509 | if (!FileSystem::Instance().Exists(file_spec: m_file_spec)) { |
510 | // Check target specific source remappings (i.e., the |
511 | // target.source-map setting), then fall back to the module |
512 | // specific remapping (i.e., the .dSYM remapping dictionary). |
513 | auto remapped = target_sp->GetSourcePathMap().FindFile(orig_spec: m_file_spec); |
514 | if (!remapped) { |
515 | FileSpec new_spec; |
516 | if (target_sp->GetImages().FindSourceFile(orig_spec: m_file_spec, new_spec)) |
517 | remapped = new_spec; |
518 | } |
519 | if (remapped) |
520 | SetFileSpec(*remapped); |
521 | } |
522 | } |
523 | } |
524 | |
525 | // If the file exists, read in the data. |
526 | if (m_mod_time != llvm::sys::TimePoint<>()) |
527 | m_data_sp = FileSystem::Instance().CreateDataBuffer(file_spec: m_file_spec); |
528 | } |
529 | |
530 | void SourceManager::File::SetFileSpec(FileSpec file_spec) { |
531 | resolve_tilde(file_spec); |
532 | m_file_spec = std::move(file_spec); |
533 | m_mod_time = FileSystem::Instance().GetModificationTime(file_spec: m_file_spec); |
534 | } |
535 | |
536 | uint32_t SourceManager::File::GetLineOffset(uint32_t line) { |
537 | if (line == 0) |
538 | return UINT32_MAX; |
539 | |
540 | if (line == 1) |
541 | return 0; |
542 | |
543 | if (CalculateLineOffsets(line)) { |
544 | if (line < m_offsets.size()) |
545 | return m_offsets[line - 1]; // yes we want "line - 1" in the index |
546 | } |
547 | return UINT32_MAX; |
548 | } |
549 | |
550 | uint32_t SourceManager::File::GetNumLines() { |
551 | CalculateLineOffsets(); |
552 | return m_offsets.size(); |
553 | } |
554 | |
555 | const char *SourceManager::File::PeekLineData(uint32_t line) { |
556 | if (!LineIsValid(line)) |
557 | return nullptr; |
558 | |
559 | size_t line_offset = GetLineOffset(line); |
560 | if (line_offset < m_data_sp->GetByteSize()) |
561 | return (const char *)m_data_sp->GetBytes() + line_offset; |
562 | return nullptr; |
563 | } |
564 | |
565 | uint32_t SourceManager::File::GetLineLength(uint32_t line, |
566 | bool include_newline_chars) { |
567 | if (!LineIsValid(line)) |
568 | return false; |
569 | |
570 | size_t start_offset = GetLineOffset(line); |
571 | size_t end_offset = GetLineOffset(line: line + 1); |
572 | if (end_offset == UINT32_MAX) |
573 | end_offset = m_data_sp->GetByteSize(); |
574 | |
575 | if (end_offset > start_offset) { |
576 | uint32_t length = end_offset - start_offset; |
577 | if (!include_newline_chars) { |
578 | const char *line_start = |
579 | (const char *)m_data_sp->GetBytes() + start_offset; |
580 | while (length > 0) { |
581 | const char last_char = line_start[length - 1]; |
582 | if ((last_char == '\r') || (last_char == '\n')) |
583 | --length; |
584 | else |
585 | break; |
586 | } |
587 | } |
588 | return length; |
589 | } |
590 | return 0; |
591 | } |
592 | |
593 | bool SourceManager::File::LineIsValid(uint32_t line) { |
594 | if (line == 0) |
595 | return false; |
596 | |
597 | if (CalculateLineOffsets(line)) |
598 | return line < m_offsets.size(); |
599 | return false; |
600 | } |
601 | |
602 | bool SourceManager::File::ModificationTimeIsStale() const { |
603 | // TODO: use host API to sign up for file modifications to anything in our |
604 | // source cache and only update when we determine a file has been updated. |
605 | // For now we check each time we want to display info for the file. |
606 | auto curr_mod_time = FileSystem::Instance().GetModificationTime(file_spec: m_file_spec); |
607 | return curr_mod_time != llvm::sys::TimePoint<>() && |
608 | m_mod_time != curr_mod_time; |
609 | } |
610 | |
611 | bool SourceManager::File::PathRemappingIsStale() const { |
612 | if (TargetSP target_sp = m_target_wp.lock()) |
613 | return GetSourceMapModificationID() != |
614 | target_sp->GetSourcePathMap().GetModificationID(); |
615 | return false; |
616 | } |
617 | |
618 | size_t SourceManager::File::DisplaySourceLines(uint32_t line, |
619 | std::optional<size_t> column, |
620 | uint32_t context_before, |
621 | uint32_t context_after, |
622 | Stream *s) { |
623 | // Nothing to write if there's no stream. |
624 | if (!s) |
625 | return 0; |
626 | |
627 | // Sanity check m_data_sp before proceeding. |
628 | if (!m_data_sp) |
629 | return 0; |
630 | |
631 | size_t bytes_written = s->GetWrittenBytes(); |
632 | |
633 | auto debugger_sp = m_debugger_wp.lock(); |
634 | |
635 | HighlightStyle style; |
636 | // Use the default Vim style if source highlighting is enabled. |
637 | if (should_highlight_source(debugger_sp)) |
638 | style = HighlightStyle::MakeVimStyle(); |
639 | |
640 | // If we should mark the stop column with color codes, then copy the prefix |
641 | // and suffix to our color style. |
642 | if (should_show_stop_column_with_ansi(debugger_sp)) |
643 | style.selected.Set(prefix: debugger_sp->GetStopShowColumnAnsiPrefix(), |
644 | suffix: debugger_sp->GetStopShowColumnAnsiSuffix()); |
645 | |
646 | HighlighterManager mgr; |
647 | std::string path = GetFileSpec().GetPath(/*denormalize*/ false); |
648 | // FIXME: Find a way to get the definitive language this file was written in |
649 | // and pass it to the highlighter. |
650 | const auto &h = mgr.getHighlighterFor(language_type: lldb::eLanguageTypeUnknown, path); |
651 | |
652 | const uint32_t start_line = |
653 | line <= context_before ? 1 : line - context_before; |
654 | const uint32_t start_line_offset = GetLineOffset(line: start_line); |
655 | if (start_line_offset != UINT32_MAX) { |
656 | const uint32_t end_line = line + context_after; |
657 | uint32_t end_line_offset = GetLineOffset(line: end_line + 1); |
658 | if (end_line_offset == UINT32_MAX) |
659 | end_line_offset = m_data_sp->GetByteSize(); |
660 | |
661 | assert(start_line_offset <= end_line_offset); |
662 | if (start_line_offset < end_line_offset) { |
663 | size_t count = end_line_offset - start_line_offset; |
664 | const uint8_t *cstr = m_data_sp->GetBytes() + start_line_offset; |
665 | |
666 | auto ref = llvm::StringRef(reinterpret_cast<const char *>(cstr), count); |
667 | |
668 | h.Highlight(options: style, line: ref, cursor_pos: column, previous_lines: "" , s&: *s); |
669 | |
670 | // Ensure we get an end of line character one way or another. |
671 | if (!is_newline_char(ch: ref.back())) |
672 | s->EOL(); |
673 | } |
674 | } |
675 | return s->GetWrittenBytes() - bytes_written; |
676 | } |
677 | |
678 | void SourceManager::File::FindLinesMatchingRegex( |
679 | RegularExpression ®ex, uint32_t start_line, uint32_t end_line, |
680 | std::vector<uint32_t> &match_lines) { |
681 | match_lines.clear(); |
682 | |
683 | if (!LineIsValid(line: start_line) || |
684 | (end_line != UINT32_MAX && !LineIsValid(line: end_line))) |
685 | return; |
686 | if (start_line > end_line) |
687 | return; |
688 | |
689 | for (uint32_t line_no = start_line; line_no < end_line; line_no++) { |
690 | std::string buffer; |
691 | if (!GetLine(line_no, buffer)) |
692 | break; |
693 | if (regex.Execute(string: buffer)) { |
694 | match_lines.push_back(x: line_no); |
695 | } |
696 | } |
697 | } |
698 | |
699 | bool lldb_private::operator==(const SourceManager::File &lhs, |
700 | const SourceManager::File &rhs) { |
701 | if (lhs.m_file_spec != rhs.m_file_spec) |
702 | return false; |
703 | return lhs.m_mod_time == rhs.m_mod_time; |
704 | } |
705 | |
706 | bool SourceManager::File::CalculateLineOffsets(uint32_t line) { |
707 | line = |
708 | UINT32_MAX; // TODO: take this line out when we support partial indexing |
709 | if (line == UINT32_MAX) { |
710 | // Already done? |
711 | if (!m_offsets.empty() && m_offsets[0] == UINT32_MAX) |
712 | return true; |
713 | |
714 | if (m_offsets.empty()) { |
715 | if (m_data_sp.get() == nullptr) |
716 | return false; |
717 | |
718 | const char *start = (const char *)m_data_sp->GetBytes(); |
719 | if (start) { |
720 | const char *end = start + m_data_sp->GetByteSize(); |
721 | |
722 | // Calculate all line offsets from scratch |
723 | |
724 | // Push a 1 at index zero to indicate the file has been completely |
725 | // indexed. |
726 | m_offsets.push_back(UINT32_MAX); |
727 | const char *s; |
728 | for (s = start; s < end; ++s) { |
729 | char curr_ch = *s; |
730 | if (is_newline_char(ch: curr_ch)) { |
731 | if (s + 1 < end) { |
732 | char next_ch = s[1]; |
733 | if (is_newline_char(ch: next_ch)) { |
734 | if (curr_ch != next_ch) |
735 | ++s; |
736 | } |
737 | } |
738 | m_offsets.push_back(x: s + 1 - start); |
739 | } |
740 | } |
741 | if (!m_offsets.empty()) { |
742 | if (m_offsets.back() < size_t(end - start)) |
743 | m_offsets.push_back(x: end - start); |
744 | } |
745 | return true; |
746 | } |
747 | } else { |
748 | // Some lines have been populated, start where we last left off |
749 | assert("Not implemented yet" && false); |
750 | } |
751 | |
752 | } else { |
753 | // Calculate all line offsets up to "line" |
754 | assert("Not implemented yet" && false); |
755 | } |
756 | return false; |
757 | } |
758 | |
759 | bool SourceManager::File::GetLine(uint32_t line_no, std::string &buffer) { |
760 | if (!LineIsValid(line: line_no)) |
761 | return false; |
762 | |
763 | size_t start_offset = GetLineOffset(line: line_no); |
764 | size_t end_offset = GetLineOffset(line: line_no + 1); |
765 | if (end_offset == UINT32_MAX) { |
766 | end_offset = m_data_sp->GetByteSize(); |
767 | } |
768 | buffer.assign(s: (const char *)m_data_sp->GetBytes() + start_offset, |
769 | n: end_offset - start_offset); |
770 | |
771 | return true; |
772 | } |
773 | |
774 | void SourceManager::SourceFileCache::AddSourceFile(const FileSpec &file_spec, |
775 | FileSP file_sp) { |
776 | llvm::sys::ScopedWriter guard(m_mutex); |
777 | |
778 | assert(file_sp && "invalid FileSP" ); |
779 | |
780 | AddSourceFileImpl(file_spec, file_sp); |
781 | const FileSpec &resolved_file_spec = file_sp->GetFileSpec(); |
782 | if (file_spec != resolved_file_spec) |
783 | AddSourceFileImpl(file_spec: file_sp->GetFileSpec(), file_sp); |
784 | } |
785 | |
786 | void SourceManager::SourceFileCache::RemoveSourceFile(const FileSP &file_sp) { |
787 | llvm::sys::ScopedWriter guard(m_mutex); |
788 | |
789 | assert(file_sp && "invalid FileSP" ); |
790 | |
791 | // Iterate over all the elements in the cache. |
792 | // This is expensive but a relatively uncommon operation. |
793 | auto it = m_file_cache.begin(); |
794 | while (it != m_file_cache.end()) { |
795 | if (it->second == file_sp) |
796 | it = m_file_cache.erase(position: it); |
797 | else |
798 | it++; |
799 | } |
800 | } |
801 | |
802 | void SourceManager::SourceFileCache::AddSourceFileImpl( |
803 | const FileSpec &file_spec, FileSP file_sp) { |
804 | FileCache::iterator pos = m_file_cache.find(x: file_spec); |
805 | if (pos == m_file_cache.end()) { |
806 | m_file_cache[file_spec] = file_sp; |
807 | } else { |
808 | if (file_sp != pos->second) |
809 | m_file_cache[file_spec] = file_sp; |
810 | } |
811 | } |
812 | |
813 | SourceManager::FileSP SourceManager::SourceFileCache::FindSourceFile( |
814 | const FileSpec &file_spec) const { |
815 | llvm::sys::ScopedReader guard(m_mutex); |
816 | |
817 | FileCache::const_iterator pos = m_file_cache.find(x: file_spec); |
818 | if (pos != m_file_cache.end()) |
819 | return pos->second; |
820 | return {}; |
821 | } |
822 | |
823 | void SourceManager::SourceFileCache::Dump(Stream &stream) const { |
824 | stream << "Modification time Lines Path\n" ; |
825 | stream << "------------------- -------- --------------------------------\n" ; |
826 | for (auto &entry : m_file_cache) { |
827 | if (!entry.second) |
828 | continue; |
829 | FileSP file = entry.second; |
830 | stream.Format(format: "{0:%Y-%m-%d %H:%M:%S} {1,8:d} {2}\n" , args: file->GetTimestamp(), |
831 | args: file->GetNumLines(), args: entry.first.GetPath()); |
832 | } |
833 | } |
834 | |