aboutsummaryrefslogtreecommitdiff
path: root/gcc/diagnostics/context.cc
diff options
context:
space:
mode:
Diffstat (limited to 'gcc/diagnostics/context.cc')
-rw-r--r--gcc/diagnostics/context.cc2152
1 files changed, 2152 insertions, 0 deletions
diff --git a/gcc/diagnostics/context.cc b/gcc/diagnostics/context.cc
new file mode 100644
index 0000000..85f7d2a
--- /dev/null
+++ b/gcc/diagnostics/context.cc
@@ -0,0 +1,2152 @@
+/* Language-independent diagnostic subroutines for the GNU Compiler Collection
+ Copyright (C) 1999-2025 Free Software Foundation, Inc.
+ Contributed by Gabriel Dos Reis <gdr@codesourcery.com>
+
+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
+<http://www.gnu.org/licenses/>. */
+
+
+/* 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 <termios.h>
+#endif
+
+#ifdef GWINSZ_IN_SYS_IOCTL
+# include <sys/ioctl.h>
+#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<pretty_printer> ().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<to_text>;
+ m_text_callbacks.m_html_start_span
+ = default_start_span_fn<to_html>;
+ m_text_callbacks.m_end_diagnostic = default_text_finalizer;
+ m_option_mgr = nullptr;
+ m_urlifier_stack = new auto_vec<urlifier_stack_node> ();
+ 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> 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> 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<client_data_hooks> 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<option_manager> 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<urlifier> 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 <urlifier *> (&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<pretty_printer> 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<int> (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<int> (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<size_t> (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<digraphs::digraph> &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<int> (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<enum kind> (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<int> (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
+
+#endif /* #if CHECKING_P */
+
+} // namespace diagnostics
+
+#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 ();
+}