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