Fix latent bug in source cache
[binutils-gdb.git] / gdb / source-cache.c
1 /* Cache of styled source file text
2 Copyright (C) 2018-2019 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
27 #ifdef HAVE_SOURCE_HIGHLIGHT
28 /* If Gnulib redirects 'open' and 'close' to its replacements
29 'rpl_open' and 'rpl_close' via cpp macros, including <fstream>
30 below with those macros in effect will cause unresolved externals
31 when GDB is linked. Happens, e.g., in the MinGW build. */
32 #undef open
33 #undef close
34 #include <fstream>
35 #include <sstream>
36 #include <srchilite/sourcehighlight.h>
37 #include <srchilite/langmap.h>
38 #endif
39
40 /* The number of source files we'll cache. */
41
42 #define MAX_ENTRIES 5
43
44 /* See source-cache.h. */
45
46 source_cache g_source_cache;
47
48 /* See source-cache.h. */
49
50 bool
51 source_cache::get_plain_source_lines (struct symtab *s, int first_line,
52 int last_line, std::string *lines)
53 {
54 scoped_fd desc (open_source_file_with_line_charpos (s));
55 if (desc.get () < 0)
56 return false;
57
58 if (first_line < 1 || first_line > s->nlines || last_line < 1)
59 return false;
60
61 if (lseek (desc.get (), s->line_charpos[first_line - 1], SEEK_SET) < 0)
62 perror_with_name (symtab_to_filename_for_display (s));
63
64 int last_charpos;
65 if (last_line >= s->nlines)
66 {
67 struct stat st;
68
69 if (fstat (desc.get (), &st) < 0)
70 perror_with_name (symtab_to_filename_for_display (s));
71 /* We could cache this in line_charpos... */
72 last_charpos = st.st_size;
73 }
74 else
75 last_charpos = s->line_charpos[last_line];
76
77 lines->resize (last_charpos - s->line_charpos[first_line - 1]);
78 if (myread (desc.get (), &(*lines)[0], lines->size ()) < 0)
79 perror_with_name (symtab_to_filename_for_display (s));
80
81 return true;
82 }
83
84 /* A helper function for get_plain_source_lines that extracts the
85 desired source lines from TEXT, putting them into LINES_OUT. The
86 arguments are as for get_source_lines. The return value is the
87 desired lines. */
88 static std::string
89 extract_lines (const std::string &text, int first_line, int last_line)
90 {
91 int lineno = 1;
92 std::string::size_type pos = 0;
93 std::string::size_type first_pos = std::string::npos;
94
95 while (pos != std::string::npos && lineno <= last_line)
96 {
97 std::string::size_type new_pos = text.find ('\n', pos);
98
99 if (lineno == first_line)
100 first_pos = pos;
101
102 pos = new_pos;
103 if (lineno == last_line || pos == std::string::npos)
104 {
105 if (first_pos == std::string::npos)
106 return {};
107 if (pos == std::string::npos)
108 pos = text.size ();
109 else
110 ++pos;
111 return text.substr (first_pos, pos - first_pos);
112 }
113 ++lineno;
114 ++pos;
115 }
116
117 return {};
118 }
119
120 #ifdef HAVE_SOURCE_HIGHLIGHT
121
122 /* Return the Source Highlight language name, given a gdb language
123 LANG. Returns NULL if the language is not known. */
124
125 static const char *
126 get_language_name (enum language lang)
127 {
128 switch (lang)
129 {
130 case language_c:
131 case language_objc:
132 return "c.lang";
133
134 case language_cplus:
135 return "cpp.lang";
136
137 case language_d:
138 return "d.lang";
139
140 case language_go:
141 return "go.lang";
142
143 case language_fortran:
144 return "fortran.lang";
145
146 case language_m2:
147 /* Not handled by Source Highlight. */
148 break;
149
150 case language_asm:
151 return "asm.lang";
152
153 case language_pascal:
154 return "pascal.lang";
155
156 case language_opencl:
157 /* Not handled by Source Highlight. */
158 break;
159
160 case language_rust:
161 /* Not handled by Source Highlight. */
162 break;
163
164 case language_ada:
165 return "ada.lang";
166
167 default:
168 break;
169 }
170
171 return nullptr;
172 }
173
174 #endif /* HAVE_SOURCE_HIGHLIGHT */
175
176 /* See source-cache.h. */
177
178 bool
179 source_cache::get_source_lines (struct symtab *s, int first_line,
180 int last_line, std::string *lines)
181 {
182 if (first_line < 1 || last_line < 1 || first_line > last_line)
183 return false;
184
185 #ifdef HAVE_SOURCE_HIGHLIGHT
186 if (source_styling && gdb_stdout->can_emit_style_escape ())
187 {
188 const char *fullname = symtab_to_fullname (s);
189
190 for (const auto &item : m_source_map)
191 {
192 if (item.fullname == fullname)
193 {
194 *lines = extract_lines (item.contents, first_line, last_line);
195 return true;
196 }
197 }
198
199 const char *lang_name = get_language_name (SYMTAB_LANGUAGE (s));
200 if (lang_name != nullptr)
201 {
202 std::ifstream input (fullname);
203 if (input.is_open ())
204 {
205 /* The global source highlight object, or null if one
206 was never constructed. This is stored here rather
207 than in the class so that we don't need to include
208 anything or do conditional compilation in
209 source-cache.h. */
210 static srchilite::SourceHighlight *highlighter;
211
212 if (s->line_charpos == 0)
213 {
214 scoped_fd desc (open_source_file_with_line_charpos (s));
215 if (desc.get () < 0)
216 return false;
217
218 /* FULLNAME points to a value owned by the symtab
219 (symtab::fullname). Calling open_source_file reallocates
220 that value, so we must refresh FULLNAME to avoid a
221 use-after-free. */
222 fullname = symtab_to_fullname (s);
223 }
224
225 if (highlighter == nullptr)
226 {
227 highlighter = new srchilite::SourceHighlight ("esc.outlang");
228 highlighter->setStyleFile ("esc.style");
229 }
230
231 std::ostringstream output;
232 highlighter->highlight (input, output, lang_name, fullname);
233
234 source_text result = { fullname, output.str () };
235 m_source_map.push_back (std::move (result));
236
237 if (m_source_map.size () > MAX_ENTRIES)
238 m_source_map.erase (m_source_map.begin ());
239
240 *lines = extract_lines (m_source_map.back ().contents,
241 first_line, last_line);
242 return true;
243 }
244 }
245 }
246 #endif /* HAVE_SOURCE_HIGHLIGHT */
247
248 return get_plain_source_lines (s, first_line, last_line, lines);
249 }
250
251 #if GDB_SELF_TEST
252 namespace selftests
253 {
254 static void extract_lines_test ()
255 {
256 std::string input_text = "abc\ndef\nghi\njkl\n";
257
258 SELF_CHECK (extract_lines (input_text, 1, 1) == "abc\n");
259 SELF_CHECK (extract_lines (input_text, 2, 1) == "");
260 SELF_CHECK (extract_lines (input_text, 1, 2) == "abc\ndef\n");
261 SELF_CHECK (extract_lines ("abc", 1, 1) == "abc");
262 }
263 }
264 #endif
265
266 void
267 _initialize_source_cache ()
268 {
269 #if GDB_SELF_TEST
270 selftests::register_test ("source-cache", selftests::extract_lines_test);
271 #endif
272 }