xref: /netbsd-src/external/gpl3/gdb.old/dist/gdb/source-cache.c (revision f4748aaa01faf324805f9747191535eb6600f82c)
1 /* Cache of styled source file text
2    Copyright (C) 2018-2020 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 
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 #endif
40 
41 /* The number of source files we'll cache.  */
42 
43 #define MAX_ENTRIES 5
44 
45 /* See source-cache.h.  */
46 
47 source_cache g_source_cache;
48 
49 /* See source-cache.h.  */
50 
51 std::string
52 source_cache::get_plain_source_lines (struct symtab *s,
53 				      const std::string &fullname)
54 {
55   scoped_fd desc (open_source_file (s));
56   if (desc.get () < 0)
57     perror_with_name (symtab_to_filename_for_display (s));
58 
59   struct stat st;
60   if (fstat (desc.get (), &st) < 0)
61     perror_with_name (symtab_to_filename_for_display (s));
62 
63   std::string lines;
64   lines.resize (st.st_size);
65   if (myread (desc.get (), &lines[0], lines.size ()) < 0)
66     perror_with_name (symtab_to_filename_for_display (s));
67 
68   time_t mtime = 0;
69   if (SYMTAB_OBJFILE (s) != NULL && SYMTAB_OBJFILE (s)->obfd != NULL)
70     mtime = SYMTAB_OBJFILE (s)->mtime;
71   else if (exec_bfd)
72     mtime = exec_bfd_mtime;
73 
74   if (mtime && mtime < st.st_mtime)
75     warning (_("Source file is more recent than executable."));
76 
77   std::vector<off_t> offsets;
78   offsets.push_back (0);
79   for (size_t offset = lines.find ('\n');
80        offset != std::string::npos;
81        offset = lines.find ('\n', offset))
82     {
83       ++offset;
84       /* A newline at the end does not start a new line.  It would
85 	 seem simpler to just strip the newline in this function, but
86 	 then "list" won't print the final newline.  */
87       if (offset != lines.size ())
88 	offsets.push_back (offset);
89     }
90 
91   offsets.shrink_to_fit ();
92   m_offset_cache.emplace (fullname, std::move (offsets));
93 
94   return lines;
95 }
96 
97 #ifdef HAVE_SOURCE_HIGHLIGHT
98 
99 /* Return the Source Highlight language name, given a gdb language
100    LANG.  Returns NULL if the language is not known.  */
101 
102 static const char *
103 get_language_name (enum language lang)
104 {
105   switch (lang)
106     {
107     case language_c:
108     case language_objc:
109       return "c.lang";
110 
111     case language_cplus:
112       return "cpp.lang";
113 
114     case language_d:
115       return "d.lang";
116 
117     case language_go:
118       return "go.lang";
119 
120     case language_fortran:
121       return "fortran.lang";
122 
123     case language_m2:
124       /* Not handled by Source Highlight.  */
125       break;
126 
127     case language_asm:
128       return "asm.lang";
129 
130     case language_pascal:
131       return "pascal.lang";
132 
133     case language_opencl:
134       /* Not handled by Source Highlight.  */
135       break;
136 
137     case language_rust:
138       return "rust.lang";
139 
140     case language_ada:
141       return "ada.lang";
142 
143     default:
144       break;
145     }
146 
147   return nullptr;
148 }
149 
150 #endif /* HAVE_SOURCE_HIGHLIGHT */
151 
152 /* See source-cache.h.  */
153 
154 bool
155 source_cache::ensure (struct symtab *s)
156 {
157   std::string fullname = symtab_to_fullname (s);
158 
159   size_t size = m_source_map.size ();
160   for (int i = 0; i < size; ++i)
161     {
162       if (m_source_map[i].fullname == fullname)
163 	{
164 	  /* This should always hold, because we create the file
165 	     offsets when reading the file, and never free them
166 	     without also clearing the contents cache.  */
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     m_source_map.erase (m_source_map.begin ());
243 
244   return true;
245 }
246 
247 /* See source-cache.h.  */
248 
249 bool
250 source_cache::get_line_charpos (struct symtab *s,
251 				const std::vector<off_t> **offsets)
252 {
253   std::string fullname = symtab_to_fullname (s);
254 
255   auto iter = m_offset_cache.find (fullname);
256   if (iter == m_offset_cache.end ())
257     {
258       if (!ensure (s))
259 	return false;
260       iter = m_offset_cache.find (fullname);
261       /* cache_source_text ensured this was entered.  */
262       gdb_assert (iter != m_offset_cache.end ());
263     }
264 
265   *offsets = &iter->second;
266   return true;
267 }
268 
269 /* A helper function that extracts the desired source lines from TEXT,
270    putting them into LINES_OUT.  The arguments are as for
271    get_source_lines.  Returns true on success, false if the line
272    numbers are invalid.  */
273 
274 static bool
275 extract_lines (const std::string &text, int first_line, int last_line,
276 	       std::string *lines_out)
277 {
278   int lineno = 1;
279   std::string::size_type pos = 0;
280   std::string::size_type first_pos = std::string::npos;
281 
282   while (pos != std::string::npos && lineno <= last_line)
283     {
284       std::string::size_type new_pos = text.find ('\n', pos);
285 
286       if (lineno == first_line)
287 	first_pos = pos;
288 
289       pos = new_pos;
290       if (lineno == last_line || pos == std::string::npos)
291 	{
292 	  /* A newline at the end does not start a new line.  */
293 	  if (first_pos == std::string::npos
294 	      || first_pos == text.size ())
295 	    return false;
296 	  if (pos == std::string::npos)
297 	    pos = text.size ();
298 	  else
299 	    ++pos;
300 	  *lines_out = text.substr (first_pos, pos - first_pos);
301 	  return true;
302 	}
303       ++lineno;
304       ++pos;
305     }
306 
307   return false;
308 }
309 
310 /* See source-cache.h.  */
311 
312 bool
313 source_cache::get_source_lines (struct symtab *s, int first_line,
314 				int last_line, std::string *lines)
315 {
316   if (first_line < 1 || last_line < 1 || first_line > last_line)
317     return false;
318 
319   if (!ensure (s))
320     return false;
321 
322   return extract_lines (m_source_map.back ().contents,
323 			first_line, last_line, lines);
324 }
325 
326 #if GDB_SELF_TEST
327 namespace selftests
328 {
329 static void extract_lines_test ()
330 {
331   std::string input_text = "abc\ndef\nghi\njkl\n";
332   std::string result;
333 
334   SELF_CHECK (extract_lines (input_text, 1, 1, &result)
335 	      && result == "abc\n");
336   SELF_CHECK (!extract_lines (input_text, 2, 1, &result));
337   SELF_CHECK (extract_lines (input_text, 1, 2, &result)
338 	      && result == "abc\ndef\n");
339   SELF_CHECK (extract_lines ("abc", 1, 1, &result)
340 	      && result == "abc");
341 }
342 }
343 #endif
344 
345 void _initialize_source_cache ();
346 void
347 _initialize_source_cache ()
348 {
349 #if GDB_SELF_TEST
350   selftests::register_test ("source-cache", selftests::extract_lines_test);
351 #endif
352 }
353