/* Language-independent diagnostic subroutines for the GNU Compiler Collection Copyright (C) 1999-2025 Free Software Foundation, Inc. Contributed by Gabriel Dos Reis This file is part of GCC. GCC is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3, or (at your option) any later version. GCC is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GCC; see the file COPYING3. If not see . */ /* This file implements the language independent aspect of diagnostic message module. */ #include "config.h" #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" #include "version.h" #include "demangle.h" #include "intl.h" #include "backtrace.h" #include "diagnostic.h" #include "diagnostics/color.h" #include "diagnostics/url.h" #include "diagnostics/metadata.h" #include "diagnostics/paths.h" #include "diagnostics/client-data-hooks.h" #include "diagnostics/diagram.h" #include "diagnostics/sink.h" #include "diagnostics/sarif-sink.h" #include "diagnostics/text-sink.h" #include "diagnostics/changes.h" #include "selftest.h" #include "diagnostics/selftest-context.h" #include "opts.h" #include "cpplib.h" #include "text-art/theme.h" #include "pretty-print-urlifier.h" #include "diagnostics/logical-locations.h" #include "diagnostics/buffering.h" #include "diagnostics/file-cache.h" #ifdef HAVE_TERMIOS_H # include #endif #ifdef GWINSZ_IN_SYS_IOCTL # include #endif /* Disable warnings about quoting issues in the pp_xxx calls below that (intentionally) don't follow GCC diagnostic conventions. */ #if __GNUC__ >= 10 # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wformat-diag" #endif static void real_abort (void) ATTRIBUTE_NORETURN; /* Name of program invoked, sans directories. */ const char *progname; /* Return a malloc'd string containing MSG formatted a la printf. The caller is responsible for freeing the memory. */ char * build_message_string (const char *msg, ...) { char *str; va_list ap; va_start (ap, msg); str = xvasprintf (msg, ap); va_end (ap); return str; } /* Return the value of the getenv("COLUMNS") as an integer. If the value is not set to a positive integer, use ioctl to get the terminal width. If it fails, return INT_MAX. */ int get_terminal_width (void) { const char * s = getenv ("COLUMNS"); if (s != nullptr) { int n = atoi (s); if (n > 0) return n; } #ifdef TIOCGWINSZ struct winsize w; w.ws_col = 0; if (ioctl (0, TIOCGWINSZ, &w) == 0 && w.ws_col > 0) return w.ws_col; #endif return INT_MAX; } namespace diagnostics { /* Set caret_max_width to value. */ void context::set_caret_max_width (int value) { /* One minus to account for the leading empty space. */ value = value ? value - 1 : (isatty (fileno (pp_buffer (get_reference_printer ())->m_stream)) ? get_terminal_width () - 1 : INT_MAX); if (value <= 0) value = INT_MAX; m_source_printing.max_width = value; } /* Initialize the diagnostic message outputting machinery. */ void context::initialize (int n_opts) { /* Allocate a basic pretty-printer. Clients will replace this a much more elaborated pretty-printer if they wish. */ m_reference_printer = std::make_unique ().release (); m_file_cache = new file_cache (); m_diagnostic_counters.clear (); m_warning_as_error_requested = false; m_n_opts = n_opts; m_option_classifier.init (n_opts); m_source_printing.enabled = false; set_caret_max_width (pp_line_cutoff (get_reference_printer ())); for (int i = 0; i < rich_location::STATICALLY_ALLOCATED_RANGES; i++) m_source_printing.caret_chars[i] = '^'; m_show_cwe = false; m_show_rules = false; m_path_format = DPF_NONE; m_show_path_depths = false; m_show_option_requested = false; m_abort_on_error = false; m_show_column = false; m_pedantic_errors = false; m_permissive = false; m_opt_permissive = 0; m_fatal_errors = false; m_inhibit_warnings = false; m_warn_system_headers = false; m_max_errors = 0; m_internal_error = nullptr; m_adjust_diagnostic_info = nullptr; m_text_callbacks.m_begin_diagnostic = default_text_starter; m_text_callbacks.m_text_start_span = default_start_span_fn; m_text_callbacks.m_html_start_span = default_start_span_fn; m_text_callbacks.m_end_diagnostic = default_text_finalizer; m_option_mgr = nullptr; m_urlifier_stack = new auto_vec (); m_last_location = UNKNOWN_LOCATION; m_client_aux_data = nullptr; m_lock = 0; m_inhibit_notes_p = false; m_source_printing.colorize_source_p = false; m_source_printing.show_labels_p = false; m_source_printing.show_line_numbers_p = false; m_source_printing.min_margin_width = 0; m_source_printing.show_ruler_p = false; m_source_printing.show_event_links_p = false; m_report_bug = false; m_extra_output_kind = EXTRA_DIAGNOSTIC_OUTPUT_none; if (const char *var = getenv ("GCC_EXTRA_DIAGNOSTIC_OUTPUT")) { if (!strcmp (var, "fixits-v1")) m_extra_output_kind = EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1; else if (!strcmp (var, "fixits-v2")) m_extra_output_kind = EXTRA_DIAGNOSTIC_OUTPUT_fixits_v2; /* Silently ignore unrecognized values. */ } m_column_unit = DIAGNOSTICS_COLUMN_UNIT_DISPLAY; m_column_origin = 1; m_tabstop = 8; m_escape_format = DIAGNOSTICS_ESCAPE_FORMAT_UNICODE; m_fixits_change_set = nullptr; m_diagnostic_groups.m_group_nesting_depth = 0; m_diagnostic_groups.m_diagnostic_nesting_level = 0; m_diagnostic_groups.m_emission_count = 0; m_diagnostic_groups.m_inhibiting_notes_from = 0; m_sinks.safe_push (new text_sink (*this, nullptr, true)); m_set_locations_cb = nullptr; m_client_data_hooks = nullptr; m_diagrams.m_theme = nullptr; m_original_argv = nullptr; m_diagnostic_buffer = nullptr; enum diagnostic_text_art_charset text_art_charset = DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI; if (const char *lang = getenv ("LANG")) { /* For LANG=C, don't assume the terminal supports anything other than ASCII. */ if (!strcmp (lang, "C")) text_art_charset = DIAGNOSTICS_TEXT_ART_CHARSET_ASCII; } set_text_art_charset (text_art_charset); } /* Maybe initialize the color support. We require clients to do this explicitly, since most clients don't want color. When called without a VALUE, it initializes with DIAGNOSTICS_COLOR_DEFAULT. */ void context::color_init (int value) { /* value == -1 is the default value. */ if (value < 0) { /* If DIAGNOSTICS_COLOR_DEFAULT is -1, default to -fdiagnostics-color=auto if GCC_COLORS is in the environment, otherwise default to -fdiagnostics-color=never, for other values default to that -fdiagnostics-color={never,auto,always}. */ if (DIAGNOSTICS_COLOR_DEFAULT == -1) { if (!getenv ("GCC_COLORS")) return; value = DIAGNOSTICS_COLOR_AUTO; } else value = DIAGNOSTICS_COLOR_DEFAULT; } pp_show_color (m_reference_printer) = colorize_init ((diagnostic_color_rule_t) value); for (auto sink_ : m_sinks) if (sink_->follows_reference_printer_p ()) pp_show_color (sink_->get_printer ()) = pp_show_color (m_reference_printer); } /* Initialize URL support within this context based on VALUE, handling "auto". */ void context::urls_init (int value) { /* value == -1 is the default value. */ if (value < 0) { /* If DIAGNOSTICS_URLS_DEFAULT is -1, default to -fdiagnostics-urls=auto if GCC_URLS or TERM_URLS is in the environment, otherwise default to -fdiagnostics-urls=never, for other values default to that -fdiagnostics-urls={never,auto,always}. */ if (DIAGNOSTICS_URLS_DEFAULT == -1) { if (!getenv ("GCC_URLS") && !getenv ("TERM_URLS")) return; value = DIAGNOSTICS_URL_AUTO; } else value = DIAGNOSTICS_URLS_DEFAULT; } m_reference_printer->set_url_format (determine_url_format ((diagnostic_url_rule_t) value)); for (auto sink_ : m_sinks) if (sink_->follows_reference_printer_p ()) sink_->get_printer ()->set_url_format (m_reference_printer->get_url_format ()); } /* Create the file_cache, if not already created, and tell it how to translate files on input. */ void context::initialize_input_context (diagnostic_input_charset_callback ccb, bool should_skip_bom) { m_file_cache->initialize_input_context (ccb, should_skip_bom); } /* Do any cleaning up required after the last diagnostic is emitted. */ void context::finish () { /* We might be handling a fatal error. Close any active diagnostic groups, which may trigger flushing sinks. */ while (m_diagnostic_groups.m_group_nesting_depth > 0) end_group (); set_diagnostic_buffer (nullptr); /* Clean ups. */ while (!m_sinks.is_empty ()) delete m_sinks.pop (); if (m_diagrams.m_theme) { delete m_diagrams.m_theme; m_diagrams.m_theme = nullptr; } delete m_file_cache; m_file_cache = nullptr; m_option_classifier.fini (); delete m_reference_printer; m_reference_printer = nullptr; if (m_fixits_change_set) { delete m_fixits_change_set; m_fixits_change_set = nullptr; } if (m_client_data_hooks) { delete m_client_data_hooks; m_client_data_hooks = nullptr; } delete m_option_mgr; m_option_mgr = nullptr; if (m_urlifier_stack) { while (!m_urlifier_stack->is_empty ()) pop_urlifier (); delete m_urlifier_stack; m_urlifier_stack = nullptr; } freeargv (m_original_argv); m_original_argv = nullptr; } /* Dump state of this diagnostics::context to OUT, for debugging. */ void context::dump (FILE *out) const { fprintf (out, "diagnostics::context:\n"); m_diagnostic_counters.dump (out, 2); fprintf (out, " reference printer:\n"); m_reference_printer->dump (out, 4); fprintf (out, " output sinks:\n"); if (m_sinks.length () > 0) { for (unsigned i = 0; i < m_sinks.length (); ++i) { fprintf (out, " sink %i:\n", i); m_sinks[i]->dump (out, 4); } } else fprintf (out, " (none):\n"); fprintf (out, " diagnostic buffer:\n"); if (m_diagnostic_buffer) m_diagnostic_buffer->dump (out, 4); else fprintf (out, " (none):\n"); fprintf (out, " file cache:\n"); if (m_file_cache) m_file_cache->dump (out, 4); else fprintf (out, " (none):\n"); } /* Return true if sufficiently severe diagnostics have been seen that we ought to exit with a non-zero exit code. */ bool context::execution_failed_p () const { /* Equivalent to (seen_error () || werrorcount), but on this context, rather than global_dc. */ return (diagnostic_count (kind::error) || diagnostic_count (kind::sorry) || diagnostic_count (kind::werror)); } void context::remove_all_output_sinks () { while (!m_sinks.is_empty ()) delete m_sinks.pop (); } void context::set_sink (std::unique_ptr sink_) { remove_all_output_sinks (); m_sinks.safe_push (sink_.release ()); } sink & context::get_sink (size_t idx) const { gcc_assert (idx < m_sinks.length ()); gcc_assert (m_sinks[idx]); return *m_sinks[idx]; } void context::add_sink (std::unique_ptr sink_) { m_sinks.safe_push (sink_.release ()); } /* Return true if there are no machine-readable formats writing to stderr. */ bool context::supports_fnotice_on_stderr_p () const { for (auto sink_ : m_sinks) if (sink_->machine_readable_stderr_p ()) return false; return true; } void context::set_main_input_filename (const char *filename) { for (auto sink_ : m_sinks) sink_->set_main_input_filename (filename); } void context::set_client_data_hooks (std::unique_ptr hooks) { delete m_client_data_hooks; /* Ideally the field would be a std::unique_ptr here. */ m_client_data_hooks = hooks.release (); } void context::set_original_argv (unique_argv original_argv) { /* Ideally we'd use a unique_argv for m_original_argv, but diagnostics::context doesn't yet have a ctor/dtor pair. */ // Ensure any old value is freed freeargv (m_original_argv); // Take ownership of the new value m_original_argv = original_argv.release (); } void context::set_option_manager (std::unique_ptr mgr, unsigned lang_mask) { delete m_option_mgr; m_option_mgr = mgr.release (); m_lang_mask = lang_mask; } void context::push_owned_urlifier (std::unique_ptr ptr) { gcc_assert (m_urlifier_stack); const urlifier_stack_node node = { ptr.release (), true }; m_urlifier_stack->safe_push (node); } void context::push_borrowed_urlifier (const urlifier &loan) { gcc_assert (m_urlifier_stack); const urlifier_stack_node node = { const_cast (&loan), false }; m_urlifier_stack->safe_push (node); } void context::pop_urlifier () { gcc_assert (m_urlifier_stack); gcc_assert (m_urlifier_stack->length () > 0); const urlifier_stack_node node = m_urlifier_stack->pop (); if (node.m_owned) delete node.m_urlifier; } const logical_locations::manager * context::get_logical_location_manager () const { if (!m_client_data_hooks) return nullptr; return m_client_data_hooks->get_logical_location_manager (); } const urlifier * context::get_urlifier () const { if (!m_urlifier_stack) return nullptr; if (m_urlifier_stack->is_empty ()) return nullptr; return m_urlifier_stack->last ().m_urlifier; } /* Set PP as the reference printer for this context. Refresh all output sinks. */ void context::set_pretty_printer (std::unique_ptr pp) { delete m_reference_printer; m_reference_printer = pp.release (); refresh_output_sinks (); } /* Give all output sinks a chance to rebuild their pretty_printer. */ void context::refresh_output_sinks () { for (auto sink_ : m_sinks) sink_->update_printer (); } /* Set FORMAT_DECODER on the reference printer and on the pretty_printer of all output sinks. */ void context::set_format_decoder (printer_fn format_decoder) { pp_format_decoder (m_reference_printer) = format_decoder; for (auto sink_ : m_sinks) pp_format_decoder (sink_->get_printer ()) = format_decoder; } void context::set_show_highlight_colors (bool val) { pp_show_highlight_colors (m_reference_printer) = val; for (auto sink_ : m_sinks) if (sink_->follows_reference_printer_p ()) pp_show_highlight_colors (sink_->get_printer ()) = val; } void context::set_prefixing_rule (diagnostic_prefixing_rule_t rule) { pp_prefixing_rule (m_reference_printer) = rule; for (auto sink_ : m_sinks) if (sink_->follows_reference_printer_p ()) pp_prefixing_rule (sink_->get_printer ()) = rule; } void context::initialize_fixits_change_set () { delete m_fixits_change_set; gcc_assert (m_file_cache); m_fixits_change_set = new changes::change_set (*m_file_cache); } } // namespace diagnostics /* Initialize DIAGNOSTIC, where the message MSG has already been translated. */ void diagnostic_set_info_translated (diagnostics::diagnostic_info *diagnostic, const char *msg, va_list *args, rich_location *richloc, enum diagnostics::kind kind) { gcc_assert (richloc); diagnostic->m_message.m_err_no = errno; diagnostic->m_message.m_args_ptr = args; diagnostic->m_message.m_format_spec = msg; diagnostic->m_message.m_richloc = richloc; diagnostic->m_richloc = richloc; diagnostic->m_metadata = nullptr; diagnostic->m_kind = kind; diagnostic->m_option_id = 0; } /* Initialize DIAGNOSTIC, where the message GMSGID has not yet been translated. */ void diagnostic_set_info (diagnostics::diagnostic_info *diagnostic, const char *gmsgid, va_list *args, rich_location *richloc, enum diagnostics::kind kind) { gcc_assert (richloc); diagnostic_set_info_translated (diagnostic, _(gmsgid), args, richloc, kind); } namespace diagnostics { static const char *const diagnostic_kind_text[] = { #define DEFINE_DIAGNOSTIC_KIND(K, T, C) (T), #include "diagnostics/kinds.def" #undef DEFINE_DIAGNOSTIC_KIND "must-not-happen" }; /* Get unlocalized string describing KIND. */ const char * get_text_for_kind (enum kind kind) { return diagnostic_kind_text[static_cast (kind)]; } static const char *const diagnostic_kind_color[] = { #define DEFINE_DIAGNOSTIC_KIND(K, T, C) (C), #include "diagnostics/kinds.def" #undef DEFINE_DIAGNOSTIC_KIND nullptr }; /* Get a color name for diagnostics of type KIND Result could be nullptr. */ const char * get_color_for_kind (enum kind kind) { return diagnostic_kind_color[static_cast (kind)]; } /* Given an expanded_location, convert the column (which is in 1-based bytes) to the requested units, without converting the origin. Return -1 if the column is invalid (<= 0). */ static int convert_column_unit (file_cache &fc, enum diagnostics_column_unit column_unit, int tabstop, expanded_location s) { if (s.column <= 0) return -1; switch (column_unit) { default: gcc_unreachable (); case DIAGNOSTICS_COLUMN_UNIT_DISPLAY: { cpp_char_column_policy policy (tabstop, cpp_wcwidth); return location_compute_display_column (fc, s, policy); } case DIAGNOSTICS_COLUMN_UNIT_BYTE: return s.column; } } column_policy::column_policy (const context &dc) : m_file_cache (dc.get_file_cache ()), m_column_unit (dc.m_column_unit), m_column_origin (dc.m_column_origin), m_tabstop (dc.m_tabstop) { } /* Given an expanded_location, convert the column (which is in 1-based bytes) to the requested units and origin. Return -1 if the column is invalid (<= 0). */ int column_policy::converted_column (expanded_location s) const { int one_based_col = convert_column_unit (m_file_cache, m_column_unit, m_tabstop, s); if (one_based_col <= 0) return -1; return one_based_col + (m_column_origin - 1); } /* Return a string describing a location e.g. "foo.c:42:10". */ label_text column_policy::get_location_text (const expanded_location &s, bool show_column, bool colorize) const { const char *locus_cs = colorize_start (colorize, "locus"); const char *locus_ce = colorize_stop (colorize); const char *file = s.file ? s.file : progname; int line = 0; int col = -1; if (strcmp (file, special_fname_builtin ())) { line = s.line; if (show_column) col = converted_column (s); } const char *line_col = maybe_line_and_column (line, col); return label_text::take (build_message_string ("%s%s%s:%s", locus_cs, file, line_col, locus_ce)); } location_print_policy:: location_print_policy (const context &dc) : m_column_policy (dc), m_show_column (dc.m_show_column) { } location_print_policy:: location_print_policy (const text_sink &text_output) : m_column_policy (text_output.get_context ()), m_show_column (text_output.get_context ().m_show_column) { } } // namespace diagnostics /* Functions at which to stop the backtrace print. It's not particularly helpful to print the callers of these functions. */ static const char * const bt_stop[] = { "main", "toplev::main", "execute_one_pass", "compile_file", }; /* A callback function passed to the backtrace_full function. */ static int bt_callback (void *data, uintptr_t pc, const char *filename, int lineno, const char *function) { int *pcount = (int *) data; /* If we don't have any useful information, don't print anything. */ if (filename == nullptr && function == nullptr) return 0; /* Skip functions in context.cc. */ if (*pcount == 0 && filename != nullptr && strcmp (lbasename (filename), "context.cc") == 0) return 0; /* Print up to 20 functions. We could make this a --param, but since this is only for debugging just use a constant for now. */ if (*pcount >= 20) { /* Returning a non-zero value stops the backtrace. */ return 1; } ++*pcount; char *alc = nullptr; if (function != nullptr) { char *str = cplus_demangle_v3 (function, (DMGL_VERBOSE | DMGL_ANSI | DMGL_GNU_V3 | DMGL_PARAMS)); if (str != nullptr) { alc = str; function = str; } for (size_t i = 0; i < ARRAY_SIZE (bt_stop); ++i) { size_t len = strlen (bt_stop[i]); if (strncmp (function, bt_stop[i], len) == 0 && (function[len] == '\0' || function[len] == '(')) { if (alc != nullptr) free (alc); /* Returning a non-zero value stops the backtrace. */ return 1; } } } fprintf (stderr, "0x%lx %s\n\t%s:%d\n", (unsigned long) pc, function == nullptr ? "???" : function, filename == nullptr ? "???" : filename, lineno); if (alc != nullptr) free (alc); return 0; } /* A callback function passed to the backtrace_full function. This is called if backtrace_full has an error. */ static void bt_err_callback (void *data ATTRIBUTE_UNUSED, const char *msg, int errnum) { if (errnum < 0) { /* This means that no debug info was available. Just quietly skip printing backtrace info. */ return; } fprintf (stderr, "%s%s%s\n", msg, errnum == 0 ? "" : ": ", errnum == 0 ? "" : xstrerror (errnum)); } namespace diagnostics { /* Check if we've met the maximum error limit, and if so fatally exit with a message. FLUSH indicates whether a diagnostics::context::finish call is needed. */ void context::check_max_errors (bool flush) { if (!m_max_errors) return; int count = (diagnostic_count (kind::error) + diagnostic_count (kind::sorry) + diagnostic_count (kind::werror)); if (count >= m_max_errors) { fnotice (stderr, "compilation terminated due to -fmax-errors=%u.\n", m_max_errors); if (flush) finish (); exit (FATAL_EXIT_CODE); } } /* Take any action which is expected to happen after the diagnostic is written out. This function does not always return. */ void context::action_after_output (enum kind diag_kind) { switch (diag_kind) { case kind::debug: case kind::note: case kind::anachronism: case kind::warning: break; case kind::error: case kind::sorry: if (m_abort_on_error) real_abort (); if (m_fatal_errors) { fnotice (stderr, "compilation terminated due to -Wfatal-errors.\n"); finish (); exit (FATAL_EXIT_CODE); } break; case kind::ice: case kind::ice_nobt: { /* Attempt to ensure that any outputs are flushed e.g. that .sarif files are written out. Only do it once. */ static bool finishing_due_to_ice = false; if (!finishing_due_to_ice) { finishing_due_to_ice = true; finish (); } struct backtrace_state *state = nullptr; if (diag_kind == kind::ice) state = backtrace_create_state (nullptr, 0, bt_err_callback, nullptr); int count = 0; if (state != nullptr) backtrace_full (state, 2, bt_callback, bt_err_callback, (void *) &count); if (m_abort_on_error) real_abort (); if (m_report_bug) fnotice (stderr, "Please submit a full bug report, " "with preprocessed source.\n"); else fnotice (stderr, "Please submit a full bug report, " "with preprocessed source (by using -freport-bug).\n"); if (count > 0) fnotice (stderr, "Please include the complete backtrace " "with any bug report.\n"); fnotice (stderr, "See %s for instructions.\n", bug_report_url); exit (ICE_EXIT_CODE); } case kind::fatal: if (m_abort_on_error) real_abort (); fnotice (stderr, "compilation terminated.\n"); finish (); exit (FATAL_EXIT_CODE); default: gcc_unreachable (); } } /* State whether we should inhibit notes in the current diagnostic_group and its future children if any. */ void context::inhibit_notes_in_group (bool inhibit) { int curr_depth = (m_diagnostic_groups.m_group_nesting_depth + m_diagnostic_groups.m_diagnostic_nesting_level); if (inhibit) { /* If we're already inhibiting, there's nothing to do. */ if (m_diagnostic_groups.m_inhibiting_notes_from) return; /* Since we're called via warning/error/... that all have their own diagnostic_group, we must consider that we started inhibiting in their parent. */ gcc_assert (m_diagnostic_groups.m_group_nesting_depth > 0); m_diagnostic_groups.m_inhibiting_notes_from = curr_depth - 1; } else if (m_diagnostic_groups.m_inhibiting_notes_from) { /* Only cancel inhibition at the depth that set it up. */ if (curr_depth >= m_diagnostic_groups.m_inhibiting_notes_from) return; m_diagnostic_groups.m_inhibiting_notes_from = 0; } } /* Return whether notes must be inhibited in the current diagnostic_group. */ bool context::notes_inhibited_in_group () const { if (m_diagnostic_groups.m_inhibiting_notes_from && (m_diagnostic_groups.m_group_nesting_depth + m_diagnostic_groups.m_diagnostic_nesting_level >= m_diagnostic_groups.m_inhibiting_notes_from)) return true; return false; } /* class diagnostics::logical_locations::manager. */ /* Return true iff this is a function or method. */ bool logical_locations::manager::function_p (key k) const { switch (get_kind (k)) { default: gcc_unreachable (); case kind::unknown: case kind::module_: case kind::namespace_: case kind::type: case kind::return_type: case kind::parameter: case kind::variable: return false; case kind::function: case kind::member: return true; } } /* Helper function for print_parseable_fixits. Print TEXT to PP, obeying the escaping rules for -fdiagnostics-parseable-fixits. */ static void print_escaped_string (pretty_printer *pp, const char *text) { gcc_assert (pp); gcc_assert (text); pp_character (pp, '"'); for (const char *ch = text; *ch; ch++) { switch (*ch) { case '\\': /* Escape backslash as two backslashes. */ pp_string (pp, "\\\\"); break; case '\t': /* Escape tab as "\t". */ pp_string (pp, "\\t"); break; case '\n': /* Escape newline as "\n". */ pp_string (pp, "\\n"); break; case '"': /* Escape doublequotes as \". */ pp_string (pp, "\\\""); break; default: if (ISPRINT (*ch)) pp_character (pp, *ch); else /* Use octal for non-printable chars. */ { unsigned char c = (*ch & 0xff); pp_printf (pp, "\\%o%o%o", (c / 64), (c / 8) & 007, c & 007); } break; } } pp_character (pp, '"'); } /* Implementation of -fdiagnostics-parseable-fixits and GCC_EXTRA_DIAGNOSTIC_OUTPUT. Print a machine-parseable version of all fixits in RICHLOC to PP, using COLUMN_UNIT to express columns. Use TABSTOP when handling DIAGNOSTICS_COLUMN_UNIT_DISPLAY. */ static void print_parseable_fixits (file_cache &fc, pretty_printer *pp, rich_location *richloc, enum diagnostics_column_unit column_unit, int tabstop) { gcc_assert (pp); gcc_assert (richloc); char *saved_prefix = pp_take_prefix (pp); pp_set_prefix (pp, nullptr); for (unsigned i = 0; i < richloc->get_num_fixit_hints (); i++) { const fixit_hint *hint = richloc->get_fixit_hint (i); location_t start_loc = hint->get_start_loc (); expanded_location start_exploc = expand_location (start_loc); pp_string (pp, "fix-it:"); print_escaped_string (pp, start_exploc.file); /* For compatibility with clang, print as a half-open range. */ location_t next_loc = hint->get_next_loc (); expanded_location next_exploc = expand_location (next_loc); int start_col = convert_column_unit (fc, column_unit, tabstop, start_exploc); int next_col = convert_column_unit (fc, column_unit, tabstop, next_exploc); pp_printf (pp, ":{%i:%i-%i:%i}:", start_exploc.line, start_col, next_exploc.line, next_col); print_escaped_string (pp, hint->get_string ()); pp_newline (pp); } pp_set_prefix (pp, saved_prefix); } /* Update the inlining info in this context for a DIAGNOSTIC. */ void context::get_any_inlining_info (diagnostic_info *diagnostic) { auto &ilocs = diagnostic->m_iinfo.m_ilocs; if (m_set_locations_cb) /* Retrieve the locations into which the expression about to be diagnosed has been inlined, including those of all the callers all the way down the inlining stack. */ m_set_locations_cb (this, diagnostic); else { /* When there's no callback use just the one location provided by the caller of the diagnostic function. */ location_t loc = diagnostic_location (diagnostic); ilocs.safe_push (loc); diagnostic->m_iinfo.m_allsyslocs = in_system_header_at (loc); } } /* Generate a URL string describing CWE. The caller is responsible for freeing the string. */ char * get_cwe_url (int cwe) { return xasprintf ("https://cwe.mitre.org/data/definitions/%i.html", cwe); } /* Returns whether a DIAGNOSTIC should be printed, and adjusts diagnostic->kind as appropriate for #pragma GCC diagnostic and -Werror=foo. */ bool context::diagnostic_enabled (diagnostic_info *diagnostic) { /* Update the inlining stack for this diagnostic. */ get_any_inlining_info (diagnostic); /* Diagnostics with no option or -fpermissive are always enabled. */ if (!diagnostic->m_option_id.m_idx || diagnostic->m_option_id == m_opt_permissive) return true; /* This tests if the user provided the appropriate -Wfoo or -Wno-foo option. */ if (!option_enabled_p (diagnostic->m_option_id)) return false; /* This tests for #pragma diagnostic changes. */ enum kind diag_class = m_option_classifier.update_effective_level_from_pragmas (diagnostic); /* This tests if the user provided the appropriate -Werror=foo option. */ if (diag_class == kind::unspecified && !option_unspecified_p (diagnostic->m_option_id)) { const enum kind new_kind = m_option_classifier.get_current_override (diagnostic->m_option_id); if (new_kind != kind::any) /* kind::any means the diagnostic is not to be ignored, but we don't want to change it specifically to kind::error or kind::warning; we want to preserve whatever the caller has specified. */ diagnostic->m_kind = new_kind; } /* This allows for future extensions, like temporarily disabling warnings for ranges of source code. */ if (diagnostic->m_kind == kind::ignored) return false; return true; } /* Returns whether warning OPT_ID is enabled at LOC. */ bool context::warning_enabled_at (location_t loc, option_id opt_id) { if (!diagnostic_report_warnings_p (this, loc)) return false; rich_location richloc (line_table, loc); diagnostic_info diagnostic = {}; diagnostic.m_option_id = opt_id; diagnostic.m_richloc = &richloc; diagnostic.m_message.m_richloc = &richloc; diagnostic.m_kind = kind::warning; return diagnostic_enabled (&diagnostic); } /* Emit a diagnostic within a diagnostic group on this context. */ bool context::emit_diagnostic_with_group (enum kind kind, rich_location &richloc, const metadata *metadata, option_id opt_id, const char *gmsgid, ...) { begin_group (); va_list ap; va_start (ap, gmsgid); bool ret = emit_diagnostic_with_group_va (kind, richloc, metadata, opt_id, gmsgid, &ap); va_end (ap); end_group (); return ret; } /* As above, but taking a va_list *. */ bool context::emit_diagnostic_with_group_va (enum kind kind, rich_location &richloc, const metadata *metadata, option_id opt_id, const char *gmsgid, va_list *ap) { begin_group (); bool ret = diagnostic_impl (&richloc, metadata, opt_id, gmsgid, ap, kind); end_group (); return ret; } /* Report a diagnostic message (an error or a warning) as specified by this diagnostics::context. front-end independent format specifiers are exactly those described in the documentation of output_format. Return true if a diagnostic was printed, false otherwise. */ bool context::report_diagnostic (diagnostic_info *diagnostic) { enum kind orig_diag_kind = diagnostic->m_kind; /* Every call to report_diagnostic should be within a begin_group/end_group pair so that output formats can reliably flush diagnostics with on_end_group when the topmost group is ended. */ gcc_assert (m_diagnostic_groups.m_group_nesting_depth > 0); /* Give preference to being able to inhibit warnings, before they get reclassified to something else. */ bool was_warning = (diagnostic->m_kind == kind::warning || diagnostic->m_kind == kind::pedwarn); if (was_warning && m_inhibit_warnings) { inhibit_notes_in_group (); return false; } if (m_adjust_diagnostic_info) m_adjust_diagnostic_info (this, diagnostic); if (diagnostic->m_kind == kind::pedwarn) { diagnostic->m_kind = m_pedantic_errors ? kind::error : kind::warning; /* We do this to avoid giving the message for -pedantic-errors. */ orig_diag_kind = diagnostic->m_kind; } if (diagnostic->m_kind == kind::note && m_inhibit_notes_p) return false; /* If the user requested that warnings be treated as errors, so be it. Note that we do this before the next block so that individual warnings can be overridden back to warnings with -Wno-error=*. */ if (m_warning_as_error_requested && diagnostic->m_kind == kind::warning) diagnostic->m_kind = kind::error; diagnostic->m_message.m_data = &diagnostic->m_x_data; /* Check to see if the diagnostic is enabled at the location and not disabled by #pragma GCC diagnostic anywhere along the inlining stack. . */ if (!diagnostic_enabled (diagnostic)) { inhibit_notes_in_group (); return false; } if ((was_warning || diagnostic->m_kind == kind::warning) && ((!m_warn_system_headers && diagnostic->m_iinfo.m_allsyslocs) || m_inhibit_warnings)) /* Bail if the warning is not to be reported because all locations in the inlining stack (if there is one) are in system headers. */ return false; if (diagnostic->m_kind == kind::note && notes_inhibited_in_group ()) /* Bail for all the notes in the diagnostic_group that started to inhibit notes. */ return false; if (diagnostic->m_kind != kind::note && diagnostic->m_kind != kind::ice) check_max_errors (false); if (m_lock > 0) { /* If we're reporting an ICE in the middle of some other error, try to flush out the previous error, then let this one through. Don't do this more than once. */ if ((diagnostic->m_kind == kind::ice || diagnostic->m_kind == kind::ice_nobt) && m_lock == 1) pp_newline_and_flush (m_reference_printer); else error_recursion (); } /* We are accepting the diagnostic, so should stop inhibiting notes. */ inhibit_notes_in_group (/*inhibit=*/false); m_lock++; if (diagnostic->m_kind == kind::ice || diagnostic->m_kind == kind::ice_nobt) { /* When not checking, ICEs are converted to fatal errors when an error has already occurred. This is counteracted by abort_on_error. */ if (!CHECKING_P && (diagnostic_count (kind::error) > 0 || diagnostic_count (kind::sorry) > 0) && !m_abort_on_error) { expanded_location s = expand_location (diagnostic_location (diagnostic)); fnotice (stderr, "%s:%d: confused by earlier errors, bailing out\n", s.file, s.line); exit (ICE_EXIT_CODE); } if (m_internal_error) (*m_internal_error) (this, diagnostic->m_message.m_format_spec, diagnostic->m_message.m_args_ptr); } /* Increment the counter for the appropriate diagnostic kind, either within this context, or within the diagnostic_buffer. */ { const enum kind kind_for_count = ((diagnostic->m_kind == kind::error && orig_diag_kind == kind::warning) ? kind::werror : diagnostic->m_kind); counters &cs = (m_diagnostic_buffer ? m_diagnostic_buffer->m_diagnostic_counters : m_diagnostic_counters); ++cs.m_count_for_kind[static_cast (kind_for_count)]; } /* Is this the initial diagnostic within the stack of groups? */ if (m_diagnostic_groups.m_emission_count == 0) for (auto sink_ : m_sinks) sink_->on_begin_group (); m_diagnostic_groups.m_emission_count++; va_list *orig_args = diagnostic->m_message.m_args_ptr; for (auto sink_ : m_sinks) { /* Formatting the message is done per-output-format, so that each output format gets its own set of pp_token_lists to work with. Run phases 1 and 2 of formatting the message before calling the format's on_report_diagnostic. In particular, some format codes may have side-effects here which need to happen before sending the diagnostic to the output format. For example, Fortran's %C and %L formatting codes populate the rich_location. Such side-effects must be idempotent, since they are run per output-format. Make a duplicate of the varargs for each call to pp_format, so that each has its own set to consume. */ va_list copied_args; va_copy (copied_args, *orig_args); diagnostic->m_message.m_args_ptr = &copied_args; pp_format (sink_->get_printer (), &diagnostic->m_message); va_end (copied_args); /* Call vfunc in the output format. This is responsible for phase 3 of formatting, and for printing the result. */ sink_->on_report_diagnostic (*diagnostic, orig_diag_kind); } switch (m_extra_output_kind) { default: break; case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1: print_parseable_fixits (get_file_cache (), m_reference_printer, diagnostic->m_richloc, DIAGNOSTICS_COLUMN_UNIT_BYTE, m_tabstop); pp_flush (m_reference_printer); break; case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v2: print_parseable_fixits (get_file_cache (), m_reference_printer, diagnostic->m_richloc, DIAGNOSTICS_COLUMN_UNIT_DISPLAY, m_tabstop); pp_flush (m_reference_printer); break; } if (m_diagnostic_buffer == nullptr || diagnostic->m_kind == kind::ice || diagnostic->m_kind == kind::ice_nobt) action_after_output (diagnostic->m_kind); diagnostic->m_x_data = nullptr; if (m_fixits_change_set) if (diagnostic->m_richloc->fixits_can_be_auto_applied_p ()) if (!m_diagnostic_buffer) m_fixits_change_set->add_fixits (diagnostic->m_richloc); m_lock--; if (!m_diagnostic_buffer) for (auto sink_ : m_sinks) sink_->after_diagnostic (*diagnostic); return true; } void context::report_verbatim (text_info &text) { va_list *orig_args = text.m_args_ptr; for (auto sink_ : m_sinks) { va_list copied_args; va_copy (copied_args, *orig_args); text.m_args_ptr = &copied_args; sink_->on_report_verbatim (text); va_end (copied_args); } } void context::report_global_digraph (const lazily_created &ldg) { for (auto sink_ : m_sinks) sink_->report_global_digraph (ldg); } /* Get the number of digits in the decimal representation of VALUE. */ int num_digits (int value) { /* Perhaps simpler to use log10 for this, but doing it this way avoids using floating point. */ gcc_assert (value >= 0); if (value == 0) return 1; int digits = 0; while (value > 0) { digits++; value /= 10; } return digits; } } // namespace diagnostics /* Given a partial pathname as input, return another pathname that shares no directory elements with the pathname of __FILE__. This is used by fancy_abort() to print `internal compiler error in expr.cc' instead of `internal compiler error in ../../GCC/gcc/expr.cc'. */ const char * trim_filename (const char *name) { static const char this_file[] = __FILE__; const char *p = name, *q = this_file; /* First skip any "../" in each filename. This allows us to give a proper reference to a file in a subdirectory. */ while (p[0] == '.' && p[1] == '.' && IS_DIR_SEPARATOR (p[2])) p += 3; while (q[0] == '.' && q[1] == '.' && IS_DIR_SEPARATOR (q[2])) q += 3; /* Now skip any parts the two filenames have in common. */ while (*p == *q && *p != 0 && *q != 0) p++, q++; /* Now go backwards until the previous directory separator. */ while (p > name && !IS_DIR_SEPARATOR (p[-1])) p--; return p; } namespace diagnostics { /* Implement emit_diagnostic, inform, warning, warning_at, pedwarn, permerror, error, error_at, error_at, sorry, fatal_error, internal_error, and internal_error_no_backtrace, as documented and defined below. */ bool context::diagnostic_impl (rich_location *richloc, const metadata *metadata, option_id opt_id, const char *gmsgid, va_list *ap, enum kind kind) { diagnostic_info diagnostic; if (kind == diagnostics::kind::permerror) { diagnostic_set_info (&diagnostic, gmsgid, ap, richloc, (m_permissive ? diagnostics::kind::warning : diagnostics::kind::error)); diagnostic.m_option_id = (opt_id.m_idx != -1 ? opt_id : m_opt_permissive); } else { diagnostic_set_info (&diagnostic, gmsgid, ap, richloc, kind); if (kind == diagnostics::kind::warning || kind == diagnostics::kind::pedwarn) diagnostic.m_option_id = opt_id; } diagnostic.m_metadata = metadata; return report_diagnostic (&diagnostic); } /* Implement inform_n, warning_n, and error_n, as documented and defined below. */ bool context::diagnostic_n_impl (rich_location *richloc, const metadata *metadata, option_id opt_id, unsigned HOST_WIDE_INT n, const char *singular_gmsgid, const char *plural_gmsgid, va_list *ap, enum kind kind) { diagnostic_info diagnostic; unsigned long gtn; if (sizeof n <= sizeof gtn) gtn = n; else /* Use the largest number ngettext can handle, otherwise preserve the six least significant decimal digits for languages where the plural form depends on them. */ gtn = n <= ULONG_MAX ? n : n % 1000000LU + 1000000LU; const char *text = ngettext (singular_gmsgid, plural_gmsgid, gtn); diagnostic_set_info_translated (&diagnostic, text, ap, richloc, kind); if (kind == diagnostics::kind::warning) diagnostic.m_option_id = opt_id; diagnostic.m_metadata = metadata; return report_diagnostic (&diagnostic); } /* Emit DIAGRAM to this context, respecting the output format. */ void context::emit_diagram (const diagram &diag) { if (m_diagrams.m_theme == nullptr) return; for (auto sink_ : m_sinks) sink_->on_diagram (diag); } /* Inform the user that an error occurred while trying to report some other error. This indicates catastrophic internal inconsistencies, so give up now. But do try to flush out the previous error. This mustn't use internal_error, that will cause infinite recursion. */ void context::error_recursion () { if (m_lock < 3) pp_newline_and_flush (m_reference_printer); fnotice (stderr, "internal compiler error: error reporting routines re-entered.\n"); /* Call action_after_output to get the "please submit a bug report" message. */ action_after_output (kind::ice); /* Do not use gcc_unreachable here; that goes through internal_error and therefore would cause infinite recursion. */ real_abort (); } } // namespace diagnostics /* Report an internal compiler error in a friendly manner. This is the function that gets called upon use of abort() in the source code generally, thanks to a special macro. */ void fancy_abort (const char *file, int line, const char *function) { /* If fancy_abort is called before the diagnostic subsystem is initialized, internal_error will crash internally in a way that prevents a useful message reaching the user. This can happen with libgccjit in the case of gcc_assert failures that occur outside of the libgccjit mutex that guards the rest of gcc's state, including global_dc (when global_dc may not be initialized yet, or might be in use by another thread). Handle such cases as gracefully as possible by falling back to a minimal abort handler that only relies on i18n. */ if (global_dc->get_reference_printer () == nullptr) { /* Print the error message. */ fnotice (stderr, diagnostics::get_text_for_kind (diagnostics::kind::ice)); fnotice (stderr, "in %s, at %s:%d", function, trim_filename (file), line); fputc ('\n', stderr); /* Attempt to print a backtrace. */ struct backtrace_state *state = backtrace_create_state (nullptr, 0, bt_err_callback, nullptr); int count = 0; if (state != nullptr) backtrace_full (state, 2, bt_callback, bt_err_callback, (void *) &count); /* We can't call warn_if_plugins or emergency_dump_function as these rely on GCC state that might not be initialized, or might be in use by another thread. */ /* Abort the process. */ real_abort (); } internal_error ("in %s, at %s:%d", function, trim_filename (file), line); } namespace diagnostics { /* class diagnostics::context. */ void context::begin_group () { m_diagnostic_groups.m_group_nesting_depth++; } void context::end_group () { if (--m_diagnostic_groups.m_group_nesting_depth == 0) { /* Handle the case where we've popped the final diagnostic group. If any diagnostics were emitted, give the context a chance to do something. */ if (m_diagnostic_groups.m_emission_count > 0) for (auto sink_ : m_sinks) sink_->on_end_group (); m_diagnostic_groups.m_emission_count = 0; } /* We're popping one level, so might need to stop inhibiting notes. */ inhibit_notes_in_group (/*inhibit=*/false); } void context::push_nesting_level () { ++m_diagnostic_groups.m_diagnostic_nesting_level; } void context::pop_nesting_level () { --m_diagnostic_groups.m_diagnostic_nesting_level; /* We're popping one level, so might need to stop inhibiting notes. */ inhibit_notes_in_group (/*inhibit=*/false); } void sink::dump (FILE *out, int indent) const { fprintf (out, "%*sprinter:\n", indent, ""); m_printer->dump (out, indent + 2); } void sink::on_report_verbatim (text_info &) { /* No-op. */ } /* Set the output format for DC to FORMAT, using BASE_FILE_NAME for file-based output formats. */ void output_format_init (context &dc, const char *main_input_filename_, const char *base_file_name, enum diagnostics_output_format format, bool json_formatting) { sink *new_sink = nullptr; switch (format) { default: gcc_unreachable (); case DIAGNOSTICS_OUTPUT_FORMAT_TEXT: /* The default; do nothing. */ break; case DIAGNOSTICS_OUTPUT_FORMAT_SARIF_STDERR: new_sink = &init_sarif_stderr (dc, line_table, json_formatting); break; case DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE: new_sink = &init_sarif_file (dc, line_table, json_formatting, base_file_name); break; } if (new_sink) new_sink->set_main_input_filename (main_input_filename_); } /* Initialize this context's m_diagrams based on CHARSET. Specifically, make a text_art::theme object for m_diagrams.m_theme, (or nullptr for "no diagrams"). */ void context::set_text_art_charset (enum diagnostic_text_art_charset charset) { delete m_diagrams.m_theme; switch (charset) { default: gcc_unreachable (); case DIAGNOSTICS_TEXT_ART_CHARSET_NONE: m_diagrams.m_theme = nullptr; break; case DIAGNOSTICS_TEXT_ART_CHARSET_ASCII: m_diagrams.m_theme = new text_art::ascii_theme (); break; case DIAGNOSTICS_TEXT_ART_CHARSET_UNICODE: m_diagrams.m_theme = new text_art::unicode_theme (); break; case DIAGNOSTICS_TEXT_ART_CHARSET_EMOJI: m_diagrams.m_theme = new text_art::emoji_theme (); break; } } /* struct diagnostics::counters. */ counters::counters () { clear (); } void counters::dump (FILE *out, int indent) const { fprintf (out, "%*scounts:\n", indent, ""); bool none = true; for (int i = 0; i < static_cast (kind::last_diagnostic_kind); i++) if (m_count_for_kind[i] > 0) { fprintf (out, "%*s%s%i\n", indent + 2, "", get_text_for_kind (static_cast (i)), m_count_for_kind[i]); none = false; } if (none) fprintf (out, "%*s(none)\n", indent + 2, ""); } void counters::move_to (counters &dest) { for (int i = 0; i < static_cast (kind::last_diagnostic_kind); i++) dest.m_count_for_kind[i] += m_count_for_kind[i]; clear (); } void counters::clear () { memset (&m_count_for_kind, 0, sizeof m_count_for_kind); } #if CHECKING_P namespace selftest { using line_table_test = ::selftest::line_table_test; using temp_source_file = ::selftest::temp_source_file; /* Helper function for test_print_escaped_string. */ static void assert_print_escaped_string (const ::selftest::location &loc, const char *expected_output, const char *input) { pretty_printer pp; print_escaped_string (&pp, input); ASSERT_STREQ_AT (loc, expected_output, pp_formatted_text (&pp)); } #define ASSERT_PRINT_ESCAPED_STRING_STREQ(EXPECTED_OUTPUT, INPUT) \ assert_print_escaped_string (SELFTEST_LOCATION, EXPECTED_OUTPUT, INPUT) /* Tests of print_escaped_string. */ static void test_print_escaped_string () { /* Empty string. */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"\"", ""); /* Non-empty string. */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"hello world\"", "hello world"); /* Various things that need to be escaped: */ /* Backslash. */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\\\after\"", "before\\after"); /* Tab. */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\tafter\"", "before\tafter"); /* Newline. */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\nafter\"", "before\nafter"); /* Double quote. */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\\"after\"", "before\"after"); /* Non-printable characters: BEL: '\a': 0x07 */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\007after\"", "before\aafter"); /* Non-printable characters: vertical tab: '\v': 0x0b */ ASSERT_PRINT_ESCAPED_STRING_STREQ ("\"before\\013after\"", "before\vafter"); } /* Tests of print_parseable_fixits. */ /* Verify that print_parseable_fixits emits the empty string if there are no fixits. */ static void test_print_parseable_fixits_none () { pretty_printer pp; file_cache fc; rich_location richloc (line_table, UNKNOWN_LOCATION); print_parseable_fixits (fc, &pp, &richloc, DIAGNOSTICS_COLUMN_UNIT_BYTE, 8); ASSERT_STREQ ("", pp_formatted_text (&pp)); } /* Verify that print_parseable_fixits does the right thing if there is an insertion fixit hint. */ static void test_print_parseable_fixits_insert () { pretty_printer pp; file_cache fc; rich_location richloc (line_table, UNKNOWN_LOCATION); linemap_add (line_table, LC_ENTER, false, "test.c", 0); linemap_line_start (line_table, 5, 100); linemap_add (line_table, LC_LEAVE, false, nullptr, 0); location_t where = linemap_position_for_column (line_table, 10); richloc.add_fixit_insert_before (where, "added content"); print_parseable_fixits (fc, &pp, &richloc, DIAGNOSTICS_COLUMN_UNIT_BYTE, 8); ASSERT_STREQ ("fix-it:\"test.c\":{5:10-5:10}:\"added content\"\n", pp_formatted_text (&pp)); } /* Verify that print_parseable_fixits does the right thing if there is an removal fixit hint. */ static void test_print_parseable_fixits_remove () { pretty_printer pp; file_cache fc; rich_location richloc (line_table, UNKNOWN_LOCATION); linemap_add (line_table, LC_ENTER, false, "test.c", 0); linemap_line_start (line_table, 5, 100); linemap_add (line_table, LC_LEAVE, false, nullptr, 0); source_range where; where.m_start = linemap_position_for_column (line_table, 10); where.m_finish = linemap_position_for_column (line_table, 20); richloc.add_fixit_remove (where); print_parseable_fixits (fc, &pp, &richloc, DIAGNOSTICS_COLUMN_UNIT_BYTE, 8); ASSERT_STREQ ("fix-it:\"test.c\":{5:10-5:21}:\"\"\n", pp_formatted_text (&pp)); } /* Verify that print_parseable_fixits does the right thing if there is an replacement fixit hint. */ static void test_print_parseable_fixits_replace () { pretty_printer pp; file_cache fc; rich_location richloc (line_table, UNKNOWN_LOCATION); linemap_add (line_table, LC_ENTER, false, "test.c", 0); linemap_line_start (line_table, 5, 100); linemap_add (line_table, LC_LEAVE, false, nullptr, 0); source_range where; where.m_start = linemap_position_for_column (line_table, 10); where.m_finish = linemap_position_for_column (line_table, 20); richloc.add_fixit_replace (where, "replacement"); print_parseable_fixits (fc, &pp, &richloc, DIAGNOSTICS_COLUMN_UNIT_BYTE, 8); ASSERT_STREQ ("fix-it:\"test.c\":{5:10-5:21}:\"replacement\"\n", pp_formatted_text (&pp)); } /* Verify that print_parseable_fixits correctly handles DIAGNOSTICS_COLUMN_UNIT_BYTE vs DIAGNOSTICS_COLUMN_UNIT_COLUMN. */ static void test_print_parseable_fixits_bytes_vs_display_columns () { line_table_test ltt; rich_location richloc (line_table, UNKNOWN_LOCATION); /* 1-based byte offsets: 12345677778888999900001234567. */ const char *const content = "smile \xf0\x9f\x98\x82 colour\n"; /* 1-based display cols: 123456[......7-8.....]9012345. */ const int tabstop = 8; temp_source_file tmp (SELFTEST_LOCATION, ".c", content); file_cache fc; const char *const fname = tmp.get_filename (); linemap_add (line_table, LC_ENTER, false, fname, 0); linemap_line_start (line_table, 1, 100); linemap_add (line_table, LC_LEAVE, false, nullptr, 0); source_range where; where.m_start = linemap_position_for_column (line_table, 12); where.m_finish = linemap_position_for_column (line_table, 17); richloc.add_fixit_replace (where, "color"); /* Escape fname. */ pretty_printer tmp_pp; print_escaped_string (&tmp_pp, fname); char *escaped_fname = xstrdup (pp_formatted_text (&tmp_pp)); const int buf_len = strlen (escaped_fname) + 100; char *const expected = XNEWVEC (char, buf_len); { pretty_printer pp; print_parseable_fixits (fc, &pp, &richloc, DIAGNOSTICS_COLUMN_UNIT_BYTE, tabstop); snprintf (expected, buf_len, "fix-it:%s:{1:12-1:18}:\"color\"\n", escaped_fname); ASSERT_STREQ (expected, pp_formatted_text (&pp)); } { pretty_printer pp; print_parseable_fixits (fc, &pp, &richloc, DIAGNOSTICS_COLUMN_UNIT_DISPLAY, tabstop); snprintf (expected, buf_len, "fix-it:%s:{1:10-1:16}:\"color\"\n", escaped_fname); ASSERT_STREQ (expected, pp_formatted_text (&pp)); } XDELETEVEC (expected); free (escaped_fname); } /* Verify that diagnostics::column_policy::get_location_text (..., SHOW_COLUMN, ...) generates EXPECTED_LOC_TEXT, given FILENAME, LINE, COLUMN, with colorization disabled. */ static void assert_location_text (const char *expected_loc_text, const char *filename, int line, int column, bool show_column, int origin = 1, enum diagnostics_column_unit column_unit = DIAGNOSTICS_COLUMN_UNIT_BYTE) { diagnostics::selftest::test_context dc; dc.m_column_unit = column_unit; dc.m_column_origin = origin; expanded_location xloc; xloc.file = filename; xloc.line = line; xloc.column = column; xloc.data = nullptr; xloc.sysp = false; diagnostics::column_policy column_policy (dc); label_text actual_loc_text = column_policy.get_location_text (xloc, show_column, false); ASSERT_STREQ (expected_loc_text, actual_loc_text.get ()); } /* Verify that get_location_text works as expected. */ static void test_get_location_text () { const char *old_progname = progname; progname = "PROGNAME"; assert_location_text ("PROGNAME:", nullptr, 0, 0, true); char *built_in_colon = concat (special_fname_builtin (), ":", (char *) 0); assert_location_text (built_in_colon, special_fname_builtin (), 42, 10, true); free (built_in_colon); assert_location_text ("foo.c:42:10:", "foo.c", 42, 10, true); assert_location_text ("foo.c:42:9:", "foo.c", 42, 10, true, 0); assert_location_text ("foo.c:42:1010:", "foo.c", 42, 10, true, 1001); for (int origin = 0; origin != 2; ++origin) assert_location_text ("foo.c:42:", "foo.c", 42, 0, true, origin); assert_location_text ("foo.c:", "foo.c", 0, 10, true); assert_location_text ("foo.c:42:", "foo.c", 42, 10, false); assert_location_text ("foo.c:", "foo.c", 0, 10, false); diagnostics::maybe_line_and_column (INT_MAX, INT_MAX); diagnostics::maybe_line_and_column (INT_MIN, INT_MIN); { /* In order to test display columns vs byte columns, we need to create a file for location_get_source_line() to read. */ const char *const content = "smile \xf0\x9f\x98\x82\n"; const int line_bytes = strlen (content) - 1; const int def_tabstop = 8; const cpp_char_column_policy policy (def_tabstop, cpp_wcwidth); const int display_width = cpp_display_width (content, line_bytes, policy); ASSERT_EQ (line_bytes - 2, display_width); temp_source_file tmp (SELFTEST_LOCATION, ".c", content); const char *const fname = tmp.get_filename (); const int buf_len = strlen (fname) + 16; char *const expected = XNEWVEC (char, buf_len); snprintf (expected, buf_len, "%s:1:%d:", fname, line_bytes); assert_location_text (expected, fname, 1, line_bytes, true, 1, DIAGNOSTICS_COLUMN_UNIT_BYTE); snprintf (expected, buf_len, "%s:1:%d:", fname, line_bytes - 1); assert_location_text (expected, fname, 1, line_bytes, true, 0, DIAGNOSTICS_COLUMN_UNIT_BYTE); snprintf (expected, buf_len, "%s:1:%d:", fname, display_width); assert_location_text (expected, fname, 1, line_bytes, true, 1, DIAGNOSTICS_COLUMN_UNIT_DISPLAY); snprintf (expected, buf_len, "%s:1:%d:", fname, display_width - 1); assert_location_text (expected, fname, 1, line_bytes, true, 0, DIAGNOSTICS_COLUMN_UNIT_DISPLAY); XDELETEVEC (expected); } progname = old_progname; } /* Selftest for num_digits. */ static void test_num_digits () { ASSERT_EQ (1, num_digits (0)); ASSERT_EQ (1, num_digits (9)); ASSERT_EQ (2, num_digits (10)); ASSERT_EQ (2, num_digits (99)); ASSERT_EQ (3, num_digits (100)); ASSERT_EQ (3, num_digits (999)); ASSERT_EQ (4, num_digits (1000)); ASSERT_EQ (4, num_digits (9999)); ASSERT_EQ (5, num_digits (10000)); ASSERT_EQ (5, num_digits (99999)); ASSERT_EQ (6, num_digits (100000)); ASSERT_EQ (6, num_digits (999999)); ASSERT_EQ (7, num_digits (1000000)); ASSERT_EQ (7, num_digits (9999999)); ASSERT_EQ (8, num_digits (10000000)); ASSERT_EQ (8, num_digits (99999999)); } /* Run all of the selftests within this file. According to https://gcc.gnu.org/pipermail/gcc/2021-November/237703.html there are some language-specific assumptions within these tests, so only run them from C/C++. */ void context_cc_tests () { test_print_escaped_string (); test_print_parseable_fixits_none (); test_print_parseable_fixits_insert (); test_print_parseable_fixits_remove (); test_print_parseable_fixits_replace (); test_print_parseable_fixits_bytes_vs_display_columns (); test_get_location_text (); test_num_digits (); } } // namespace diagnostics::selftest } // namespace diagnostics #endif /* #if CHECKING_P */ #if __GNUC__ >= 10 # pragma GCC diagnostic pop #endif static void real_abort (void) ATTRIBUTE_NORETURN; /* Really call the system 'abort'. This has to go right at the end of this file, so that there are no functions after it that call abort and get the system abort instead of our macro. */ #undef abort static void real_abort (void) { abort (); }