1 /* Cache of styled source file text
2 Copyright (C) 2018-2025 Free Software Foundation, Inc.
4 This file is part of GDB.
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.
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.
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/>. */
19 #include "source-cache.h"
20 #include "gdbsupport/scoped_fd.h"
22 #include "cli/cli-style.h"
26 #include "cli/cli-cmds.h"
28 #ifdef HAVE_SOURCE_HIGHLIGHT
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. */
36 #include <srchilite/sourcehighlight.h>
37 #include <srchilite/langmap.h>
38 #include <srchilite/settings.h>
42 #include "gdbsupport/selftest.h"
45 /* The number of source files we'll cache. */
49 /* See source-cache.h. */
51 source_cache g_source_cache
;
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. */
57 static bool use_gnu_source_highlight
;
59 /* The "maint show gnu-source-highlight enabled" command. */
62 show_use_gnu_source_highlight_enabled (struct ui_file
*file
, int from_tty
,
63 struct cmd_list_element
*c
,
67 _("Use of GNU Source Highlight library is \"%s\".\n"),
71 /* The "maint set gnu-source-highlight enabled" command. */
74 set_use_gnu_source_highlight_enabled (const char *ignore_args
,
76 struct cmd_list_element
*c
)
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
)
83 use_gnu_source_highlight
= false;
84 error (_("the GNU Source Highlight library is not available"));
87 /* We (might) have just changed how we style source code, discard any
88 previously cached contents. */
89 forget_cached_source_info ();
93 /* See source-cache.h. */
96 source_cache::get_plain_source_lines (struct symtab
*s
,
97 const std::string
&fullname
)
99 scoped_fd
desc (open_source_file (s
));
101 perror_with_name (symtab_to_filename_for_display (s
), -desc
.get ());
104 if (fstat (desc
.get (), &st
) < 0)
105 perror_with_name (symtab_to_filename_for_display (s
));
108 lines
.resize (st
.st_size
);
109 if (myread (desc
.get (), &lines
[0], lines
.size ()) < 0)
110 perror_with_name (symtab_to_filename_for_display (s
));
113 if (s
->compunit ()->objfile () != NULL
114 && s
->compunit ()->objfile ()->obfd
!= NULL
)
115 mtime
= s
->compunit ()->objfile ()->mtime
;
116 else if (current_program_space
->exec_bfd ())
117 mtime
= current_program_space
->ebfd_mtime
;
119 if (mtime
&& mtime
< st
.st_mtime
)
120 warning (_("Source file is more recent than executable."));
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
))
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
);
136 offsets
.shrink_to_fit ();
137 m_offset_cache
.emplace (fullname
, std::move (offsets
));
142 #ifdef HAVE_SOURCE_HIGHLIGHT
144 /* Return the Source Highlight language name, given a gdb language
145 LANG. Returns NULL if the language is not known. */
148 get_language_name (enum language lang
)
165 case language_fortran
:
166 return "fortran.lang";
169 /* Not handled by Source Highlight. */
175 case language_pascal
:
176 return "pascal.lang";
178 case language_opencl
:
179 /* Not handled by Source Highlight. */
195 #endif /* HAVE_SOURCE_HIGHLIGHT */
197 /* Try to highlight CONTENTS from file FULLNAME in language LANG using
198 the GNU source-highlight library. Return true if highlighting
202 try_source_highlight (std::string
&contents ATTRIBUTE_UNUSED
,
203 enum language lang ATTRIBUTE_UNUSED
,
204 const std::string
&fullname ATTRIBUTE_UNUSED
)
206 #ifdef HAVE_SOURCE_HIGHLIGHT
207 if (!use_gnu_source_highlight
)
210 const char *lang_name
= get_language_name (lang
);
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
;
218 /* The global source highlight language map object. */
219 static srchilite::LangMap
*langmap
;
224 if (highlighter
== nullptr)
226 highlighter
= new srchilite::SourceHighlight ("esc.outlang");
227 highlighter
->setStyleFile ("esc.style");
229 const std::string
&datadir
= srchilite::Settings::retrieveDataDir ();
230 langmap
= new srchilite::LangMap (datadir
, "lang.map");
233 std::string detected_lang
;
234 if (lang_name
== nullptr)
236 detected_lang
= langmap
->getMappedFileNameFromFileName (fullname
);
237 if (detected_lang
.empty ())
239 lang_name
= detected_lang
.c_str ();
242 std::istringstream
input (contents
);
243 std::ostringstream output
;
244 highlighter
->highlight (input
, output
, lang_name
, fullname
);
245 contents
= std::move (output
).str ();
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. */
260 #endif /* HAVE_SOURCE_HIGHLIGHT */
263 #ifdef HAVE_SOURCE_HIGHLIGHT
267 static void gnu_source_highlight_test ()
269 const std::string prog
275 const std::string fullname
= "test.c";
276 std::string styled_prog
;
279 bool saw_exception
= false;
283 res
= try_source_highlight (styled_prog
, language_c
, fullname
);
285 catch (const gdb_exception
&e
)
287 if (e
.reason
!= RETURN_ERROR
)
289 saw_exception
= true;
293 saw_exception
= true;
296 SELF_CHECK (!saw_exception
);
298 SELF_CHECK (prog
.size () < styled_prog
.size ());
300 SELF_CHECK (prog
== styled_prog
);
303 #endif /* GDB_SELF_TEST */
304 #endif /* HAVE_SOURCE_HIGHLIGHT */
306 /* See source-cache.h. */
309 source_cache::ensure (struct symtab
*s
)
311 std::string fullname
= symtab_to_fullname (s
);
313 size_t size
= m_source_map
.size ();
314 for (int i
= 0; i
< size
; ++i
)
316 if (m_source_map
[i
].fullname
== fullname
)
318 /* This should always hold, because we create the file offsets
319 when reading the file. */
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
327 std::swap (m_source_map
[i
], m_source_map
[size
- 1]);
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)
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 ();
346 std::string contents
;
347 bool styled_p
= false;
350 contents
= get_plain_source_lines (s
, fullname
);
352 catch (const gdb_exception_error
&e
)
354 /* If 's' is not found, an exception is thrown. */
358 if (source_styling
&& gdb_stdout
->can_emit_style_escape ()
359 && m_no_styling_files
.count (fullname
) == 0)
362 = try_source_highlight (contents
, s
->language (), fullname
);
366 std::optional
<std::string
> ext_contents
;
367 ext_contents
= ext_lang_colorize (fullname
, contents
,
369 if (ext_contents
.has_value ())
371 contents
= std::move (*ext_contents
);
378 /* Styling failed. Styling can fail for instance for these
380 - the language is not supported.
381 - the language cannot not be auto-detected from the file name.
382 - no stylers available.
384 Since styling failed, don't try styling the file again after it
385 drops from the cache.
387 Note that clearing the source cache also clears
388 m_no_styling_files. */
389 m_no_styling_files
.insert (fullname
);
394 = { std::move (fullname
), std::move (contents
), styled_p
};
395 m_source_map
.push_back (std::move (result
));
397 if (m_source_map
.size () > MAX_ENTRIES
)
399 auto iter
= m_source_map
.begin ();
400 m_offset_cache
.erase (iter
->fullname
);
401 m_source_map
.erase (iter
);
407 /* See source-cache.h. */
410 source_cache::get_line_charpos (struct symtab
*s
,
411 const std::vector
<off_t
> **offsets
)
413 std::string fullname
= symtab_to_fullname (s
);
415 auto iter
= m_offset_cache
.find (fullname
);
416 if (iter
== m_offset_cache
.end ())
420 iter
= m_offset_cache
.find (fullname
);
421 /* cache_source_text ensured this was entered. */
422 gdb_assert (iter
!= m_offset_cache
.end ());
425 *offsets
= &iter
->second
;
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. */
435 extract_lines (const std::string
&text
, int first_line
, int last_line
,
436 std::string
*lines_out
)
439 std::string::size_type pos
= 0;
440 std::string::size_type first_pos
= std::string::npos
;
442 while (pos
!= std::string::npos
&& lineno
<= last_line
)
444 std::string::size_type new_pos
= text
.find ('\n', pos
);
446 if (lineno
== first_line
)
450 if (lineno
== last_line
|| pos
== std::string::npos
)
452 /* A newline at the end does not start a new line. */
453 if (first_pos
== std::string::npos
454 || first_pos
== text
.size ())
456 if (pos
== std::string::npos
)
460 *lines_out
= text
.substr (first_pos
, pos
- first_pos
);
470 /* See source-cache.h. */
473 source_cache::get_source_lines (struct symtab
*s
, int first_line
,
474 int last_line
, std::string
*lines
)
476 if (first_line
< 1 || last_line
< 1 || first_line
> last_line
)
482 return extract_lines (m_source_map
.back ().contents
,
483 first_line
, last_line
, lines
);
486 /* Implement 'maint flush source-cache' command. */
489 source_cache_flush_command (const char *command
, int from_tty
)
491 forget_cached_source_info ();
492 gdb_printf (_("Source cache flushed.\n"));
498 static void extract_lines_test ()
500 std::string input_text
= "abc\ndef\nghi\njkl\n";
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
)
514 INIT_GDB_FILE (source_cache
)
516 add_cmd ("source-cache", class_maintenance
, source_cache_flush_command
,
517 _("Force gdb to flush its source code cache."),
518 &maintenanceflushlist
);
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
;
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
);
533 /* Adds 'maint set|show gnu-source-highlight enabled'. */
534 add_setshow_boolean_cmd ("enabled", class_maintenance
,
535 &use_gnu_source_highlight
, _("\
536 Set whether the GNU Source Highlight library should be used."), _("\
537 Show whether the GNU Source Highlight library is being used."),_("\
538 When enabled, GDB will use the GNU Source Highlight library to apply\n\
539 styling 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
);
545 /* Enable use of GNU Source Highlight library, if we have it. */
546 #ifdef HAVE_SOURCE_HIGHLIGHT
547 use_gnu_source_highlight
= true;
551 selftests::register_test ("source-cache", selftests::extract_lines_test
);
552 #ifdef HAVE_SOURCE_HIGHLIGHT
553 selftests::register_test ("gnu-source-highlight",
554 selftests::gnu_source_highlight_test
);