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