/* Classic text-based output of diagnostics. Copyright (C) 1999-2025 Free Software Foundation, Inc. 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 . */ #include "config.h" #define INCLUDE_VECTOR #include "system.h" #include "coretypes.h" #include "version.h" #include "intl.h" #include "diagnostic.h" #include "diagnostic-color.h" #include "diagnostic-url.h" #include "diagnostic-metadata.h" #include "diagnostic-path.h" #include "diagnostic-client-data-hooks.h" #include "diagnostic-diagram.h" #include "diagnostic-format-text.h" #include "diagnostic-buffer.h" #include "text-art/theme.h" #include "make-unique.h" /* 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 /* Concrete buffering implementation subclass for JSON output. */ class diagnostic_text_format_buffer : public diagnostic_per_format_buffer { public: friend class diagnostic_text_output_format; diagnostic_text_format_buffer (diagnostic_output_format &format); void dump (FILE *out, int indent) const final override; bool empty_p () const final override; void move_to (diagnostic_per_format_buffer &dest) final override; void clear () final override; void flush () final override; private: diagnostic_output_format &m_format; output_buffer m_output_buffer; }; /* class diagnostic_text_format_buffer : public diagnostic_per_format_buffer. */ diagnostic_text_format_buffer:: diagnostic_text_format_buffer (diagnostic_output_format &format) : m_format (format) { m_output_buffer.m_flush_p = false; } void diagnostic_text_format_buffer::dump (FILE *out, int indent) const { fprintf (out, "%*sdiagnostic_text_format_buffer:\n", indent, ""); m_output_buffer.dump (out, indent + 2); } bool diagnostic_text_format_buffer::empty_p () const { return output_buffer_last_position_in_text (&m_output_buffer) == nullptr; } void diagnostic_text_format_buffer::move_to (diagnostic_per_format_buffer &base_dest) { diagnostic_text_format_buffer &dest = static_cast (base_dest); const char *str = output_buffer_formatted_text (&m_output_buffer); output_buffer_append_r (&dest.m_output_buffer, str, strlen (str)); obstack_free (m_output_buffer.m_obstack, obstack_base (m_output_buffer.m_obstack)); m_output_buffer.m_line_length = 0; } void diagnostic_text_format_buffer::clear () { pretty_printer *const pp = m_format.get_printer (); output_buffer *const old_output_buffer = pp_buffer (pp); pp_buffer (pp) = &m_output_buffer; pp_clear_output_area (pp); gcc_assert (empty_p ()); pp_buffer (pp) = old_output_buffer; } void diagnostic_text_format_buffer::flush () { pretty_printer *const pp = m_format.get_printer (); output_buffer *const old_output_buffer = pp_buffer (pp); pp_buffer (pp) = &m_output_buffer; pp_really_flush (pp); gcc_assert (empty_p ()); pp_buffer (pp) = old_output_buffer; } /* class diagnostic_text_output_format : public diagnostic_output_format. */ diagnostic_text_output_format::~diagnostic_text_output_format () { /* Some of the errors may actually have been warnings. */ if (m_context.diagnostic_count (DK_WERROR)) { pretty_printer *pp = get_printer (); /* -Werror was given. */ if (m_context.warning_as_error_requested_p ()) pp_verbatim (pp, _("%s: all warnings being treated as errors"), progname); /* At least one -Werror= was given. */ else pp_verbatim (pp, _("%s: some warnings being treated as errors"), progname); pp_newline_and_flush (pp); } if (m_includes_seen) { delete m_includes_seen; m_includes_seen = nullptr; } } void diagnostic_text_output_format::dump (FILE *out, int indent) const { fprintf (out, "%*sdiagnostic_text_output_format\n", indent, ""); fprintf (out, "%*sm_follows_reference_printer: %s\n", indent, "", m_follows_reference_printer ? "true" : "false"); diagnostic_output_format::dump (out, indent); fprintf (out, "%*ssaved_output_buffer:\n", indent + 2, ""); if (m_saved_output_buffer) m_saved_output_buffer->dump (out, indent + 4); else fprintf (out, "%*s(none):\n", indent + 4, ""); } void diagnostic_text_output_format::set_buffer (diagnostic_per_format_buffer *base) { diagnostic_text_format_buffer * const buffer = static_cast (base); pretty_printer *const pp = get_printer (); if (!m_saved_output_buffer) m_saved_output_buffer = pp_buffer (pp); if (buffer) pp_buffer (pp) = &buffer->m_output_buffer; else { gcc_assert (m_saved_output_buffer); pp_buffer (pp) = m_saved_output_buffer; } } std::unique_ptr diagnostic_text_output_format::make_per_format_buffer () { return ::make_unique (*this); } /* Implementation of diagnostic_output_format::on_report_diagnostic vfunc for GCC's standard textual output. */ void diagnostic_text_output_format:: on_report_diagnostic (const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind) { pretty_printer *pp = get_printer (); (*diagnostic_text_starter (&m_context)) (*this, &diagnostic); pp_output_formatted_text (pp, m_context.get_urlifier ()); if (m_context.m_show_cwe) print_any_cwe (diagnostic); if (m_context.m_show_rules) print_any_rules (diagnostic); if (m_context.m_show_option_requested) print_option_information (diagnostic, orig_diag_kind); /* If we're showing nested diagnostics, then print the location on a new line, indented. */ if (m_show_nesting && m_show_locations_in_nesting) { const int nesting_level = get_context ().get_diagnostic_nesting_level (); if (nesting_level > 0) { location_t loc = diagnostic_location (&diagnostic); pp_set_prefix (pp, nullptr); char *indent_prefix = build_indent_prefix (false); /* Only print changes of location. */ if (loc != get_context ().m_last_location && loc > BUILTINS_LOCATION) { const expanded_location s = diagnostic_expand_location (&diagnostic); label_text location_text = get_location_text (s); pp_newline (pp); pp_printf (pp, "%s%s", indent_prefix, location_text.get ()); } pp_set_prefix (pp, indent_prefix); } } (*diagnostic_text_finalizer (&m_context)) (*this, &diagnostic, orig_diag_kind); if (m_show_nesting && m_show_locations_in_nesting) get_context ().m_last_location = diagnostic_location (&diagnostic); } void diagnostic_text_output_format::on_report_verbatim (text_info &text) { pp_format_verbatim (get_printer (), &text); pp_newline_and_flush (get_printer ()); } void diagnostic_text_output_format::on_diagram (const diagnostic_diagram &diagram) { pretty_printer *const pp = get_printer (); char *saved_prefix = pp_take_prefix (pp); pp_set_prefix (pp, NULL); /* Use a newline before and after and a two-space indent to make the diagram stand out a little from the wall of text. */ pp_newline (pp); diagram.get_canvas ().print_to_pp (pp, " "); pp_newline (pp); pp_set_prefix (pp, saved_prefix); pp_flush (pp); } void diagnostic_text_output_format:: after_diagnostic (const diagnostic_info &diagnostic) { if (const diagnostic_path *path = diagnostic.richloc->get_path ()) print_path (*path); } /* Return a malloc'd string describing a location and the severity of the diagnostic, e.g. "foo.c:42:10: error: ". If m_show_nesting, then the above will be preceded by indentation to show the level, and a bullet point. The caller is responsible for freeing the memory. */ char * diagnostic_text_output_format:: build_prefix (const diagnostic_info &diagnostic) const { gcc_assert (diagnostic.kind < DK_LAST_DIAGNOSTIC_KIND); const char *text = _(get_diagnostic_kind_text (diagnostic.kind)); const char *text_cs = "", *text_ce = ""; pretty_printer *pp = get_printer (); if (const char *color_name = diagnostic_get_color_for_kind (diagnostic.kind)) { text_cs = colorize_start (pp_show_color (pp), color_name); text_ce = colorize_stop (pp_show_color (pp)); } const int nesting_level = get_context ().get_diagnostic_nesting_level (); if (m_show_nesting && nesting_level > 0) { char *indent_prefix = build_indent_prefix (true); /* Reduce verbosity of nested diagnostics by not printing "note: " all the time. */ if (diagnostic.kind == DK_NOTE) return indent_prefix; char *result = build_message_string ("%s%s%s%s", indent_prefix, text_cs, text, text_ce); free (indent_prefix); return result; } else { const expanded_location s = diagnostic_expand_location (&diagnostic); label_text location_text = get_location_text (s); return build_message_string ("%s %s%s%s", location_text.get (), text_cs, text, text_ce); } } /* Same as build_prefix, but only the source FILE is given. */ char * diagnostic_text_output_format::file_name_as_prefix (const char *f) const { pretty_printer *const pp = get_printer (); const char *locus_cs = colorize_start (pp_show_color (pp), "locus"); const char *locus_ce = colorize_stop (pp_show_color (pp)); return build_message_string ("%s%s:%s ", locus_cs, f, locus_ce); } /* Get the unicode code point for bullet points when showing nested diagnostics. */ static unsigned get_bullet_point_unichar (bool unicode) { if (unicode) return 0x2022; /* U+2022: Bullet */ else return '*'; } /* Return true if DC's theme supports unicode characters. */ static bool use_unicode_p (const diagnostic_context &dc) { if (text_art::theme *theme = dc.get_diagram_theme ()) return theme->unicode_p (); else return false; } /* Get the unicode code point for bullet points when showing nested diagnostics. */ static unsigned get_bullet_point_unichar (diagnostic_context &dc) { return get_bullet_point_unichar (use_unicode_p (dc)); } /* Return a malloc'd string for use as a prefix to show indentation. If m_show_nesting is false, or we're at the top-level, then the result will be the empty string. If m_show_nesting, then the result will contain indentation to show the nesting level, then either a bullet point (if WITH_BULLET is true), or a space. The caller is responsible for freeing the memory. */ char * diagnostic_text_output_format::build_indent_prefix (bool with_bullet) const { if (!m_show_nesting) return xstrdup (""); const int nesting_level = get_context ().get_diagnostic_nesting_level (); if (nesting_level == 0) return xstrdup (""); pretty_printer pp; for (int i = 0; i < nesting_level; i++) pp_string (&pp, " "); if (with_bullet) pp_unicode_character (&pp, get_bullet_point_unichar (get_context ())); else pp_space (&pp); pp_space (&pp); if (m_show_nesting_levels) pp_printf (&pp, "(level %i):", nesting_level); return xstrdup (pp_formatted_text (&pp)); } /* Add a purely textual note with text GMSGID and with LOCATION. */ void diagnostic_text_output_format::append_note (location_t location, const char * gmsgid, ...) { diagnostic_context *context = &get_context (); diagnostic_info diagnostic; va_list ap; rich_location richloc (line_table, location); va_start (ap, gmsgid); diagnostic_set_info (&diagnostic, gmsgid, &ap, &richloc, DK_NOTE); if (context->m_inhibit_notes_p) { va_end (ap); return; } pretty_printer *pp = get_printer (); char *saved_prefix = pp_take_prefix (pp); pp_set_prefix (pp, build_prefix (diagnostic)); pp_format (pp, &diagnostic.message); pp_output_formatted_text (pp); pp_destroy_prefix (pp); pp_set_prefix (pp, saved_prefix); pp_newline (pp); diagnostic_show_locus (context, get_source_printing_options (), &richloc, DK_NOTE, pp); va_end (ap); } bool diagnostic_text_output_format::follows_reference_printer_p () const { return m_follows_reference_printer; } void diagnostic_text_output_format:: update_printer () { pretty_printer *copy_from_pp = (m_follows_reference_printer ? get_context ().get_reference_printer () : m_printer.get ()); const bool show_color = pp_show_color (copy_from_pp); const diagnostic_url_format url_format = copy_from_pp->get_url_format (); m_printer = get_context ().clone_printer (); pp_show_color (m_printer.get ()) = show_color; m_printer->set_url_format (url_format); // ...etc m_source_printing = get_context ().m_source_printing; } /* If DIAGNOSTIC has a CWE identifier, print it. For example, if the diagnostic metadata associates it with CWE-119, " [CWE-119]" will be printed, suitably colorized, and with a URL of a description of the security issue. */ void diagnostic_text_output_format::print_any_cwe (const diagnostic_info &diagnostic) { if (diagnostic.metadata == NULL) return; int cwe = diagnostic.metadata->get_cwe (); if (cwe) { pretty_printer * const pp = get_printer (); char *saved_prefix = pp_take_prefix (pp); pp_string (pp, " ["); const char *kind_color = diagnostic_get_color_for_kind (diagnostic.kind); pp_string (pp, colorize_start (pp_show_color (pp), kind_color)); if (pp->supports_urls_p ()) { char *cwe_url = get_cwe_url (cwe); pp_begin_url (pp, cwe_url); free (cwe_url); } pp_printf (pp, "CWE-%i", cwe); pp_set_prefix (pp, saved_prefix); if (pp->supports_urls_p ()) pp_end_url (pp); pp_string (pp, colorize_stop (pp_show_color (pp))); pp_character (pp, ']'); } } /* If DIAGNOSTIC has any rules associated with it, print them. For example, if the diagnostic metadata associates it with a rule named "STR34-C", then " [STR34-C]" will be printed, suitably colorized, with any URL provided by the rule. */ void diagnostic_text_output_format:: print_any_rules (const diagnostic_info &diagnostic) { if (diagnostic.metadata == NULL) return; for (unsigned idx = 0; idx < diagnostic.metadata->get_num_rules (); idx++) { const diagnostic_metadata::rule &rule = diagnostic.metadata->get_rule (idx); if (char *desc = rule.make_description ()) { pretty_printer * const pp = get_printer (); char *saved_prefix = pp_take_prefix (pp); pp_string (pp, " ["); const char *kind_color = diagnostic_get_color_for_kind (diagnostic.kind); pp_string (pp, colorize_start (pp_show_color (pp), kind_color)); char *url = NULL; if (pp->supports_urls_p ()) { url = rule.make_url (); if (url) pp_begin_url (pp, url); } pp_string (pp, desc); pp_set_prefix (pp, saved_prefix); if (pp->supports_urls_p ()) if (url) pp_end_url (pp); free (url); pp_string (pp, colorize_stop (pp_show_color (pp))); pp_character (pp, ']'); free (desc); } } } /* Print any metadata about the option used to control DIAGNOSTIC to CONTEXT's printer, e.g. " [-Werror=uninitialized]". Subroutine of diagnostic_context::report_diagnostic. */ void diagnostic_text_output_format:: print_option_information (const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind) { if (char *option_text = m_context.make_option_name (diagnostic.option_id, orig_diag_kind, diagnostic.kind)) { char *option_url = nullptr; pretty_printer * const pp = get_printer (); if (pp->supports_urls_p ()) option_url = m_context.make_option_url (diagnostic.option_id); pp_string (pp, " ["); const char *kind_color = diagnostic_get_color_for_kind (diagnostic.kind); pp_string (pp, colorize_start (pp_show_color (pp), kind_color)); if (option_url) pp_begin_url (pp, option_url); pp_string (pp, option_text); if (option_url) { pp_end_url (pp); free (option_url); } pp_string (pp, colorize_stop (pp_show_color (pp))); pp_character (pp, ']'); free (option_text); } } /* Only dump the "In file included from..." stack once for each file. */ bool diagnostic_text_output_format::includes_seen_p (const line_map_ordinary *map) { /* No include path for main. */ if (MAIN_FILE_P (map)) return true; /* Always identify C++ modules, at least for now. */ auto probe = map; if (linemap_check_ordinary (map)->reason == LC_RENAME) /* The module source file shows up as LC_RENAME inside LC_MODULE. */ probe = linemap_included_from_linemap (line_table, map); if (MAP_MODULE_P (probe)) return false; if (!m_includes_seen) m_includes_seen = new hash_set; /* Hash the location of the #include directive to better handle files that are included multiple times with different macros defined. */ return m_includes_seen->add (linemap_included_from (map)); } label_text diagnostic_text_output_format:: get_location_text (const expanded_location &s) const { diagnostic_column_policy column_policy (get_context ()); return column_policy.get_location_text (s, show_column_p (), pp_show_color (get_printer ())); } /* Helpers for writing lang-specific starters/finalizers for text output. */ /* Return a formatted line and column ':%line:%column'. Elided if line == 0 or col < 0. (A column of 0 may be valid due to the -fdiagnostics-column-origin option.) The result is a statically allocated buffer. */ const char * maybe_line_and_column (int line, int col) { static char result[32]; if (line) { size_t l = snprintf (result, sizeof (result), col >= 0 ? ":%d:%d" : ":%d", line, col); gcc_checking_assert (l < sizeof (result)); } else result[0] = 0; return result; } void diagnostic_text_output_format::report_current_module (location_t where) { pretty_printer *pp = get_printer (); const line_map_ordinary *map = NULL; if (pp_needs_newline (pp)) { pp_newline (pp); pp_needs_newline (pp) = false; } if (where <= BUILTINS_LOCATION) return; linemap_resolve_location (line_table, where, LRK_MACRO_DEFINITION_LOCATION, &map); if (map && m_last_module != map) { m_last_module = map; if (!includes_seen_p (map)) { bool first = true, need_inc = true, was_module = MAP_MODULE_P (map); expanded_location s = {}; do { where = linemap_included_from (map); map = linemap_included_from_linemap (line_table, map); bool is_module = MAP_MODULE_P (map); s.file = LINEMAP_FILE (map); s.line = SOURCE_LINE (map, where); int col = -1; if (first && show_column_p ()) { s.column = SOURCE_COLUMN (map, where); col = get_column_policy ().converted_column (s); } const char *line_col = maybe_line_and_column (s.line, col); static const char *const msgs[] = { NULL, N_(" from"), N_("In file included from"), /* 2 */ N_(" included from"), N_("In module"), /* 4 */ N_("of module"), N_("In module imported at"), /* 6 */ N_("imported at"), }; unsigned index = (was_module ? 6 : is_module ? 4 : need_inc ? 2 : 0) + !first; pp_verbatim (pp, "%s%s %r%s%s%R", first ? "" : was_module ? ", " : ",\n", _(msgs[index]), "locus", s.file, line_col); first = false, need_inc = was_module, was_module = is_module; } while (!includes_seen_p (map)); pp_verbatim (pp, ":"); pp_newline (pp); } } } void default_diagnostic_text_starter (diagnostic_text_output_format &text_output, const diagnostic_info *diagnostic) { text_output.report_current_module (diagnostic_location (diagnostic)); pretty_printer *const pp = text_output.get_printer (); pp_set_prefix (pp, text_output.build_prefix (*diagnostic)); } void default_diagnostic_text_finalizer (diagnostic_text_output_format &text_output, const diagnostic_info *diagnostic, diagnostic_t) { pretty_printer *const pp = text_output.get_printer (); char *saved_prefix = pp_take_prefix (pp); pp_set_prefix (pp, NULL); pp_newline (pp); diagnostic_show_locus (&text_output.get_context (), text_output.get_source_printing_options (), diagnostic->richloc, diagnostic->kind, pp); pp_set_prefix (pp, saved_prefix); pp_flush (pp); } #if __GNUC__ >= 10 # pragma GCC diagnostic pop #endif