Add file offsets to the source cache
[external/binutils.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 #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       /* Not handled by Source Highlight.  */
139       break;
140
141     case language_ada:
142       return "ada.lang";
143
144     default:
145       break;
146     }
147
148   return nullptr;
149 }
150
151 #endif /* HAVE_SOURCE_HIGHLIGHT */
152
153 /* See source-cache.h.  */
154
155 bool
156 source_cache::ensure (struct symtab *s)
157 {
158   std::string fullname = symtab_to_fullname (s);
159
160   size_t size = m_source_map.size ();
161   for (int i = 0; i < size; ++i)
162     {
163       if (m_source_map[i].fullname == fullname)
164         {
165           /* This should always hold, because we create the file
166              offsets when reading the file, and never free them
167              without also clearing the contents cache.  */
168           gdb_assert (m_offset_cache.find (fullname)
169                       != m_offset_cache.end ());
170           /* Not strictly LRU, but at least ensure that the most
171              recently used entry is always the last candidate for
172              deletion.  Note that this property is relied upon by at
173              least one caller.  */
174           if (i != size - 1)
175             std::swap (m_source_map[i], m_source_map[size - 1]);
176           return true;
177         }
178     }
179
180   std::string contents = get_plain_source_lines (s, fullname);
181
182 #ifdef HAVE_SOURCE_HIGHLIGHT
183   if (source_styling && gdb_stdout->can_emit_style_escape ())
184     {
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           if (highlighter == nullptr)
195             {
196               highlighter = new srchilite::SourceHighlight ("esc.outlang");
197               highlighter->setStyleFile ("esc.style");
198             }
199
200           std::istringstream input (contents);
201           std::ostringstream output;
202           highlighter->highlight (input, output, lang_name, fullname);
203
204           contents = output.str ();
205         }
206     }
207 #endif /* HAVE_SOURCE_HIGHLIGHT */
208
209   source_text result = { std::move (fullname), std::move (contents) };
210   m_source_map.push_back (std::move (result));
211
212   if (m_source_map.size () > MAX_ENTRIES)
213     m_source_map.erase (m_source_map.begin ());
214
215   return true;
216 }
217
218 /* See source-cache.h.  */
219
220 bool
221 source_cache::get_line_charpos (struct symtab *s,
222                                 const std::vector<off_t> **offsets)
223 {
224   std::string fullname = symtab_to_fullname (s);
225
226   auto iter = m_offset_cache.find (fullname);
227   if (iter == m_offset_cache.end ())
228     {
229       ensure (s);
230       iter = m_offset_cache.find (fullname);
231       /* cache_source_text ensured this was entered.  */
232       gdb_assert (iter != m_offset_cache.end ());
233     }
234
235   *offsets = &iter->second;
236   return true;
237 }
238
239 /* A helper function that extracts the desired source lines from TEXT,
240    putting them into LINES_OUT.  The arguments are as for
241    get_source_lines.  Returns true on success, false if the line
242    numbers are invalid.  */
243
244 static bool
245 extract_lines (const std::string &text, int first_line, int last_line,
246                std::string *lines_out)
247 {
248   int lineno = 1;
249   std::string::size_type pos = 0;
250   std::string::size_type first_pos = std::string::npos;
251
252   while (pos != std::string::npos && lineno <= last_line)
253     {
254       std::string::size_type new_pos = text.find ('\n', pos);
255
256       if (lineno == first_line)
257         first_pos = pos;
258
259       pos = new_pos;
260       if (lineno == last_line || pos == std::string::npos)
261         {
262           /* A newline at the end does not start a new line.  */
263           if (first_pos == std::string::npos
264               || first_pos == text.size ())
265             return false;
266           if (pos == std::string::npos)
267             pos = text.size ();
268           else
269             ++pos;
270           *lines_out = text.substr (first_pos, pos - first_pos);
271           return true;
272         }
273       ++lineno;
274       ++pos;
275     }
276
277   return false;
278 }
279
280 /* See source-cache.h.  */
281
282 bool
283 source_cache::get_source_lines (struct symtab *s, int first_line,
284                                 int last_line, std::string *lines)
285 {
286   if (first_line < 1 || last_line < 1 || first_line > last_line)
287     return false;
288
289   if (!ensure (s))
290     return false;
291
292   return extract_lines (m_source_map.back ().contents,
293                         first_line, last_line, lines);
294 }
295
296 #if GDB_SELF_TEST
297 namespace selftests
298 {
299 static void extract_lines_test ()
300 {
301   std::string input_text = "abc\ndef\nghi\njkl\n";
302   std::string result;
303
304   SELF_CHECK (extract_lines (input_text, 1, 1, &result)
305               && result == "abc\n");
306   SELF_CHECK (!extract_lines (input_text, 2, 1, &result));
307   SELF_CHECK (extract_lines (input_text, 1, 2, &result)
308               && result == "abc\ndef\n");
309   SELF_CHECK (extract_lines ("abc", 1, 1, &result)
310               && result == "abc");
311 }
312 }
313 #endif
314
315 void
316 _initialize_source_cache ()
317 {
318 #if GDB_SELF_TEST
319   selftests::register_test ("source-cache", selftests::extract_lines_test);
320 #endif
321 }