]> git.ipfire.org Git - thirdparty/binutils-gdb.git/blame - gdb/source-cache.c
[gdb/cli] Allow source-highlight to autodetect language
[thirdparty/binutils-gdb.git] / gdb / source-cache.c
CommitLineData
62f29fda 1/* Cache of styled source file text
213516ef 2 Copyright (C) 2018-2023 Free Software Foundation, Inc.
62f29fda
TT
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"
268a13a5 21#include "gdbsupport/scoped_fd.h"
62f29fda
TT
22#include "source.h"
23#include "cli/cli-style.h"
0d12e84c 24#include "symtab.h"
269249d9 25#include "gdbsupport/selftest.h"
cb44333d
TT
26#include "objfiles.h"
27#include "exec.h"
39370778 28#include "cli/cli-cmds.h"
62f29fda
TT
29
30#ifdef HAVE_SOURCE_HIGHLIGHT
3a350822
EZ
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
62f29fda
TT
37#include <sstream>
38#include <srchilite/sourcehighlight.h>
39#include <srchilite/langmap.h>
eefa43c9 40#include <srchilite/settings.h>
62f29fda
TT
41#endif
42
43/* The number of source files we'll cache. */
44
45#define MAX_ENTRIES 5
46
47/* See source-cache.h. */
48
49source_cache g_source_cache;
50
643b1268
AB
51/* When this is true we will use the GNU Source Highlight to add styling to
52 source code (assuming the library is available). This is initialized to
53 true (if appropriate) in _initialize_source_cache below. */
54
55static bool use_gnu_source_highlight;
56
57/* The "maint show gnu-source-highlight enabled" command. */
58
59static void
60show_use_gnu_source_highlight_enabled (struct ui_file *file, int from_tty,
61 struct cmd_list_element *c,
62 const char *value)
63{
6cb06a8c
TT
64 gdb_printf (file,
65 _("Use of GNU Source Highlight library is \"%s\".\n"),
66 value);
643b1268
AB
67}
68
69/* The "maint set gnu-source-highlight enabled" command. */
70
71static void
72set_use_gnu_source_highlight_enabled (const char *ignore_args,
73 int from_tty,
74 struct cmd_list_element *c)
75{
76#ifndef HAVE_SOURCE_HIGHLIGHT
77 /* If the library is not available and the user tried to enable use of
78 the library, then disable use of the library, and give an error. */
79 if (use_gnu_source_highlight)
80 {
81 use_gnu_source_highlight = false;
82 error (_("the GNU Source Highlight library is not available"));
83 }
84#else
85 /* We (might) have just changed how we style source code, discard any
86 previously cached contents. */
87 forget_cached_source_info ();
88#endif
89}
90
62f29fda
TT
91/* See source-cache.h. */
92
cb44333d
TT
93std::string
94source_cache::get_plain_source_lines (struct symtab *s,
95 const std::string &fullname)
62f29fda 96{
cb44333d 97 scoped_fd desc (open_source_file (s));
62f29fda 98 if (desc.get () < 0)
8cc96ee4 99 perror_with_name (symtab_to_filename_for_display (s), -desc.get ());
62f29fda 100
872dceaa 101 struct stat st;
872dceaa 102 if (fstat (desc.get (), &st) < 0)
62f29fda
TT
103 perror_with_name (symtab_to_filename_for_display (s));
104
cb44333d
TT
105 std::string lines;
106 lines.resize (st.st_size);
107 if (myread (desc.get (), &lines[0], lines.size ()) < 0)
62f29fda
TT
108 perror_with_name (symtab_to_filename_for_display (s));
109
cb44333d 110 time_t mtime = 0;
3c86fae3
SM
111 if (s->compunit ()->objfile () != NULL
112 && s->compunit ()->objfile ()->obfd != NULL)
113 mtime = s->compunit ()->objfile ()->mtime;
7e10abd1 114 else if (current_program_space->exec_bfd ())
5a36e715 115 mtime = current_program_space->ebfd_mtime;
62f29fda 116
cb44333d
TT
117 if (mtime && mtime < st.st_mtime)
118 warning (_("Source file is more recent than executable."));
62f29fda 119
cb44333d
TT
120 std::vector<off_t> offsets;
121 offsets.push_back (0);
122 for (size_t offset = lines.find ('\n');
123 offset != std::string::npos;
124 offset = lines.find ('\n', offset))
62f29fda 125 {
cb44333d
TT
126 ++offset;
127 /* A newline at the end does not start a new line. It would
128 seem simpler to just strip the newline in this function, but
129 then "list" won't print the final newline. */
130 if (offset != lines.size ())
131 offsets.push_back (offset);
62f29fda
TT
132 }
133
cb44333d
TT
134 offsets.shrink_to_fit ();
135 m_offset_cache.emplace (fullname, std::move (offsets));
136
137 return lines;
62f29fda
TT
138}
139
64c45143
TT
140#ifdef HAVE_SOURCE_HIGHLIGHT
141
62f29fda
TT
142/* Return the Source Highlight language name, given a gdb language
143 LANG. Returns NULL if the language is not known. */
144
145static const char *
146get_language_name (enum language lang)
147{
148 switch (lang)
149 {
150 case language_c:
151 case language_objc:
152 return "c.lang";
153
154 case language_cplus:
155 return "cpp.lang";
156
157 case language_d:
158 return "d.lang";
159
160 case language_go:
161 return "go.lang";
162
163 case language_fortran:
164 return "fortran.lang";
165
166 case language_m2:
167 /* Not handled by Source Highlight. */
168 break;
169
170 case language_asm:
171 return "asm.lang";
172
173 case language_pascal:
174 return "pascal.lang";
175
176 case language_opencl:
177 /* Not handled by Source Highlight. */
178 break;
179
180 case language_rust:
d806ea2d 181 return "rust.lang";
62f29fda
TT
182
183 case language_ada:
184 return "ada.lang";
185
186 default:
187 break;
188 }
189
190 return nullptr;
191}
192
64c45143
TT
193#endif /* HAVE_SOURCE_HIGHLIGHT */
194
62dfd02e
TV
195/* Try to highlight CONTENTS from file FULLNAME in language LANG using
196 the GNU source-higlight library. Return true if highlighting
197 succeeded. */
198
199static bool
200try_source_highlight (std::string &contents ATTRIBUTE_UNUSED,
201 enum language lang ATTRIBUTE_UNUSED,
202 const std::string &fullname ATTRIBUTE_UNUSED)
203{
204#ifdef HAVE_SOURCE_HIGHLIGHT
205 if (!use_gnu_source_highlight)
206 return false;
207
208 const char *lang_name = get_language_name (lang);
62dfd02e
TV
209
210 /* The global source highlight object, or null if one was
211 never constructed. This is stored here rather than in
212 the class so that we don't need to include anything or do
213 conditional compilation in source-cache.h. */
214 static srchilite::SourceHighlight *highlighter;
215
eefa43c9
TV
216 /* The global source highlight language map object. */
217 static srchilite::LangMap *langmap;
218
62dfd02e
TV
219 bool styled = false;
220 try
221 {
222 if (highlighter == nullptr)
223 {
224 highlighter = new srchilite::SourceHighlight ("esc.outlang");
225 highlighter->setStyleFile ("esc.style");
eefa43c9
TV
226
227 const std::string &datadir = srchilite::Settings::retrieveDataDir ();
228 langmap = new srchilite::LangMap (datadir, "lang.map");
229 }
230
231 std::string detected_lang;
232 if (lang_name == nullptr)
233 {
234 detected_lang = langmap->getMappedFileNameFromFileName (fullname);
235 if (detected_lang.empty ())
236 return false;
237 lang_name = detected_lang.c_str ();
62dfd02e
TV
238 }
239
240 std::istringstream input (contents);
241 std::ostringstream output;
242 highlighter->highlight (input, output, lang_name, fullname);
243 contents = std::move (output).str ();
244 styled = true;
245 }
246 catch (...)
247 {
248 /* Source Highlight will throw an exception if
249 highlighting fails. One possible reason it can fail
250 is if the language is unknown -- which matters to gdb
251 because Rust support wasn't added until after 3.1.8.
252 Ignore exceptions here. */
253 }
254
255 return styled;
256#else
257 return false;
258#endif /* HAVE_SOURCE_HIGHLIGHT */
259}
260
62f29fda
TT
261/* See source-cache.h. */
262
263bool
cb44333d 264source_cache::ensure (struct symtab *s)
62f29fda 265{
872dceaa 266 std::string fullname = symtab_to_fullname (s);
62f29fda 267
cb44333d
TT
268 size_t size = m_source_map.size ();
269 for (int i = 0; i < size; ++i)
872dceaa 270 {
cb44333d 271 if (m_source_map[i].fullname == fullname)
62f29fda 272 {
0e42221a
AB
273 /* This should always hold, because we create the file offsets
274 when reading the file. */
cb44333d
TT
275 gdb_assert (m_offset_cache.find (fullname)
276 != m_offset_cache.end ());
277 /* Not strictly LRU, but at least ensure that the most
278 recently used entry is always the last candidate for
279 deletion. Note that this property is relied upon by at
280 least one caller. */
281 if (i != size - 1)
282 std::swap (m_source_map[i], m_source_map[size - 1]);
872dceaa 283 return true;
62f29fda 284 }
872dceaa 285 }
62f29fda 286
1d5d29e7
SV
287 std::string contents;
288 try
289 {
290 contents = get_plain_source_lines (s, fullname);
291 }
292 catch (const gdb_exception_error &e)
293 {
294 /* If 's' is not found, an exception is thrown. */
295 return false;
296 }
872dceaa 297
dcbdb080
TV
298 if (source_styling && gdb_stdout->can_emit_style_escape ()
299 && m_no_styling_files.count (fullname) == 0)
872dceaa 300 {
62dfd02e
TV
301 bool already_styled
302 = try_source_highlight (contents, s->language (), fullname);
f6474de9
TT
303
304 if (!already_styled)
f6474de9
TT
305 {
306 gdb::optional<std::string> ext_contents;
307 ext_contents = ext_lang_colorize (fullname, contents);
308 if (ext_contents.has_value ())
dcbdb080
TV
309 {
310 contents = std::move (*ext_contents);
311 already_styled = true;
312 }
313 }
314
315 if (!already_styled)
316 {
317 /* Styling failed. Styling can fail for instance for these
318 reasons:
319 - the language is not supported.
320 - the language cannot not be auto-detected from the file name.
321 - no stylers available.
322
323 Since styling failed, don't try styling the file again after it
324 drops from the cache.
325
326 Note that clearing the source cache also clears
327 m_no_styling_files. */
328 m_no_styling_files.insert (fullname);
f6474de9
TT
329 }
330 }
62f29fda 331
872dceaa
TT
332 source_text result = { std::move (fullname), std::move (contents) };
333 m_source_map.push_back (std::move (result));
334
335 if (m_source_map.size () > MAX_ENTRIES)
0e42221a
AB
336 {
337 auto iter = m_source_map.begin ();
338 m_offset_cache.erase (iter->fullname);
339 m_source_map.erase (iter);
340 }
872dceaa 341
872dceaa 342 return true;
62f29fda 343}
269249d9 344
cb44333d
TT
345/* See source-cache.h. */
346
347bool
348source_cache::get_line_charpos (struct symtab *s,
349 const std::vector<off_t> **offsets)
350{
1d5d29e7 351 std::string fullname = symtab_to_fullname (s);
cb44333d 352
1d5d29e7
SV
353 auto iter = m_offset_cache.find (fullname);
354 if (iter == m_offset_cache.end ())
cb44333d 355 {
1d5d29e7
SV
356 if (!ensure (s))
357 return false;
358 iter = m_offset_cache.find (fullname);
359 /* cache_source_text ensured this was entered. */
360 gdb_assert (iter != m_offset_cache.end ());
cb44333d 361 }
1d5d29e7
SV
362
363 *offsets = &iter->second;
364 return true;
cb44333d
TT
365}
366
367/* A helper function that extracts the desired source lines from TEXT,
368 putting them into LINES_OUT. The arguments are as for
369 get_source_lines. Returns true on success, false if the line
370 numbers are invalid. */
371
372static bool
373extract_lines (const std::string &text, int first_line, int last_line,
374 std::string *lines_out)
375{
376 int lineno = 1;
377 std::string::size_type pos = 0;
378 std::string::size_type first_pos = std::string::npos;
379
380 while (pos != std::string::npos && lineno <= last_line)
381 {
382 std::string::size_type new_pos = text.find ('\n', pos);
383
384 if (lineno == first_line)
385 first_pos = pos;
386
387 pos = new_pos;
388 if (lineno == last_line || pos == std::string::npos)
389 {
390 /* A newline at the end does not start a new line. */
391 if (first_pos == std::string::npos
392 || first_pos == text.size ())
393 return false;
394 if (pos == std::string::npos)
395 pos = text.size ();
396 else
397 ++pos;
398 *lines_out = text.substr (first_pos, pos - first_pos);
399 return true;
400 }
401 ++lineno;
402 ++pos;
403 }
404
405 return false;
406}
407
408/* See source-cache.h. */
409
410bool
411source_cache::get_source_lines (struct symtab *s, int first_line,
412 int last_line, std::string *lines)
413{
414 if (first_line < 1 || last_line < 1 || first_line > last_line)
415 return false;
416
417 if (!ensure (s))
418 return false;
419
420 return extract_lines (m_source_map.back ().contents,
421 first_line, last_line, lines);
422}
423
39370778
AB
424/* Implement 'maint flush source-cache' command. */
425
426static void
427source_cache_flush_command (const char *command, int from_tty)
428{
429 forget_cached_source_info ();
6cb06a8c 430 gdb_printf (_("Source cache flushed.\n"));
39370778
AB
431}
432
269249d9
TT
433#if GDB_SELF_TEST
434namespace selftests
435{
436static void extract_lines_test ()
437{
438 std::string input_text = "abc\ndef\nghi\njkl\n";
cb44333d
TT
439 std::string result;
440
441 SELF_CHECK (extract_lines (input_text, 1, 1, &result)
442 && result == "abc\n");
443 SELF_CHECK (!extract_lines (input_text, 2, 1, &result));
444 SELF_CHECK (extract_lines (input_text, 1, 2, &result)
445 && result == "abc\ndef\n");
446 SELF_CHECK (extract_lines ("abc", 1, 1, &result)
447 && result == "abc");
269249d9
TT
448}
449}
450#endif
451
6c265988 452void _initialize_source_cache ();
269249d9
TT
453void
454_initialize_source_cache ()
455{
39370778
AB
456 add_cmd ("source-cache", class_maintenance, source_cache_flush_command,
457 _("Force gdb to flush its source code cache."),
458 &maintenanceflushlist);
459
643b1268
AB
460 /* All the 'maint set|show gnu-source-highlight' sub-commands. */
461 static struct cmd_list_element *maint_set_gnu_source_highlight_cmdlist;
462 static struct cmd_list_element *maint_show_gnu_source_highlight_cmdlist;
463
464 /* Adds 'maint set|show gnu-source-highlight'. */
465 add_setshow_prefix_cmd ("gnu-source-highlight", class_maintenance,
466 _("Set gnu-source-highlight specific variables."),
467 _("Show gnu-source-highlight specific variables."),
468 &maint_set_gnu_source_highlight_cmdlist,
469 &maint_show_gnu_source_highlight_cmdlist,
470 &maintenance_set_cmdlist,
471 &maintenance_show_cmdlist);
472
473 /* Adds 'maint set|show gnu-source-highlight enabled'. */
474 add_setshow_boolean_cmd ("enabled", class_maintenance,
475 &use_gnu_source_highlight, _("\
476Set whether the GNU Source Highlight library should be used."), _("\
477Show whether the GNU Source Highlight library is being used."),_("\
478When enabled, GDB will use the GNU Source Highlight library to apply\n\
479styling to source code lines that are shown."),
480 set_use_gnu_source_highlight_enabled,
481 show_use_gnu_source_highlight_enabled,
482 &maint_set_gnu_source_highlight_cmdlist,
483 &maint_show_gnu_source_highlight_cmdlist);
484
485 /* Enable use of GNU Source Highlight library, if we have it. */
486#ifdef HAVE_SOURCE_HIGHLIGHT
487 use_gnu_source_highlight = true;
488#endif
489
269249d9
TT
490#if GDB_SELF_TEST
491 selftests::register_test ("source-cache", selftests::extract_lines_test);
492#endif
493}