1 /* Cache of styled source file text
2 Copyright (C) 2018-2023 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/>. */
20 #include "source-cache.h"
21 #include "gdbsupport/scoped_fd.h"
23 #include "cli/cli-style.h"
27 #include "cli/cli-cmds.h"
29 #ifdef HAVE_SOURCE_HIGHLIGHT
30 /* If Gnulib redirects 'open' and 'close' to its replacements
31 'rpl_open' and 'rpl_close' via cpp macros, including <fstream>
32 below with those macros in effect will cause unresolved externals
33 when GDB is linked. Happens, e.g., in the MinGW build. */
37 #include <srchilite/sourcehighlight.h>
38 #include <srchilite/langmap.h>
39 #include <srchilite/settings.h>
43 #include "gdbsupport/selftest.h"
46 /* The number of source files we'll cache. */
50 /* See source-cache.h. */
52 source_cache g_source_cache
;
54 /* When this is true we will use the GNU Source Highlight to add styling to
55 source code (assuming the library is available). This is initialized to
56 true (if appropriate) in _initialize_source_cache below. */
58 static bool use_gnu_source_highlight
;
60 /* The "maint show gnu-source-highlight enabled" command. */
63 show_use_gnu_source_highlight_enabled (struct ui_file
*file
, int from_tty
,
64 struct cmd_list_element
*c
,
68 _("Use of GNU Source Highlight library is \"%s\".\n"),
72 /* The "maint set gnu-source-highlight enabled" command. */
75 set_use_gnu_source_highlight_enabled (const char *ignore_args
,
77 struct cmd_list_element
*c
)
79 #ifndef HAVE_SOURCE_HIGHLIGHT
80 /* If the library is not available and the user tried to enable use of
81 the library, then disable use of the library, and give an error. */
82 if (use_gnu_source_highlight
)
84 use_gnu_source_highlight
= false;
85 error (_("the GNU Source Highlight library is not available"));
88 /* We (might) have just changed how we style source code, discard any
89 previously cached contents. */
90 forget_cached_source_info ();
94 /* See source-cache.h. */
97 source_cache::get_plain_source_lines (struct symtab
*s
,
98 const std::string
&fullname
)
100 scoped_fd
desc (open_source_file (s
));
102 perror_with_name (symtab_to_filename_for_display (s
), -desc
.get ());
105 if (fstat (desc
.get (), &st
) < 0)
106 perror_with_name (symtab_to_filename_for_display (s
));
109 lines
.resize (st
.st_size
);
110 if (myread (desc
.get (), &lines
[0], lines
.size ()) < 0)
111 perror_with_name (symtab_to_filename_for_display (s
));
114 if (s
->compunit ()->objfile () != NULL
115 && s
->compunit ()->objfile ()->obfd
!= NULL
)
116 mtime
= s
->compunit ()->objfile ()->mtime
;
117 else if (current_program_space
->exec_bfd ())
118 mtime
= current_program_space
->ebfd_mtime
;
120 if (mtime
&& mtime
< st
.st_mtime
)
121 warning (_("Source file is more recent than executable."));
123 std::vector
<off_t
> offsets
;
124 offsets
.push_back (0);
125 for (size_t offset
= lines
.find ('\n');
126 offset
!= std::string::npos
;
127 offset
= lines
.find ('\n', offset
))
130 /* A newline at the end does not start a new line. It would
131 seem simpler to just strip the newline in this function, but
132 then "list" won't print the final newline. */
133 if (offset
!= lines
.size ())
134 offsets
.push_back (offset
);
137 offsets
.shrink_to_fit ();
138 m_offset_cache
.emplace (fullname
, std::move (offsets
));
143 #ifdef HAVE_SOURCE_HIGHLIGHT
145 /* Return the Source Highlight language name, given a gdb language
146 LANG. Returns NULL if the language is not known. */
149 get_language_name (enum language lang
)
166 case language_fortran
:
167 return "fortran.lang";
170 /* Not handled by Source Highlight. */
176 case language_pascal
:
177 return "pascal.lang";
179 case language_opencl
:
180 /* Not handled by Source Highlight. */
196 #endif /* HAVE_SOURCE_HIGHLIGHT */
198 /* Try to highlight CONTENTS from file FULLNAME in language LANG using
199 the GNU source-higlight library. Return true if highlighting
203 try_source_highlight (std::string
&contents ATTRIBUTE_UNUSED
,
204 enum language lang ATTRIBUTE_UNUSED
,
205 const std::string
&fullname ATTRIBUTE_UNUSED
)
207 #ifdef HAVE_SOURCE_HIGHLIGHT
208 if (!use_gnu_source_highlight
)
211 const char *lang_name
= get_language_name (lang
);
213 /* The global source highlight object, or null if one was
214 never constructed. This is stored here rather than in
215 the class so that we don't need to include anything or do
216 conditional compilation in source-cache.h. */
217 static srchilite::SourceHighlight
*highlighter
;
219 /* The global source highlight language map object. */
220 static srchilite::LangMap
*langmap
;
225 if (highlighter
== nullptr)
227 highlighter
= new srchilite::SourceHighlight ("esc.outlang");
228 highlighter
->setStyleFile ("esc.style");
230 const std::string
&datadir
= srchilite::Settings::retrieveDataDir ();
231 langmap
= new srchilite::LangMap (datadir
, "lang.map");
234 std::string detected_lang
;
235 if (lang_name
== nullptr)
237 detected_lang
= langmap
->getMappedFileNameFromFileName (fullname
);
238 if (detected_lang
.empty ())
240 lang_name
= detected_lang
.c_str ();
243 std::istringstream
input (contents
);
244 std::ostringstream output
;
245 highlighter
->highlight (input
, output
, lang_name
, fullname
);
246 contents
= std::move (output
).str ();
251 /* Source Highlight will throw an exception if
252 highlighting fails. One possible reason it can fail
253 is if the language is unknown -- which matters to gdb
254 because Rust support wasn't added until after 3.1.8.
255 Ignore exceptions here. */
261 #endif /* HAVE_SOURCE_HIGHLIGHT */
264 #ifdef HAVE_SOURCE_HIGHLIGHT
268 static void gnu_source_highlight_test ()
270 const std::string prog
276 const std::string fullname
= "test.c";
277 std::string styled_prog
;
280 bool saw_exception
= false;
284 res
= try_source_highlight (styled_prog
, language_c
, fullname
);
288 saw_exception
= true;
291 SELF_CHECK (!saw_exception
);
293 SELF_CHECK (prog
.size () < styled_prog
.size ());
295 SELF_CHECK (prog
== styled_prog
);
298 #endif /* GDB_SELF_TEST */
299 #endif /* HAVE_SOURCE_HIGHLIGHT */
301 /* See source-cache.h. */
304 source_cache::ensure (struct symtab
*s
)
306 std::string fullname
= symtab_to_fullname (s
);
308 size_t size
= m_source_map
.size ();
309 for (int i
= 0; i
< size
; ++i
)
311 if (m_source_map
[i
].fullname
== fullname
)
313 /* This should always hold, because we create the file offsets
314 when reading the file. */
315 gdb_assert (m_offset_cache
.find (fullname
)
316 != m_offset_cache
.end ());
317 /* Not strictly LRU, but at least ensure that the most
318 recently used entry is always the last candidate for
319 deletion. Note that this property is relied upon by at
322 std::swap (m_source_map
[i
], m_source_map
[size
- 1]);
327 std::string contents
;
330 contents
= get_plain_source_lines (s
, fullname
);
332 catch (const gdb_exception_error
&e
)
334 /* If 's' is not found, an exception is thrown. */
338 if (source_styling
&& gdb_stdout
->can_emit_style_escape ()
339 && m_no_styling_files
.count (fullname
) == 0)
342 = try_source_highlight (contents
, s
->language (), fullname
);
346 gdb::optional
<std::string
> ext_contents
;
347 ext_contents
= ext_lang_colorize (fullname
, contents
);
348 if (ext_contents
.has_value ())
350 contents
= std::move (*ext_contents
);
351 already_styled
= true;
357 /* Styling failed. Styling can fail for instance for these
359 - the language is not supported.
360 - the language cannot not be auto-detected from the file name.
361 - no stylers available.
363 Since styling failed, don't try styling the file again after it
364 drops from the cache.
366 Note that clearing the source cache also clears
367 m_no_styling_files. */
368 m_no_styling_files
.insert (fullname
);
372 source_text result
= { std::move (fullname
), std::move (contents
) };
373 m_source_map
.push_back (std::move (result
));
375 if (m_source_map
.size () > MAX_ENTRIES
)
377 auto iter
= m_source_map
.begin ();
378 m_offset_cache
.erase (iter
->fullname
);
379 m_source_map
.erase (iter
);
385 /* See source-cache.h. */
388 source_cache::get_line_charpos (struct symtab
*s
,
389 const std::vector
<off_t
> **offsets
)
391 std::string fullname
= symtab_to_fullname (s
);
393 auto iter
= m_offset_cache
.find (fullname
);
394 if (iter
== m_offset_cache
.end ())
398 iter
= m_offset_cache
.find (fullname
);
399 /* cache_source_text ensured this was entered. */
400 gdb_assert (iter
!= m_offset_cache
.end ());
403 *offsets
= &iter
->second
;
407 /* A helper function that extracts the desired source lines from TEXT,
408 putting them into LINES_OUT. The arguments are as for
409 get_source_lines. Returns true on success, false if the line
410 numbers are invalid. */
413 extract_lines (const std::string
&text
, int first_line
, int last_line
,
414 std::string
*lines_out
)
417 std::string::size_type pos
= 0;
418 std::string::size_type first_pos
= std::string::npos
;
420 while (pos
!= std::string::npos
&& lineno
<= last_line
)
422 std::string::size_type new_pos
= text
.find ('\n', pos
);
424 if (lineno
== first_line
)
428 if (lineno
== last_line
|| pos
== std::string::npos
)
430 /* A newline at the end does not start a new line. */
431 if (first_pos
== std::string::npos
432 || first_pos
== text
.size ())
434 if (pos
== std::string::npos
)
438 *lines_out
= text
.substr (first_pos
, pos
- first_pos
);
448 /* See source-cache.h. */
451 source_cache::get_source_lines (struct symtab
*s
, int first_line
,
452 int last_line
, std::string
*lines
)
454 if (first_line
< 1 || last_line
< 1 || first_line
> last_line
)
460 return extract_lines (m_source_map
.back ().contents
,
461 first_line
, last_line
, lines
);
464 /* Implement 'maint flush source-cache' command. */
467 source_cache_flush_command (const char *command
, int from_tty
)
469 forget_cached_source_info ();
470 gdb_printf (_("Source cache flushed.\n"));
476 static void extract_lines_test ()
478 std::string input_text
= "abc\ndef\nghi\njkl\n";
481 SELF_CHECK (extract_lines (input_text
, 1, 1, &result
)
482 && result
== "abc\n");
483 SELF_CHECK (!extract_lines (input_text
, 2, 1, &result
));
484 SELF_CHECK (extract_lines (input_text
, 1, 2, &result
)
485 && result
== "abc\ndef\n");
486 SELF_CHECK (extract_lines ("abc", 1, 1, &result
)
492 void _initialize_source_cache ();
494 _initialize_source_cache ()
496 add_cmd ("source-cache", class_maintenance
, source_cache_flush_command
,
497 _("Force gdb to flush its source code cache."),
498 &maintenanceflushlist
);
500 /* All the 'maint set|show gnu-source-highlight' sub-commands. */
501 static struct cmd_list_element
*maint_set_gnu_source_highlight_cmdlist
;
502 static struct cmd_list_element
*maint_show_gnu_source_highlight_cmdlist
;
504 /* Adds 'maint set|show gnu-source-highlight'. */
505 add_setshow_prefix_cmd ("gnu-source-highlight", class_maintenance
,
506 _("Set gnu-source-highlight specific variables."),
507 _("Show gnu-source-highlight specific variables."),
508 &maint_set_gnu_source_highlight_cmdlist
,
509 &maint_show_gnu_source_highlight_cmdlist
,
510 &maintenance_set_cmdlist
,
511 &maintenance_show_cmdlist
);
513 /* Adds 'maint set|show gnu-source-highlight enabled'. */
514 add_setshow_boolean_cmd ("enabled", class_maintenance
,
515 &use_gnu_source_highlight
, _("\
516 Set whether the GNU Source Highlight library should be used."), _("\
517 Show whether the GNU Source Highlight library is being used."),_("\
518 When enabled, GDB will use the GNU Source Highlight library to apply\n\
519 styling to source code lines that are shown."),
520 set_use_gnu_source_highlight_enabled
,
521 show_use_gnu_source_highlight_enabled
,
522 &maint_set_gnu_source_highlight_cmdlist
,
523 &maint_show_gnu_source_highlight_cmdlist
);
525 /* Enable use of GNU Source Highlight library, if we have it. */
526 #ifdef HAVE_SOURCE_HIGHLIGHT
527 use_gnu_source_highlight
= true;
531 selftests::register_test ("source-cache", selftests::extract_lines_test
);
532 #ifdef HAVE_SOURCE_HIGHLIGHT
533 selftests::register_test ("gnu-source-highlight",
534 selftests::gnu_source_highlight_test
);