gdb: erase items from the source_cache::m_offset_cache
[binutils-gdb.git] / gdb / source-cache.c
1 /* Cache of styled source file text
2 Copyright (C) 2018-2022 Free Software Foundation, Inc.
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
19 #include "defs.h"
20 #include "source-cache.h"
21 #include "gdbsupport/scoped_fd.h"
22 #include "source.h"
23 #include "cli/cli-style.h"
24 #include "symtab.h"
25 #include "gdbsupport/selftest.h"
26 #include "objfiles.h"
27 #include "exec.h"
28 #include "cli/cli-cmds.h"
29
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. */
35 #undef open
36 #undef close
37 #include <sstream>
38 #include <srchilite/sourcehighlight.h>
39 #include <srchilite/langmap.h>
40 #endif
41
42 /* The number of source files we'll cache. */
43
44 #define MAX_ENTRIES 5
45
46 /* See source-cache.h. */
47
48 source_cache g_source_cache;
49
50 /* See source-cache.h. */
51
52 std::string
53 source_cache::get_plain_source_lines (struct symtab *s,
54 const std::string &fullname)
55 {
56 scoped_fd desc (open_source_file (s));
57 if (desc.get () < 0)
58 perror_with_name (symtab_to_filename_for_display (s));
59
60 struct stat st;
61 if (fstat (desc.get (), &st) < 0)
62 perror_with_name (symtab_to_filename_for_display (s));
63
64 std::string lines;
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));
68
69 time_t mtime = 0;
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;
74
75 if (mtime && mtime < st.st_mtime)
76 warning (_("Source file is more recent than executable."));
77
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))
83 {
84 ++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);
90 }
91
92 offsets.shrink_to_fit ();
93 m_offset_cache.emplace (fullname, std::move (offsets));
94
95 return lines;
96 }
97
98 #ifdef HAVE_SOURCE_HIGHLIGHT
99
100 /* Return the Source Highlight language name, given a gdb language
101 LANG. Returns NULL if the language is not known. */
102
103 static const char *
104 get_language_name (enum language lang)
105 {
106 switch (lang)
107 {
108 case language_c:
109 case language_objc:
110 return "c.lang";
111
112 case language_cplus:
113 return "cpp.lang";
114
115 case language_d:
116 return "d.lang";
117
118 case language_go:
119 return "go.lang";
120
121 case language_fortran:
122 return "fortran.lang";
123
124 case language_m2:
125 /* Not handled by Source Highlight. */
126 break;
127
128 case language_asm:
129 return "asm.lang";
130
131 case language_pascal:
132 return "pascal.lang";
133
134 case language_opencl:
135 /* Not handled by Source Highlight. */
136 break;
137
138 case language_rust:
139 return "rust.lang";
140
141 case language_ada:
142 return "ada.lang";
143
144 default:
145 break;
146 }
147
148 return nullptr;
149 }
150
151 #endif /* HAVE_SOURCE_HIGHLIGHT */
152
153 /* See source-cache.h. */
154
155 bool
156 source_cache::ensure (struct symtab *s)
157 {
158 std::string fullname = symtab_to_fullname (s);
159
160 size_t size = m_source_map.size ();
161 for (int i = 0; i < size; ++i)
162 {
163 if (m_source_map[i].fullname == fullname)
164 {
165 /* This should always hold, because we create the file offsets
166 when reading the file. */
167 gdb_assert (m_offset_cache.find (fullname)
168 != m_offset_cache.end ());
169 /* Not strictly LRU, but at least ensure that the most
170 recently used entry is always the last candidate for
171 deletion. Note that this property is relied upon by at
172 least one caller. */
173 if (i != size - 1)
174 std::swap (m_source_map[i], m_source_map[size - 1]);
175 return true;
176 }
177 }
178
179 std::string contents;
180 try
181 {
182 contents = get_plain_source_lines (s, fullname);
183 }
184 catch (const gdb_exception_error &e)
185 {
186 /* If 's' is not found, an exception is thrown. */
187 return false;
188 }
189
190 if (source_styling && gdb_stdout->can_emit_style_escape ())
191 {
192 #ifdef HAVE_SOURCE_HIGHLIGHT
193 bool already_styled = false;
194 const char *lang_name = get_language_name (SYMTAB_LANGUAGE (s));
195 if (lang_name != nullptr)
196 {
197 /* The global source highlight object, or null if one was
198 never constructed. This is stored here rather than in
199 the class so that we don't need to include anything or do
200 conditional compilation in source-cache.h. */
201 static srchilite::SourceHighlight *highlighter;
202
203 try
204 {
205 if (highlighter == nullptr)
206 {
207 highlighter = new srchilite::SourceHighlight ("esc.outlang");
208 highlighter->setStyleFile ("esc.style");
209 }
210
211 std::istringstream input (contents);
212 std::ostringstream output;
213 highlighter->highlight (input, output, lang_name, fullname);
214 contents = output.str ();
215 already_styled = true;
216 }
217 catch (...)
218 {
219 /* Source Highlight will throw an exception if
220 highlighting fails. One possible reason it can fail
221 is if the language is unknown -- which matters to gdb
222 because Rust support wasn't added until after 3.1.8.
223 Ignore exceptions here and fall back to
224 un-highlighted text. */
225 }
226 }
227
228 if (!already_styled)
229 #endif /* HAVE_SOURCE_HIGHLIGHT */
230 {
231 gdb::optional<std::string> ext_contents;
232 ext_contents = ext_lang_colorize (fullname, contents);
233 if (ext_contents.has_value ())
234 contents = std::move (*ext_contents);
235 }
236 }
237
238 source_text result = { std::move (fullname), std::move (contents) };
239 m_source_map.push_back (std::move (result));
240
241 if (m_source_map.size () > MAX_ENTRIES)
242 {
243 auto iter = m_source_map.begin ();
244 m_offset_cache.erase (iter->fullname);
245 m_source_map.erase (iter);
246 }
247
248 return true;
249 }
250
251 /* See source-cache.h. */
252
253 bool
254 source_cache::get_line_charpos (struct symtab *s,
255 const std::vector<off_t> **offsets)
256 {
257 std::string fullname = symtab_to_fullname (s);
258
259 auto iter = m_offset_cache.find (fullname);
260 if (iter == m_offset_cache.end ())
261 {
262 if (!ensure (s))
263 return false;
264 iter = m_offset_cache.find (fullname);
265 /* cache_source_text ensured this was entered. */
266 gdb_assert (iter != m_offset_cache.end ());
267 }
268
269 *offsets = &iter->second;
270 return true;
271 }
272
273 /* A helper function that extracts the desired source lines from TEXT,
274 putting them into LINES_OUT. The arguments are as for
275 get_source_lines. Returns true on success, false if the line
276 numbers are invalid. */
277
278 static bool
279 extract_lines (const std::string &text, int first_line, int last_line,
280 std::string *lines_out)
281 {
282 int lineno = 1;
283 std::string::size_type pos = 0;
284 std::string::size_type first_pos = std::string::npos;
285
286 while (pos != std::string::npos && lineno <= last_line)
287 {
288 std::string::size_type new_pos = text.find ('\n', pos);
289
290 if (lineno == first_line)
291 first_pos = pos;
292
293 pos = new_pos;
294 if (lineno == last_line || pos == std::string::npos)
295 {
296 /* A newline at the end does not start a new line. */
297 if (first_pos == std::string::npos
298 || first_pos == text.size ())
299 return false;
300 if (pos == std::string::npos)
301 pos = text.size ();
302 else
303 ++pos;
304 *lines_out = text.substr (first_pos, pos - first_pos);
305 return true;
306 }
307 ++lineno;
308 ++pos;
309 }
310
311 return false;
312 }
313
314 /* See source-cache.h. */
315
316 bool
317 source_cache::get_source_lines (struct symtab *s, int first_line,
318 int last_line, std::string *lines)
319 {
320 if (first_line < 1 || last_line < 1 || first_line > last_line)
321 return false;
322
323 if (!ensure (s))
324 return false;
325
326 return extract_lines (m_source_map.back ().contents,
327 first_line, last_line, lines);
328 }
329
330 /* Implement 'maint flush source-cache' command. */
331
332 static void
333 source_cache_flush_command (const char *command, int from_tty)
334 {
335 forget_cached_source_info ();
336 printf_filtered (_("Source cache flushed.\n"));
337 }
338
339 #if GDB_SELF_TEST
340 namespace selftests
341 {
342 static void extract_lines_test ()
343 {
344 std::string input_text = "abc\ndef\nghi\njkl\n";
345 std::string result;
346
347 SELF_CHECK (extract_lines (input_text, 1, 1, &result)
348 && result == "abc\n");
349 SELF_CHECK (!extract_lines (input_text, 2, 1, &result));
350 SELF_CHECK (extract_lines (input_text, 1, 2, &result)
351 && result == "abc\ndef\n");
352 SELF_CHECK (extract_lines ("abc", 1, 1, &result)
353 && result == "abc");
354 }
355 }
356 #endif
357
358 void _initialize_source_cache ();
359 void
360 _initialize_source_cache ()
361 {
362 add_cmd ("source-cache", class_maintenance, source_cache_flush_command,
363 _("Force gdb to flush its source code cache."),
364 &maintenanceflushlist);
365
366 #if GDB_SELF_TEST
367 selftests::register_test ("source-cache", selftests::extract_lines_test);
368 #endif
369 }