Allow use of Pygments to colorize source code
[binutils-gdb.git] / gdb / source-cache.c
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 = get_plain_source_lines (s, fullname);
180
181 if (source_styling && gdb_stdout->can_emit_style_escape ())
182 {
183 #ifdef HAVE_SOURCE_HIGHLIGHT
184 bool already_styled = false;
185 const char *lang_name = get_language_name (SYMTAB_LANGUAGE (s));
186 if (lang_name != nullptr)
187 {
188 /* The global source highlight object, or null if one was
189 never constructed. This is stored here rather than in
190 the class so that we don't need to include anything or do
191 conditional compilation in source-cache.h. */
192 static srchilite::SourceHighlight *highlighter;
193
194 try
195 {
196 if (highlighter == nullptr)
197 {
198 highlighter = new srchilite::SourceHighlight ("esc.outlang");
199 highlighter->setStyleFile ("esc.style");
200 }
201
202 std::istringstream input (contents);
203 std::ostringstream output;
204 highlighter->highlight (input, output, lang_name, fullname);
205 contents = output.str ();
206 already_styled = true;
207 }
208 catch (...)
209 {
210 /* Source Highlight will throw an exception if
211 highlighting fails. One possible reason it can fail
212 is if the language is unknown -- which matters to gdb
213 because Rust support wasn't added until after 3.1.8.
214 Ignore exceptions here and fall back to
215 un-highlighted text. */
216 }
217 }
218
219 if (!already_styled)
220 #endif /* HAVE_SOURCE_HIGHLIGHT */
221 {
222 gdb::optional<std::string> ext_contents;
223 ext_contents = ext_lang_colorize (fullname, contents);
224 if (ext_contents.has_value ())
225 contents = std::move (*ext_contents);
226 }
227 }
228
229 source_text result = { std::move (fullname), std::move (contents) };
230 m_source_map.push_back (std::move (result));
231
232 if (m_source_map.size () > MAX_ENTRIES)
233 m_source_map.erase (m_source_map.begin ());
234
235 return true;
236 }
237
238 /* See source-cache.h. */
239
240 bool
241 source_cache::get_line_charpos (struct symtab *s,
242 const std::vector<off_t> **offsets)
243 {
244 try
245 {
246 std::string fullname = symtab_to_fullname (s);
247
248 auto iter = m_offset_cache.find (fullname);
249 if (iter == m_offset_cache.end ())
250 {
251 ensure (s);
252 iter = m_offset_cache.find (fullname);
253 /* cache_source_text ensured this was entered. */
254 gdb_assert (iter != m_offset_cache.end ());
255 }
256
257 *offsets = &iter->second;
258 return true;
259 }
260 catch (const gdb_exception_error &e)
261 {
262 return false;
263 }
264 }
265
266 /* A helper function that extracts the desired source lines from TEXT,
267 putting them into LINES_OUT. The arguments are as for
268 get_source_lines. Returns true on success, false if the line
269 numbers are invalid. */
270
271 static bool
272 extract_lines (const std::string &text, int first_line, int last_line,
273 std::string *lines_out)
274 {
275 int lineno = 1;
276 std::string::size_type pos = 0;
277 std::string::size_type first_pos = std::string::npos;
278
279 while (pos != std::string::npos && lineno <= last_line)
280 {
281 std::string::size_type new_pos = text.find ('\n', pos);
282
283 if (lineno == first_line)
284 first_pos = pos;
285
286 pos = new_pos;
287 if (lineno == last_line || pos == std::string::npos)
288 {
289 /* A newline at the end does not start a new line. */
290 if (first_pos == std::string::npos
291 || first_pos == text.size ())
292 return false;
293 if (pos == std::string::npos)
294 pos = text.size ();
295 else
296 ++pos;
297 *lines_out = text.substr (first_pos, pos - first_pos);
298 return true;
299 }
300 ++lineno;
301 ++pos;
302 }
303
304 return false;
305 }
306
307 /* See source-cache.h. */
308
309 bool
310 source_cache::get_source_lines (struct symtab *s, int first_line,
311 int last_line, std::string *lines)
312 {
313 if (first_line < 1 || last_line < 1 || first_line > last_line)
314 return false;
315
316 if (!ensure (s))
317 return false;
318
319 return extract_lines (m_source_map.back ().contents,
320 first_line, last_line, lines);
321 }
322
323 #if GDB_SELF_TEST
324 namespace selftests
325 {
326 static void extract_lines_test ()
327 {
328 std::string input_text = "abc\ndef\nghi\njkl\n";
329 std::string result;
330
331 SELF_CHECK (extract_lines (input_text, 1, 1, &result)
332 && result == "abc\n");
333 SELF_CHECK (!extract_lines (input_text, 2, 1, &result));
334 SELF_CHECK (extract_lines (input_text, 1, 2, &result)
335 && result == "abc\ndef\n");
336 SELF_CHECK (extract_lines ("abc", 1, 1, &result)
337 && result == "abc");
338 }
339 }
340 #endif
341
342 void _initialize_source_cache ();
343 void
344 _initialize_source_cache ()
345 {
346 #if GDB_SELF_TEST
347 selftests::register_test ("source-cache", selftests::extract_lines_test);
348 #endif
349 }