]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blame - gdb/source-cache.c
Automatic date update in version.in
[thirdparty/binutils-gdb.git] / gdb / source-cache.c
CommitLineData
62f29fda 1/* Cache of styled source file text
d01e8234 2 Copyright (C) 2018-2025 Free Software Foundation, Inc.
62f29fda
TT
3
4 This file is part of GDB.
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>. */
18
62f29fda 19#include "source-cache.h"
268a13a5 20#include "gdbsupport/scoped_fd.h"
62f29fda
TT
21#include "source.h"
22#include "cli/cli-style.h"
0d12e84c 23#include "symtab.h"
cb44333d
TT
24#include "objfiles.h"
25#include "exec.h"
39370778 26#include "cli/cli-cmds.h"
62f29fda
TT
27
28#ifdef HAVE_SOURCE_HIGHLIGHT
3a350822
EZ
29/* If Gnulib redirects 'open' and 'close' to its replacements
30 'rpl_open' and 'rpl_close' via cpp macros, including <fstream>
31 below with those macros in effect will cause unresolved externals
32 when GDB is linked. Happens, e.g., in the MinGW build. */
33#undef open
34#undef close
62f29fda
TT
35#include <sstream>
36#include <srchilite/sourcehighlight.h>
37#include <srchilite/langmap.h>
eefa43c9 38#include <srchilite/settings.h>
62f29fda
TT
39#endif
40
cda75080
TV
41#if GDB_SELF_TEST
42#include "gdbsupport/selftest.h"
43#endif
44
62f29fda
TT
45/* The number of source files we'll cache. */
46
47#define MAX_ENTRIES 5
48
49/* See source-cache.h. */
50
51source_cache g_source_cache;
52
643b1268
AB
53/* When this is true we will use the GNU Source Highlight to add styling to
54 source code (assuming the library is available). This is initialized to
55 true (if appropriate) in _initialize_source_cache below. */
56
57static bool use_gnu_source_highlight;
58
59/* The "maint show gnu-source-highlight enabled" command. */
60
61static void
62show_use_gnu_source_highlight_enabled (struct ui_file *file, int from_tty,
63 struct cmd_list_element *c,
64 const char *value)
65{
6cb06a8c
TT
66 gdb_printf (file,
67 _("Use of GNU Source Highlight library is \"%s\".\n"),
68 value);
643b1268
AB
69}
70
71/* The "maint set gnu-source-highlight enabled" command. */
72
73static void
74set_use_gnu_source_highlight_enabled (const char *ignore_args,
75 int from_tty,
76 struct cmd_list_element *c)
77{
78#ifndef HAVE_SOURCE_HIGHLIGHT
79 /* If the library is not available and the user tried to enable use of
80 the library, then disable use of the library, and give an error. */
81 if (use_gnu_source_highlight)
82 {
83 use_gnu_source_highlight = false;
84 error (_("the GNU Source Highlight library is not available"));
85 }
86#else
87 /* We (might) have just changed how we style source code, discard any
88 previously cached contents. */
89 forget_cached_source_info ();
90#endif
91}
92
62f29fda
TT
93/* See source-cache.h. */
94
cb44333d
TT
95std::string
96source_cache::get_plain_source_lines (struct symtab *s,
97 const std::string &fullname)
62f29fda 98{
cb44333d 99 scoped_fd desc (open_source_file (s));
62f29fda 100 if (desc.get () < 0)
8cc96ee4 101 perror_with_name (symtab_to_filename_for_display (s), -desc.get ());
62f29fda 102
872dceaa 103 struct stat st;
872dceaa 104 if (fstat (desc.get (), &st) < 0)
62f29fda
TT
105 perror_with_name (symtab_to_filename_for_display (s));
106
cb44333d
TT
107 std::string lines;
108 lines.resize (st.st_size);
109 if (myread (desc.get (), &lines[0], lines.size ()) < 0)
62f29fda
TT
110 perror_with_name (symtab_to_filename_for_display (s));
111
cb44333d 112 time_t mtime = 0;
3c86fae3
SM
113 if (s->compunit ()->objfile () != NULL
114 && s->compunit ()->objfile ()->obfd != NULL)
115 mtime = s->compunit ()->objfile ()->mtime;
7e10abd1 116 else if (current_program_space->exec_bfd ())
5a36e715 117 mtime = current_program_space->ebfd_mtime;
62f29fda 118
cb44333d
TT
119 if (mtime && mtime < st.st_mtime)
120 warning (_("Source file is more recent than executable."));
62f29fda 121
cb44333d
TT
122 std::vector<off_t> offsets;
123 offsets.push_back (0);
124 for (size_t offset = lines.find ('\n');
125 offset != std::string::npos;
126 offset = lines.find ('\n', offset))
62f29fda 127 {
cb44333d
TT
128 ++offset;
129 /* A newline at the end does not start a new line. It would
130 seem simpler to just strip the newline in this function, but
131 then "list" won't print the final newline. */
132 if (offset != lines.size ())
133 offsets.push_back (offset);
62f29fda
TT
134 }
135
cb44333d
TT
136 offsets.shrink_to_fit ();
137 m_offset_cache.emplace (fullname, std::move (offsets));
138
139 return lines;
62f29fda
TT
140}
141
64c45143
TT
142#ifdef HAVE_SOURCE_HIGHLIGHT
143
62f29fda
TT
144/* Return the Source Highlight language name, given a gdb language
145 LANG. Returns NULL if the language is not known. */
146
147static const char *
148get_language_name (enum language lang)
149{
150 switch (lang)
151 {
152 case language_c:
153 case language_objc:
154 return "c.lang";
155
156 case language_cplus:
157 return "cpp.lang";
158
159 case language_d:
160 return "d.lang";
161
162 case language_go:
163 return "go.lang";
164
165 case language_fortran:
166 return "fortran.lang";
167
168 case language_m2:
169 /* Not handled by Source Highlight. */
170 break;
171
172 case language_asm:
173 return "asm.lang";
174
175 case language_pascal:
176 return "pascal.lang";
177
178 case language_opencl:
179 /* Not handled by Source Highlight. */
180 break;
181
182 case language_rust:
d806ea2d 183 return "rust.lang";
62f29fda
TT
184
185 case language_ada:
186 return "ada.lang";
187
188 default:
189 break;
190 }
191
192 return nullptr;
193}
194
64c45143
TT
195#endif /* HAVE_SOURCE_HIGHLIGHT */
196
62dfd02e 197/* Try to highlight CONTENTS from file FULLNAME in language LANG using
bbc8c5a1 198 the GNU source-highlight library. Return true if highlighting
62dfd02e
TV
199 succeeded. */
200
201static bool
202try_source_highlight (std::string &contents ATTRIBUTE_UNUSED,
203 enum language lang ATTRIBUTE_UNUSED,
204 const std::string &fullname ATTRIBUTE_UNUSED)
205{
206#ifdef HAVE_SOURCE_HIGHLIGHT
207 if (!use_gnu_source_highlight)
208 return false;
209
210 const char *lang_name = get_language_name (lang);
62dfd02e
TV
211
212 /* The global source highlight object, or null if one was
213 never constructed. This is stored here rather than in
214 the class so that we don't need to include anything or do
215 conditional compilation in source-cache.h. */
216 static srchilite::SourceHighlight *highlighter;
217
eefa43c9
TV
218 /* The global source highlight language map object. */
219 static srchilite::LangMap *langmap;
220
62dfd02e
TV
221 bool styled = false;
222 try
223 {
224 if (highlighter == nullptr)
225 {
226 highlighter = new srchilite::SourceHighlight ("esc.outlang");
227 highlighter->setStyleFile ("esc.style");
eefa43c9
TV
228
229 const std::string &datadir = srchilite::Settings::retrieveDataDir ();
230 langmap = new srchilite::LangMap (datadir, "lang.map");
231 }
232
233 std::string detected_lang;
234 if (lang_name == nullptr)
235 {
236 detected_lang = langmap->getMappedFileNameFromFileName (fullname);
237 if (detected_lang.empty ())
238 return false;
239 lang_name = detected_lang.c_str ();
62dfd02e
TV
240 }
241
242 std::istringstream input (contents);
243 std::ostringstream output;
244 highlighter->highlight (input, output, lang_name, fullname);
245 contents = std::move (output).str ();
246 styled = true;
247 }
248 catch (...)
249 {
250 /* Source Highlight will throw an exception if
251 highlighting fails. One possible reason it can fail
252 is if the language is unknown -- which matters to gdb
253 because Rust support wasn't added until after 3.1.8.
254 Ignore exceptions here. */
255 }
256
257 return styled;
258#else
259 return false;
260#endif /* HAVE_SOURCE_HIGHLIGHT */
261}
262
cda75080
TV
263#ifdef HAVE_SOURCE_HIGHLIGHT
264#if GDB_SELF_TEST
265namespace selftests
266{
267static void gnu_source_highlight_test ()
268{
269 const std::string prog
270 = ("int\n"
271 "foo (void)\n"
272 "{\n"
273 " return 0;\n"
274 "}\n");
275 const std::string fullname = "test.c";
276 std::string styled_prog;
277
278 bool res = false;
279 bool saw_exception = false;
280 styled_prog = prog;
281 try
282 {
283 res = try_source_highlight (styled_prog, language_c, fullname);
284 }
b3926d6a
TV
285 catch (const gdb_exception &e)
286 {
287 if (e.reason != RETURN_ERROR)
288 throw;
289 saw_exception = true;
290 }
cda75080
TV
291 catch (...)
292 {
293 saw_exception = true;
294 }
295
296 SELF_CHECK (!saw_exception);
297 if (res)
298 SELF_CHECK (prog.size () < styled_prog.size ());
299 else
300 SELF_CHECK (prog == styled_prog);
301}
302}
303#endif /* GDB_SELF_TEST */
304#endif /* HAVE_SOURCE_HIGHLIGHT */
305
62f29fda
TT
306/* See source-cache.h. */
307
308bool
cb44333d 309source_cache::ensure (struct symtab *s)
62f29fda 310{
872dceaa 311 std::string fullname = symtab_to_fullname (s);
62f29fda 312
cb44333d
TT
313 size_t size = m_source_map.size ();
314 for (int i = 0; i < size; ++i)
872dceaa 315 {
cb44333d 316 if (m_source_map[i].fullname == fullname)
62f29fda 317 {
0e42221a
AB
318 /* This should always hold, because we create the file offsets
319 when reading the file. */
cb44333d
TT
320 gdb_assert (m_offset_cache.find (fullname)
321 != m_offset_cache.end ());
322 /* Not strictly LRU, but at least ensure that the most
323 recently used entry is always the last candidate for
324 deletion. Note that this property is relied upon by at
325 least one caller. */
326 if (i != size - 1)
327 std::swap (m_source_map[i], m_source_map[size - 1]);
05f5f4f2
AB
328
329 /* If the styling status of the cached entry matches our desired
330 styling status, or we know this file cannot be styled, in
331 which case, this (unstyled) content, is the best we can do. */
332 if (((source_styling && gdb_stdout->can_emit_style_escape ())
333 == m_source_map[size - 1].styled)
334 || m_no_styling_files.count (fullname) > 0)
335 return true;
336
337 /* We found a match, but styling status doesn't match the desired
338 styling status. We already moved the matching item to the
339 back of M_SOURCE_MAP, so drop the entry now, and then
340 recompute with the desired styling. */
341 m_source_map.pop_back ();
342 break;
62f29fda 343 }
872dceaa 344 }
62f29fda 345
1d5d29e7 346 std::string contents;
05f5f4f2 347 bool styled_p = false;
1d5d29e7
SV
348 try
349 {
350 contents = get_plain_source_lines (s, fullname);
351 }
352 catch (const gdb_exception_error &e)
353 {
354 /* If 's' is not found, an exception is thrown. */
355 return false;
356 }
872dceaa 357
dcbdb080
TV
358 if (source_styling && gdb_stdout->can_emit_style_escape ()
359 && m_no_styling_files.count (fullname) == 0)
872dceaa 360 {
05f5f4f2 361 styled_p
62dfd02e 362 = try_source_highlight (contents, s->language (), fullname);
f6474de9 363
05f5f4f2 364 if (!styled_p)
f6474de9 365 {
6b09f134 366 std::optional<std::string> ext_contents;
93bb1ebf
TV
367 ext_contents = ext_lang_colorize (fullname, contents,
368 s->language ());
f6474de9 369 if (ext_contents.has_value ())
dcbdb080
TV
370 {
371 contents = std::move (*ext_contents);
05f5f4f2 372 styled_p = true;
dcbdb080
TV
373 }
374 }
375
05f5f4f2 376 if (!styled_p)
dcbdb080
TV
377 {
378 /* Styling failed. Styling can fail for instance for these
379 reasons:
380 - the language is not supported.
381 - the language cannot not be auto-detected from the file name.
382 - no stylers available.
383
384 Since styling failed, don't try styling the file again after it
385 drops from the cache.
386
387 Note that clearing the source cache also clears
388 m_no_styling_files. */
389 m_no_styling_files.insert (fullname);
f6474de9
TT
390 }
391 }
62f29fda 392
05f5f4f2
AB
393 source_text result
394 = { std::move (fullname), std::move (contents), styled_p };
872dceaa
TT
395 m_source_map.push_back (std::move (result));
396
397 if (m_source_map.size () > MAX_ENTRIES)
0e42221a
AB
398 {
399 auto iter = m_source_map.begin ();
400 m_offset_cache.erase (iter->fullname);
401 m_source_map.erase (iter);
402 }
872dceaa 403
872dceaa 404 return true;
62f29fda 405}
269249d9 406
cb44333d
TT
407/* See source-cache.h. */
408
409bool
410source_cache::get_line_charpos (struct symtab *s,
411 const std::vector<off_t> **offsets)
412{
1d5d29e7 413 std::string fullname = symtab_to_fullname (s);
cb44333d 414
1d5d29e7
SV
415 auto iter = m_offset_cache.find (fullname);
416 if (iter == m_offset_cache.end ())
cb44333d 417 {
1d5d29e7
SV
418 if (!ensure (s))
419 return false;
420 iter = m_offset_cache.find (fullname);
421 /* cache_source_text ensured this was entered. */
422 gdb_assert (iter != m_offset_cache.end ());
cb44333d 423 }
1d5d29e7
SV
424
425 *offsets = &iter->second;
426 return true;
cb44333d
TT
427}
428
429/* A helper function that extracts the desired source lines from TEXT,
430 putting them into LINES_OUT. The arguments are as for
431 get_source_lines. Returns true on success, false if the line
432 numbers are invalid. */
433
434static bool
435extract_lines (const std::string &text, int first_line, int last_line,
436 std::string *lines_out)
437{
438 int lineno = 1;
439 std::string::size_type pos = 0;
440 std::string::size_type first_pos = std::string::npos;
441
442 while (pos != std::string::npos && lineno <= last_line)
443 {
444 std::string::size_type new_pos = text.find ('\n', pos);
445
446 if (lineno == first_line)
447 first_pos = pos;
448
449 pos = new_pos;
450 if (lineno == last_line || pos == std::string::npos)
451 {
452 /* A newline at the end does not start a new line. */
453 if (first_pos == std::string::npos
454 || first_pos == text.size ())
455 return false;
456 if (pos == std::string::npos)
457 pos = text.size ();
458 else
459 ++pos;
460 *lines_out = text.substr (first_pos, pos - first_pos);
461 return true;
462 }
463 ++lineno;
464 ++pos;
465 }
466
467 return false;
468}
469
470/* See source-cache.h. */
471
472bool
473source_cache::get_source_lines (struct symtab *s, int first_line,
474 int last_line, std::string *lines)
475{
476 if (first_line < 1 || last_line < 1 || first_line > last_line)
477 return false;
478
479 if (!ensure (s))
480 return false;
481
482 return extract_lines (m_source_map.back ().contents,
483 first_line, last_line, lines);
484}
485
39370778
AB
486/* Implement 'maint flush source-cache' command. */
487
488static void
489source_cache_flush_command (const char *command, int from_tty)
490{
491 forget_cached_source_info ();
6cb06a8c 492 gdb_printf (_("Source cache flushed.\n"));
39370778
AB
493}
494
269249d9
TT
495#if GDB_SELF_TEST
496namespace selftests
497{
498static void extract_lines_test ()
499{
500 std::string input_text = "abc\ndef\nghi\njkl\n";
cb44333d
TT
501 std::string result;
502
503 SELF_CHECK (extract_lines (input_text, 1, 1, &result)
504 && result == "abc\n");
505 SELF_CHECK (!extract_lines (input_text, 2, 1, &result));
506 SELF_CHECK (extract_lines (input_text, 1, 2, &result)
507 && result == "abc\ndef\n");
508 SELF_CHECK (extract_lines ("abc", 1, 1, &result)
509 && result == "abc");
269249d9
TT
510}
511}
512#endif
513
5fe70629 514INIT_GDB_FILE (source_cache)
269249d9 515{
39370778
AB
516 add_cmd ("source-cache", class_maintenance, source_cache_flush_command,
517 _("Force gdb to flush its source code cache."),
518 &maintenanceflushlist);
519
643b1268
AB
520 /* All the 'maint set|show gnu-source-highlight' sub-commands. */
521 static struct cmd_list_element *maint_set_gnu_source_highlight_cmdlist;
522 static struct cmd_list_element *maint_show_gnu_source_highlight_cmdlist;
523
524 /* Adds 'maint set|show gnu-source-highlight'. */
525 add_setshow_prefix_cmd ("gnu-source-highlight", class_maintenance,
526 _("Set gnu-source-highlight specific variables."),
527 _("Show gnu-source-highlight specific variables."),
528 &maint_set_gnu_source_highlight_cmdlist,
529 &maint_show_gnu_source_highlight_cmdlist,
530 &maintenance_set_cmdlist,
531 &maintenance_show_cmdlist);
532
533 /* Adds 'maint set|show gnu-source-highlight enabled'. */
534 add_setshow_boolean_cmd ("enabled", class_maintenance,
535 &use_gnu_source_highlight, _("\
536Set whether the GNU Source Highlight library should be used."), _("\
537Show whether the GNU Source Highlight library is being used."),_("\
538When enabled, GDB will use the GNU Source Highlight library to apply\n\
539styling to source code lines that are shown."),
540 set_use_gnu_source_highlight_enabled,
541 show_use_gnu_source_highlight_enabled,
542 &maint_set_gnu_source_highlight_cmdlist,
543 &maint_show_gnu_source_highlight_cmdlist);
544
545 /* Enable use of GNU Source Highlight library, if we have it. */
546#ifdef HAVE_SOURCE_HIGHLIGHT
547 use_gnu_source_highlight = true;
548#endif
549
269249d9
TT
550#if GDB_SELF_TEST
551 selftests::register_test ("source-cache", selftests::extract_lines_test);
cda75080
TV
552#ifdef HAVE_SOURCE_HIGHLIGHT
553 selftests::register_test ("gnu-source-highlight",
554 selftests::gnu_source_highlight_test);
555#endif
269249d9
TT
556#endif
557}