diff options
Diffstat (limited to 'gcc/diagnostics/context.cc')
-rw-r--r-- | gcc/diagnostics/context.cc | 2152 |
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 (); +} |