| /* 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 (); |
| } |