Remove path name from test case
[binutils-gdb.git] / gdb / source-cache.c
1 /* Cache of styled source file text
2 Copyright (C) 2018-2023 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 "objfiles.h"
26 #include "exec.h"
27 #include "cli/cli-cmds.h"
28
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. */
34 #undef open
35 #undef close
36 #include <sstream>
37 #include <srchilite/sourcehighlight.h>
38 #include <srchilite/langmap.h>
39 #include <srchilite/settings.h>
40 #endif
41
42 #if GDB_SELF_TEST
43 #include "gdbsupport/selftest.h"
44 #endif
45
46 /* The number of source files we'll cache. */
47
48 #define MAX_ENTRIES 5
49
50 /* See source-cache.h. */
51
52 source_cache g_source_cache;
53
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. */
57
58 static bool use_gnu_source_highlight;
59
60 /* The "maint show gnu-source-highlight enabled" command. */
61
62 static void
63 show_use_gnu_source_highlight_enabled (struct ui_file *file, int from_tty,
64 struct cmd_list_element *c,
65 const char *value)
66 {
67 gdb_printf (file,
68 _("Use of GNU Source Highlight library is \"%s\".\n"),
69 value);
70 }
71
72 /* The "maint set gnu-source-highlight enabled" command. */
73
74 static void
75 set_use_gnu_source_highlight_enabled (const char *ignore_args,
76 int from_tty,
77 struct cmd_list_element *c)
78 {
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)
83 {
84 use_gnu_source_highlight = false;
85 error (_("the GNU Source Highlight library is not available"));
86 }
87 #else
88 /* We (might) have just changed how we style source code, discard any
89 previously cached contents. */
90 forget_cached_source_info ();
91 #endif
92 }
93
94 /* See source-cache.h. */
95
96 std::string
97 source_cache::get_plain_source_lines (struct symtab *s,
98 const std::string &fullname)
99 {
100 scoped_fd desc (open_source_file (s));
101 if (desc.get () < 0)
102 perror_with_name (symtab_to_filename_for_display (s), -desc.get ());
103
104 struct stat st;
105 if (fstat (desc.get (), &st) < 0)
106 perror_with_name (symtab_to_filename_for_display (s));
107
108 std::string lines;
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));
112
113 time_t mtime = 0;
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;
119
120 if (mtime && mtime < st.st_mtime)
121 warning (_("Source file is more recent than executable."));
122
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))
128 {
129 ++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);
135 }
136
137 offsets.shrink_to_fit ();
138 m_offset_cache.emplace (fullname, std::move (offsets));
139
140 return lines;
141 }
142
143 #ifdef HAVE_SOURCE_HIGHLIGHT
144
145 /* Return the Source Highlight language name, given a gdb language
146 LANG. Returns NULL if the language is not known. */
147
148 static const char *
149 get_language_name (enum language lang)
150 {
151 switch (lang)
152 {
153 case language_c:
154 case language_objc:
155 return "c.lang";
156
157 case language_cplus:
158 return "cpp.lang";
159
160 case language_d:
161 return "d.lang";
162
163 case language_go:
164 return "go.lang";
165
166 case language_fortran:
167 return "fortran.lang";
168
169 case language_m2:
170 /* Not handled by Source Highlight. */
171 break;
172
173 case language_asm:
174 return "asm.lang";
175
176 case language_pascal:
177 return "pascal.lang";
178
179 case language_opencl:
180 /* Not handled by Source Highlight. */
181 break;
182
183 case language_rust:
184 return "rust.lang";
185
186 case language_ada:
187 return "ada.lang";
188
189 default:
190 break;
191 }
192
193 return nullptr;
194 }
195
196 #endif /* HAVE_SOURCE_HIGHLIGHT */
197
198 /* Try to highlight CONTENTS from file FULLNAME in language LANG using
199 the GNU source-higlight library. Return true if highlighting
200 succeeded. */
201
202 static bool
203 try_source_highlight (std::string &contents ATTRIBUTE_UNUSED,
204 enum language lang ATTRIBUTE_UNUSED,
205 const std::string &fullname ATTRIBUTE_UNUSED)
206 {
207 #ifdef HAVE_SOURCE_HIGHLIGHT
208 if (!use_gnu_source_highlight)
209 return false;
210
211 const char *lang_name = get_language_name (lang);
212
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;
218
219 /* The global source highlight language map object. */
220 static srchilite::LangMap *langmap;
221
222 bool styled = false;
223 try
224 {
225 if (highlighter == nullptr)
226 {
227 highlighter = new srchilite::SourceHighlight ("esc.outlang");
228 highlighter->setStyleFile ("esc.style");
229
230 const std::string &datadir = srchilite::Settings::retrieveDataDir ();
231 langmap = new srchilite::LangMap (datadir, "lang.map");
232 }
233
234 std::string detected_lang;
235 if (lang_name == nullptr)
236 {
237 detected_lang = langmap->getMappedFileNameFromFileName (fullname);
238 if (detected_lang.empty ())
239 return false;
240 lang_name = detected_lang.c_str ();
241 }
242
243 std::istringstream input (contents);
244 std::ostringstream output;
245 highlighter->highlight (input, output, lang_name, fullname);
246 contents = std::move (output).str ();
247 styled = true;
248 }
249 catch (...)
250 {
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. */
256 }
257
258 return styled;
259 #else
260 return false;
261 #endif /* HAVE_SOURCE_HIGHLIGHT */
262 }
263
264 #ifdef HAVE_SOURCE_HIGHLIGHT
265 #if GDB_SELF_TEST
266 namespace selftests
267 {
268 static void gnu_source_highlight_test ()
269 {
270 const std::string prog
271 = ("int\n"
272 "foo (void)\n"
273 "{\n"
274 " return 0;\n"
275 "}\n");
276 const std::string fullname = "test.c";
277 std::string styled_prog;
278
279 bool res = false;
280 bool saw_exception = false;
281 styled_prog = prog;
282 try
283 {
284 res = try_source_highlight (styled_prog, language_c, fullname);
285 }
286 catch (...)
287 {
288 saw_exception = true;
289 }
290
291 SELF_CHECK (!saw_exception);
292 if (res)
293 SELF_CHECK (prog.size () < styled_prog.size ());
294 else
295 SELF_CHECK (prog == styled_prog);
296 }
297 }
298 #endif /* GDB_SELF_TEST */
299 #endif /* HAVE_SOURCE_HIGHLIGHT */
300
301 /* See source-cache.h. */
302
303 bool
304 source_cache::ensure (struct symtab *s)
305 {
306 std::string fullname = symtab_to_fullname (s);
307
308 size_t size = m_source_map.size ();
309 for (int i = 0; i < size; ++i)
310 {
311 if (m_source_map[i].fullname == fullname)
312 {
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
320 least one caller. */
321 if (i != size - 1)
322 std::swap (m_source_map[i], m_source_map[size - 1]);
323 return true;
324 }
325 }
326
327 std::string contents;
328 try
329 {
330 contents = get_plain_source_lines (s, fullname);
331 }
332 catch (const gdb_exception_error &e)
333 {
334 /* If 's' is not found, an exception is thrown. */
335 return false;
336 }
337
338 if (source_styling && gdb_stdout->can_emit_style_escape ()
339 && m_no_styling_files.count (fullname) == 0)
340 {
341 bool already_styled
342 = try_source_highlight (contents, s->language (), fullname);
343
344 if (!already_styled)
345 {
346 gdb::optional<std::string> ext_contents;
347 ext_contents = ext_lang_colorize (fullname, contents);
348 if (ext_contents.has_value ())
349 {
350 contents = std::move (*ext_contents);
351 already_styled = true;
352 }
353 }
354
355 if (!already_styled)
356 {
357 /* Styling failed. Styling can fail for instance for these
358 reasons:
359 - the language is not supported.
360 - the language cannot not be auto-detected from the file name.
361 - no stylers available.
362
363 Since styling failed, don't try styling the file again after it
364 drops from the cache.
365
366 Note that clearing the source cache also clears
367 m_no_styling_files. */
368 m_no_styling_files.insert (fullname);
369 }
370 }
371
372 source_text result = { std::move (fullname), std::move (contents) };
373 m_source_map.push_back (std::move (result));
374
375 if (m_source_map.size () > MAX_ENTRIES)
376 {
377 auto iter = m_source_map.begin ();
378 m_offset_cache.erase (iter->fullname);
379 m_source_map.erase (iter);
380 }
381
382 return true;
383 }
384
385 /* See source-cache.h. */
386
387 bool
388 source_cache::get_line_charpos (struct symtab *s,
389 const std::vector<off_t> **offsets)
390 {
391 std::string fullname = symtab_to_fullname (s);
392
393 auto iter = m_offset_cache.find (fullname);
394 if (iter == m_offset_cache.end ())
395 {
396 if (!ensure (s))
397 return false;
398 iter = m_offset_cache.find (fullname);
399 /* cache_source_text ensured this was entered. */
400 gdb_assert (iter != m_offset_cache.end ());
401 }
402
403 *offsets = &iter->second;
404 return true;
405 }
406
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. */
411
412 static bool
413 extract_lines (const std::string &text, int first_line, int last_line,
414 std::string *lines_out)
415 {
416 int lineno = 1;
417 std::string::size_type pos = 0;
418 std::string::size_type first_pos = std::string::npos;
419
420 while (pos != std::string::npos && lineno <= last_line)
421 {
422 std::string::size_type new_pos = text.find ('\n', pos);
423
424 if (lineno == first_line)
425 first_pos = pos;
426
427 pos = new_pos;
428 if (lineno == last_line || pos == std::string::npos)
429 {
430 /* A newline at the end does not start a new line. */
431 if (first_pos == std::string::npos
432 || first_pos == text.size ())
433 return false;
434 if (pos == std::string::npos)
435 pos = text.size ();
436 else
437 ++pos;
438 *lines_out = text.substr (first_pos, pos - first_pos);
439 return true;
440 }
441 ++lineno;
442 ++pos;
443 }
444
445 return false;
446 }
447
448 /* See source-cache.h. */
449
450 bool
451 source_cache::get_source_lines (struct symtab *s, int first_line,
452 int last_line, std::string *lines)
453 {
454 if (first_line < 1 || last_line < 1 || first_line > last_line)
455 return false;
456
457 if (!ensure (s))
458 return false;
459
460 return extract_lines (m_source_map.back ().contents,
461 first_line, last_line, lines);
462 }
463
464 /* Implement 'maint flush source-cache' command. */
465
466 static void
467 source_cache_flush_command (const char *command, int from_tty)
468 {
469 forget_cached_source_info ();
470 gdb_printf (_("Source cache flushed.\n"));
471 }
472
473 #if GDB_SELF_TEST
474 namespace selftests
475 {
476 static void extract_lines_test ()
477 {
478 std::string input_text = "abc\ndef\nghi\njkl\n";
479 std::string result;
480
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)
487 && result == "abc");
488 }
489 }
490 #endif
491
492 void _initialize_source_cache ();
493 void
494 _initialize_source_cache ()
495 {
496 add_cmd ("source-cache", class_maintenance, source_cache_flush_command,
497 _("Force gdb to flush its source code cache."),
498 &maintenanceflushlist);
499
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;
503
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);
512
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);
524
525 /* Enable use of GNU Source Highlight library, if we have it. */
526 #ifdef HAVE_SOURCE_HIGHLIGHT
527 use_gnu_source_highlight = true;
528 #endif
529
530 #if GDB_SELF_TEST
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);
535 #endif
536 #endif
537 }