1 /* Cache of styled source file text
2 Copyright (C) 2018-2022 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"
25 #include "gdbsupport/selftest.h"
28 #include "cli/cli-cmds.h"
30 #ifdef HAVE_SOURCE_HIGHLIGHT
31 /* If Gnulib redirects 'open' and 'close' to its replacements
32 'rpl_open' and 'rpl_close' via cpp macros, including <fstream>
33 below with those macros in effect will cause unresolved externals
34 when GDB is linked. Happens, e.g., in the MinGW build. */
38 #include <srchilite/sourcehighlight.h>
39 #include <srchilite/langmap.h>
42 /* The number of source files we'll cache. */
46 /* See source-cache.h. */
48 source_cache g_source_cache
;
50 /* See source-cache.h. */
53 source_cache::get_plain_source_lines (struct symtab
*s
,
54 const std::string
&fullname
)
56 scoped_fd
desc (open_source_file (s
));
58 perror_with_name (symtab_to_filename_for_display (s
));
61 if (fstat (desc
.get (), &st
) < 0)
62 perror_with_name (symtab_to_filename_for_display (s
));
65 lines
.resize (st
.st_size
);
66 if (myread (desc
.get (), &lines
[0], lines
.size ()) < 0)
67 perror_with_name (symtab_to_filename_for_display (s
));
70 if (SYMTAB_OBJFILE (s
) != NULL
&& SYMTAB_OBJFILE (s
)->obfd
!= NULL
)
71 mtime
= SYMTAB_OBJFILE (s
)->mtime
;
72 else if (current_program_space
->exec_bfd ())
73 mtime
= current_program_space
->ebfd_mtime
;
75 if (mtime
&& mtime
< st
.st_mtime
)
76 warning (_("Source file is more recent than executable."));
78 std::vector
<off_t
> offsets
;
79 offsets
.push_back (0);
80 for (size_t offset
= lines
.find ('\n');
81 offset
!= std::string::npos
;
82 offset
= lines
.find ('\n', offset
))
85 /* A newline at the end does not start a new line. It would
86 seem simpler to just strip the newline in this function, but
87 then "list" won't print the final newline. */
88 if (offset
!= lines
.size ())
89 offsets
.push_back (offset
);
92 offsets
.shrink_to_fit ();
93 m_offset_cache
.emplace (fullname
, std::move (offsets
));
98 #ifdef HAVE_SOURCE_HIGHLIGHT
100 /* Return the Source Highlight language name, given a gdb language
101 LANG. Returns NULL if the language is not known. */
104 get_language_name (enum language lang
)
121 case language_fortran
:
122 return "fortran.lang";
125 /* Not handled by Source Highlight. */
131 case language_pascal
:
132 return "pascal.lang";
134 case language_opencl
:
135 /* Not handled by Source Highlight. */
151 #endif /* HAVE_SOURCE_HIGHLIGHT */
153 /* See source-cache.h. */
156 source_cache::ensure (struct symtab
*s
)
158 std::string fullname
= symtab_to_fullname (s
);
160 size_t size
= m_source_map
.size ();
161 for (int i
= 0; i
< size
; ++i
)
163 if (m_source_map
[i
].fullname
== fullname
)
165 /* This should always hold, because we create the file
166 offsets when reading the file, and never free them
167 without also clearing the contents cache. */
168 gdb_assert (m_offset_cache
.find (fullname
)
169 != m_offset_cache
.end ());
170 /* Not strictly LRU, but at least ensure that the most
171 recently used entry is always the last candidate for
172 deletion. Note that this property is relied upon by at
175 std::swap (m_source_map
[i
], m_source_map
[size
- 1]);
180 std::string contents
;
183 contents
= get_plain_source_lines (s
, fullname
);
185 catch (const gdb_exception_error
&e
)
187 /* If 's' is not found, an exception is thrown. */
191 if (source_styling
&& gdb_stdout
->can_emit_style_escape ())
193 #ifdef HAVE_SOURCE_HIGHLIGHT
194 bool already_styled
= false;
195 const char *lang_name
= get_language_name (SYMTAB_LANGUAGE (s
));
196 if (lang_name
!= nullptr)
198 /* The global source highlight object, or null if one was
199 never constructed. This is stored here rather than in
200 the class so that we don't need to include anything or do
201 conditional compilation in source-cache.h. */
202 static srchilite::SourceHighlight
*highlighter
;
206 if (highlighter
== nullptr)
208 highlighter
= new srchilite::SourceHighlight ("esc.outlang");
209 highlighter
->setStyleFile ("esc.style");
212 std::istringstream
input (contents
);
213 std::ostringstream output
;
214 highlighter
->highlight (input
, output
, lang_name
, fullname
);
215 contents
= output
.str ();
216 already_styled
= true;
220 /* Source Highlight will throw an exception if
221 highlighting fails. One possible reason it can fail
222 is if the language is unknown -- which matters to gdb
223 because Rust support wasn't added until after 3.1.8.
224 Ignore exceptions here and fall back to
225 un-highlighted text. */
230 #endif /* HAVE_SOURCE_HIGHLIGHT */
232 gdb::optional
<std::string
> ext_contents
;
233 ext_contents
= ext_lang_colorize (fullname
, contents
);
234 if (ext_contents
.has_value ())
235 contents
= std::move (*ext_contents
);
239 source_text result
= { std::move (fullname
), std::move (contents
) };
240 m_source_map
.push_back (std::move (result
));
242 if (m_source_map
.size () > MAX_ENTRIES
)
243 m_source_map
.erase (m_source_map
.begin ());
248 /* See source-cache.h. */
251 source_cache::get_line_charpos (struct symtab
*s
,
252 const std::vector
<off_t
> **offsets
)
254 std::string fullname
= symtab_to_fullname (s
);
256 auto iter
= m_offset_cache
.find (fullname
);
257 if (iter
== m_offset_cache
.end ())
261 iter
= m_offset_cache
.find (fullname
);
262 /* cache_source_text ensured this was entered. */
263 gdb_assert (iter
!= m_offset_cache
.end ());
266 *offsets
= &iter
->second
;
270 /* A helper function that extracts the desired source lines from TEXT,
271 putting them into LINES_OUT. The arguments are as for
272 get_source_lines. Returns true on success, false if the line
273 numbers are invalid. */
276 extract_lines (const std::string
&text
, int first_line
, int last_line
,
277 std::string
*lines_out
)
280 std::string::size_type pos
= 0;
281 std::string::size_type first_pos
= std::string::npos
;
283 while (pos
!= std::string::npos
&& lineno
<= last_line
)
285 std::string::size_type new_pos
= text
.find ('\n', pos
);
287 if (lineno
== first_line
)
291 if (lineno
== last_line
|| pos
== std::string::npos
)
293 /* A newline at the end does not start a new line. */
294 if (first_pos
== std::string::npos
295 || first_pos
== text
.size ())
297 if (pos
== std::string::npos
)
301 *lines_out
= text
.substr (first_pos
, pos
- first_pos
);
311 /* See source-cache.h. */
314 source_cache::get_source_lines (struct symtab
*s
, int first_line
,
315 int last_line
, std::string
*lines
)
317 if (first_line
< 1 || last_line
< 1 || first_line
> last_line
)
323 return extract_lines (m_source_map
.back ().contents
,
324 first_line
, last_line
, lines
);
327 /* Implement 'maint flush source-cache' command. */
330 source_cache_flush_command (const char *command
, int from_tty
)
332 forget_cached_source_info ();
333 printf_filtered (_("Source cache flushed.\n"));
339 static void extract_lines_test ()
341 std::string input_text
= "abc\ndef\nghi\njkl\n";
344 SELF_CHECK (extract_lines (input_text
, 1, 1, &result
)
345 && result
== "abc\n");
346 SELF_CHECK (!extract_lines (input_text
, 2, 1, &result
));
347 SELF_CHECK (extract_lines (input_text
, 1, 2, &result
)
348 && result
== "abc\ndef\n");
349 SELF_CHECK (extract_lines ("abc", 1, 1, &result
)
355 void _initialize_source_cache ();
357 _initialize_source_cache ()
359 add_cmd ("source-cache", class_maintenance
, source_cache_flush_command
,
360 _("Force gdb to flush its source code cache."),
361 &maintenanceflushlist
);
364 selftests::register_test ("source-cache", selftests::extract_lines_test
);