diff --git a/gcc/Makefile.in b/gcc/Makefile.in index 1be4ea02992..798d4302fa7 100644 --- a/gcc/Makefile.in +++ b/gcc/Makefile.in @@ -1849,7 +1849,8 @@ OBJS-libcommon = diagnostic-spec.o diagnostic.o diagnostic-color.o \ # Objects in libcommon-target.a, used by drivers and by the core # compiler and containing target-dependent code. OBJS-libcommon-target = $(common_out_object_file) prefix.o \ - opts.o opts-common.o options.o vec.o hooks.o common/common-targhooks.o \ + opts.o opts-common.o opts-diagnostic.o options.o \ + vec.o hooks.o common/common-targhooks.o \ hash-table.o file-find.o spellcheck.o selftest.o opt-suggestions.o \ options-urls.o diff --git a/gcc/ada/gcc-interface/misc.cc b/gcc/ada/gcc-interface/misc.cc index 8c921db7dcd..56742e75dde 100644 --- a/gcc/ada/gcc-interface/misc.cc +++ b/gcc/ada/gcc-interface/misc.cc @@ -308,14 +308,14 @@ internal_error_function (diagnostic_context *context, const char *msgid, emergency_dump_function (); /* Reset the pretty-printer. */ - pp_clear_output_area (context->m_printer); + pp_clear_output_area (context->get_reference_printer ()); /* Format the message into the pretty-printer. */ text_info tinfo (msgid, ap, errno); - pp_format_verbatim (context->m_printer, &tinfo); + pp_format_verbatim (context->get_reference_printer (), &tinfo); /* Extract a (writable) pointer to the formatted text. */ - buffer = xstrdup (pp_formatted_text (context->m_printer)); + buffer = xstrdup (pp_formatted_text (context->get_reference_printer ())); /* Go up to the first newline. */ for (p = buffer; *p; p++) diff --git a/gcc/analyzer/analyzer-language.cc b/gcc/analyzer/analyzer-language.cc index b4eea6b6581..e66ab38c9e7 100644 --- a/gcc/analyzer/analyzer-language.cc +++ b/gcc/analyzer/analyzer-language.cc @@ -120,7 +120,7 @@ on_finish_translation_unit (const translation_unit &tu) log_user the_logger (NULL); if (logfile) the_logger.set_logger (new logger (logfile, 0, 0, - *global_dc->m_printer)); + *global_dc->get_reference_printer ())); stash_named_constants (the_logger.get_logger (), tu); run_callbacks (the_logger.get_logger (), tu); diff --git a/gcc/analyzer/engine.cc b/gcc/analyzer/engine.cc index 3b23990ef80..ca81285175b 100644 --- a/gcc/analyzer/engine.cc +++ b/gcc/analyzer/engine.cc @@ -6327,7 +6327,7 @@ run_checkers () get_or_create_any_logfile (); if (dump_fout) the_logger.set_logger (new logger (dump_fout, 0, 0, - *global_dc->m_printer)); + *global_dc->get_reference_printer ())); LOG_SCOPE (the_logger.get_logger ()); impl_run_checkers (the_logger.get_logger ()); diff --git a/gcc/analyzer/program-point.cc b/gcc/analyzer/program-point.cc index 673933307a8..313df9af1d3 100644 --- a/gcc/analyzer/program-point.cc +++ b/gcc/analyzer/program-point.cc @@ -280,7 +280,7 @@ function_point::print_source_line (pretty_printer *pp) const diagnostic_source_print_policy source_policy (tmp_dc); gcc_assert (pp); source_policy.print (*pp, richloc, DK_ERROR, nullptr); - pp_string (pp, pp_formatted_text (tmp_dc.m_printer)); + pp_string (pp, pp_formatted_text (tmp_dc.get_reference_printer ())); } /* class program_point. */ diff --git a/gcc/c-family/c-format.cc b/gcc/c-family/c-format.cc index 035080e90aa..119859b66d9 100644 --- a/gcc/c-family/c-format.cc +++ b/gcc/c-family/c-format.cc @@ -5579,14 +5579,14 @@ test_type_mismatch_range_labels () richloc.add_range (param, SHOW_RANGE_WITHOUT_CARET, ¶m_label); test_diagnostic_context dc; - diagnostic_show_locus (&dc, &richloc, DK_ERROR, dc.m_printer); + diagnostic_show_locus (&dc, &richloc, DK_ERROR, dc.get_reference_printer ()); if (c_dialect_cxx ()) /* "char*", without a space. */ ASSERT_STREQ (" printf (\"msg: %i\\n\", msg);\n" " ~^ ~~~\n" " | |\n" " char* int\n", - pp_formatted_text (dc.m_printer)); + pp_formatted_text (dc.get_reference_printer ())); else /* "char *", with a space. */ ASSERT_STREQ (" printf (\"msg: %i\\n\", msg);\n" @@ -5594,7 +5594,7 @@ test_type_mismatch_range_labels () " | |\n" " | int\n" " char *\n", - pp_formatted_text (dc.m_printer)); + pp_formatted_text (dc.get_reference_printer ())); } /* Run all of the selftests within this file. */ diff --git a/gcc/c/c-objc-common.cc b/gcc/c/c-objc-common.cc index dc91373f5b9..dcabab0581c 100644 --- a/gcc/c/c-objc-common.cc +++ b/gcc/c/c-objc-common.cc @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see #include "stringpool.h" #include "attribs.h" #include "dwarf2.h" +#include "make-unique.h" static bool c_tree_printer (pretty_printer *, text_info *, const char *, int, bool, bool, bool, bool *, pp_token_list &); @@ -412,16 +413,9 @@ has_c_linkage (const_tree decl ATTRIBUTE_UNUSED) void c_initialize_diagnostics (diagnostic_context *context) { - pretty_printer *base = context->m_printer; - c_pretty_printer *pp = XNEW (c_pretty_printer); - context->m_printer = new (pp) c_pretty_printer (); - - /* It is safe to free this object because it was previously XNEW()'d. */ - base->~pretty_printer (); - XDELETE (base); - + context->set_pretty_printer (::make_unique ()); c_common_diagnostics_set_defaults (context); - diagnostic_format_decoder (context) = &c_tree_printer; + context->set_format_decoder (&c_tree_printer); } int diff --git a/gcc/common.opt b/gcc/common.opt index 70a22cdc71a..0b1f1ec26e1 100644 --- a/gcc/common.opt +++ b/gcc/common.opt @@ -1440,6 +1440,14 @@ fdiagnostics-format= Common Joined RejectNegative Enum(diagnostics_output_format) -fdiagnostics-format=[text|sarif-stderr|sarif-file|json|json-stderr|json-file] Select output format. +fdiagnostics-add-output= +Common Joined RejectNegative +Add output format. + +fdiagnostics-set-output= +Common Joined RejectNegative +Set output format. + fdiagnostics-escape-format= Common Joined RejectNegative Enum(diagnostics_escape_format) -fdiagnostics-escape-format=[unicode|bytes] Select how to escape non-printable-ASCII bytes in the source for diagnostics that suggest it. @@ -1487,10 +1495,6 @@ Enum(diagnostics_output_format) String(sarif-stderr) Value(DIAGNOSTICS_OUTPUT_FO EnumValue Enum(diagnostics_output_format) String(sarif-file) Value(DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE) -EnumValue -Enum(diagnostics_output_format) String(sarif-file-2.2-prerelease) Value(DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE) -; undocumented - fdiagnostics-parseable-fixits Common Var(flag_diagnostics_parseable_fixits) Print fix-it hints in machine-readable form. diff --git a/gcc/common.opt.urls b/gcc/common.opt.urls index e31736cd994..78e0dc209d1 100644 --- a/gcc/common.opt.urls +++ b/gcc/common.opt.urls @@ -564,6 +564,12 @@ UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-colu fdiagnostics-format= UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-format) +fdiagnostics-add-output= +UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-add-output) + +fdiagnostics-set-output= +UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-set-output) + fdiagnostics-escape-format= UrlSuffix(gcc/Diagnostic-Message-Formatting-Options.html#index-fdiagnostics-escape-format) diff --git a/gcc/cp/error.cc b/gcc/cp/error.cc index 4a60fac9694..7ef79b90868 100644 --- a/gcc/cp/error.cc +++ b/gcc/cp/error.cc @@ -275,20 +275,15 @@ cp_seen_error () void cxx_initialize_diagnostics (diagnostic_context *context) { - pretty_printer *base = context->m_printer; - cxx_pretty_printer *pp = XNEW (cxx_pretty_printer); - context->m_printer = new (pp) cxx_pretty_printer (); - - /* It is safe to free this object because it was previously XNEW()'d. */ - base->~pretty_printer (); - XDELETE (base); + cxx_pretty_printer *pp = new cxx_pretty_printer (); + pp_format_postprocessor (pp) = new cxx_format_postprocessor (); + context->set_pretty_printer (std::unique_ptr (pp)); c_common_diagnostics_set_defaults (context); diagnostic_text_starter (context) = cp_diagnostic_text_starter; /* diagnostic_finalizer is already c_diagnostic_text_finalizer. */ - diagnostic_format_decoder (context) = cp_printer; + context->set_format_decoder (cp_printer); context->m_adjust_diagnostic_info = cp_adjust_diagnostic_info; - pp_format_postprocessor (pp) = new cxx_format_postprocessor (); } /* Dump an '@module' name suffix for DECL, if any. */ diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc index f72e0154dd6..9323023ff7f 100644 --- a/gcc/cp/module.cc +++ b/gcc/cp/module.cc @@ -4698,7 +4698,7 @@ noisy_p () if (quiet_flag) return false; - pp_needs_newline (global_dc->m_printer) = true; + pp_needs_newline (global_dc->get_reference_printer ()) = true; diagnostic_set_last_function (global_dc, (diagnostic_info *) NULL); return true; diff --git a/gcc/d/d-diagnostic.cc b/gcc/d/d-diagnostic.cc index d584d2ac2b2..f8c32ae918d 100644 --- a/gcc/d/d-diagnostic.cc +++ b/gcc/d/d-diagnostic.cc @@ -208,7 +208,7 @@ d_diagnostic_report_diagnostic (const Loc &loc, int opt, const char *format, /* Write verbatim messages with no location direct to stream. */ text_info text (expand_d_format (format), &argp, errno, nullptr); - pretty_printer *const pp = global_dc->m_printer; + pretty_printer *const pp = global_dc->get_reference_printer (); pp_format_verbatim (pp, &text); pp_newline_and_flush (pp); } diff --git a/gcc/diagnostic-buffer.h b/gcc/diagnostic-buffer.h index deb933a8763..07ccebf9357 100644 --- a/gcc/diagnostic-buffer.h +++ b/gcc/diagnostic-buffer.h @@ -59,6 +59,7 @@ class diagnostic_buffer friend class diagnostic_context; diagnostic_buffer (diagnostic_context &ctxt); + ~diagnostic_buffer (); void dump (FILE *out, int indent) const; void DEBUG_FUNCTION dump () const { dump (stderr, 0); } @@ -73,10 +74,10 @@ class diagnostic_buffer void move_to (diagnostic_buffer &dest); private: - void ensure_per_format_buffer (); + void ensure_per_format_buffers (); diagnostic_context &m_ctxt; - std::unique_ptr m_per_format_buffer; + auto_vec *m_per_format_buffers; /* The number of buffered diagnostics of each kind. */ diagnostic_counters m_diagnostic_counters; diff --git a/gcc/diagnostic-format-json.cc b/gcc/diagnostic-format-json.cc index fdc55b36416..ab51fce8ad7 100644 --- a/gcc/diagnostic-format-json.cc +++ b/gcc/diagnostic-format-json.cc @@ -104,6 +104,15 @@ public: { /* No-op. */ } + void update_printer () final override + { + m_printer = m_context.clone_printer (); + pp_show_color (m_printer.get ()) = false; + } + bool follows_reference_printer_p () const final override + { + return false; + } protected: json_output_format (diagnostic_context &context, @@ -501,9 +510,6 @@ static void diagnostic_output_format_init_json (diagnostic_context &context, std::unique_ptr fmt) { - /* Suppress normal textual path output. */ - context.set_path_format (DPF_NONE); - /* Don't colorize the text. */ pp_show_color (fmt->get_printer ()) = false; context.set_show_highlight_colors (false); diff --git a/gcc/diagnostic-format-sarif.cc b/gcc/diagnostic-format-sarif.cc index 9973301cb29..5a3fd3f5dac 100644 --- a/gcc/diagnostic-format-sarif.cc +++ b/gcc/diagnostic-format-sarif.cc @@ -38,6 +38,7 @@ along with GCC; see the file COPYING3. If not see #include "diagnostic-diagram.h" #include "text-art/canvas.h" #include "diagnostic-format-sarif.h" +#include "diagnostic-format-text.h" #include "ordered-hash-map.h" #include "sbitmap.h" #include "make-unique.h" @@ -680,12 +681,18 @@ public: friend class diagnostic_sarif_format_buffer; sarif_builder (diagnostic_context &context, + pretty_printer &printer, const line_maps *line_maps, const char *main_input_filename_, bool formatted, enum sarif_version version); ~sarif_builder (); + void set_printer (pretty_printer &printer) + { + m_printer = &printer; + } + void on_report_diagnostic (const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind, diagnostic_sarif_format_buffer *buffer); @@ -1540,12 +1547,13 @@ sarif_thread_flow::add_location () /* sarif_builder's ctor. */ sarif_builder::sarif_builder (diagnostic_context &context, + pretty_printer &printer, const line_maps *line_maps, const char *main_input_filename_, bool formatted, enum sarif_version version) : m_context (context), - m_printer (context.m_printer), + m_printer (&printer), m_line_maps (line_maps), m_token_printer (*this), m_version (version), @@ -2123,11 +2131,13 @@ sarif_builder::make_location_object (sarif_location_manager &loc_mgr, diagnostic_source_print_policy source_policy (dc); dc.set_escape_format (m_escape_format); - source_policy.print (*dc.m_printer, my_rich_loc, DK_ERROR, nullptr); + diagnostic_text_output_format text_output (dc); + source_policy.print (*text_output.get_printer (), + my_rich_loc, DK_ERROR, nullptr); + const char *buf = pp_formatted_text (text_output.get_printer ()); std::unique_ptr result - = builder.make_multiformat_message_string - (pp_formatted_text (dc.m_printer)); + = builder.make_multiformat_message_string (buf); diagnostic_finish (&dc); @@ -3423,6 +3433,28 @@ public: m_buffer = buffer; } + bool follows_reference_printer_p () const final override + { + return false; + } + + void update_printer () final override + { + m_printer = m_context.clone_printer (); + + /* Don't colorize the text. */ + pp_show_color (m_printer.get ()) = false; + + /* No textual URLs. */ + m_printer->set_url_format (URL_FORMAT_NONE); + + /* Use builder's token printer. */ + get_printer ()->set_token_printer (&m_builder.get_token_printer ()); + + /* Update the builder to use the new printer. */ + m_builder.set_printer (*get_printer ()); + } + void on_begin_group () final override { /* No-op, */ @@ -3458,7 +3490,8 @@ protected: bool formatted, enum sarif_version version) : diagnostic_output_format (context), - m_builder (context, line_maps, main_input_filename_, formatted, version), + m_builder (context, *get_printer (), line_maps, main_input_filename_, + formatted, version), m_buffer (nullptr) {} @@ -3651,15 +3684,8 @@ static void diagnostic_output_format_init_sarif (diagnostic_context &context, std::unique_ptr fmt) { - /* Suppress normal textual path output. */ - context.set_path_format (DPF_NONE); + fmt->update_printer (); - /* Don't colorize the text. */ - pp_show_color (fmt->get_printer ()) = false; - context.set_show_highlight_colors (false); - - context.m_printer->set_token_printer - (&fmt->get_builder ().get_token_printer ()); context.set_output_format (std::move (fmt)); } @@ -3683,26 +3709,23 @@ diagnostic_output_format_init_sarif_stderr (diagnostic_context &context, stderr)); } -/* Populate CONTEXT in preparation for SARIF output to a file named - BASE_FILE_NAME.sarif. */ +/* Attempt to open BASE_FILE_NAME.sarif for writing. + Return a non-null diagnostic_output_file, + or return a null diagnostic_output_file and complain to CONTEXT + using LINE_MAPS. */ -void -diagnostic_output_format_init_sarif_file (diagnostic_context &context, +diagnostic_output_file +diagnostic_output_format_open_sarif_file (diagnostic_context &context, line_maps *line_maps, - const char *main_input_filename_, - bool formatted, - enum sarif_version version, const char *base_file_name) { - gcc_assert (line_maps); - if (!base_file_name) { rich_location richloc (line_maps, UNKNOWN_LOCATION); context.emit_diagnostic_with_group (DK_ERROR, richloc, nullptr, 0, "unable to determine filename for SARIF output"); - return; + return diagnostic_output_file (); } label_text filename = label_text::take (concat (base_file_name, @@ -3716,9 +3739,29 @@ diagnostic_output_format_init_sarif_file (diagnostic_context &context, (DK_ERROR, richloc, nullptr, 0, "unable to open %qs for SARIF output: %m", filename.get ()); - return; + return diagnostic_output_file (); } - diagnostic_output_file output_file (outf, true, std::move (filename)); + return diagnostic_output_file (outf, true, std::move (filename)); +} + +/* Populate CONTEXT in preparation for SARIF output to a file named + BASE_FILE_NAME.sarif. */ + +void +diagnostic_output_format_init_sarif_file (diagnostic_context &context, + line_maps *line_maps, + const char *main_input_filename_, + bool formatted, + enum sarif_version version, + const char *base_file_name) +{ + gcc_assert (line_maps); + + diagnostic_output_file output_file + = diagnostic_output_format_open_sarif_file (context, + line_maps, + base_file_name); + diagnostic_output_format_init_sarif (context, ::make_unique (context, @@ -3750,6 +3793,23 @@ diagnostic_output_format_init_sarif_stream (diagnostic_context &context, stream)); } +std::unique_ptr +make_sarif_sink (diagnostic_context &context, + const line_maps &line_maps, + const char *main_input_filename_, + enum sarif_version version, + diagnostic_output_file output_file) +{ + auto sink = ::make_unique (context, + &line_maps, + main_input_filename_, + true, + version, + std::move (output_file)); + sink->update_printer (); + return sink; +} + #if CHECKING_P namespace selftest { @@ -3822,8 +3882,9 @@ test_make_location_object (const line_table_case &case_, return; test_diagnostic_context dc; - - sarif_builder builder (dc, line_table, "MAIN_INPUT_FILENAME", true, version); + pretty_printer pp; + sarif_builder builder (dc, pp, line_table, "MAIN_INPUT_FILENAME", + true, version); /* These "columns" are byte offsets, whereas later on the columns in the generated SARIF use sarif_builder::get_sarif_column and diff --git a/gcc/diagnostic-format-sarif.h b/gcc/diagnostic-format-sarif.h index 5f8751aa350..f149d2983a4 100644 --- a/gcc/diagnostic-format-sarif.h +++ b/gcc/diagnostic-format-sarif.h @@ -35,6 +35,11 @@ enum class sarif_version num_versions }; +extern diagnostic_output_file +diagnostic_output_format_open_sarif_file (diagnostic_context &context, + line_maps *line_maps, + const char *base_file_name); + extern void diagnostic_output_format_init_sarif_stderr (diagnostic_context &context, const line_maps *line_maps, @@ -55,6 +60,12 @@ diagnostic_output_format_init_sarif_stream (diagnostic_context &context, bool formatted, enum sarif_version version, FILE *stream); +extern std::unique_ptr +make_sarif_sink (diagnostic_context &context, + const line_maps &line_maps, + const char *main_input_filename_, + enum sarif_version version, + diagnostic_output_file output_file); /* Concrete subclass of json::object for SARIF property bags (SARIF v2.1.0 section 3.8). */ diff --git a/gcc/diagnostic-format-text.cc b/gcc/diagnostic-format-text.cc index c23ac6c505d..fd47ca4a15b 100644 --- a/gcc/diagnostic-format-text.cc +++ b/gcc/diagnostic-format-text.cc @@ -160,6 +160,9 @@ 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) @@ -222,6 +225,13 @@ on_report_diagnostic (const diagnostic_info &diagnostic, orig_diag_kind); } +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) { @@ -315,6 +325,30 @@ diagnostic_text_output_format::append_note (location_t location, 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 +} + /* If DIAGNOSTIC has a CWE identifier, print it. For example, if the diagnostic metadata associates it with CWE-119, diff --git a/gcc/diagnostic-format-text.h b/gcc/diagnostic-format-text.h index b43501eaa4a..78d1b2da0a0 100644 --- a/gcc/diagnostic-format-text.h +++ b/gcc/diagnostic-format-text.h @@ -32,12 +32,14 @@ along with GCC; see the file COPYING3. If not see class diagnostic_text_output_format : public diagnostic_output_format { public: - diagnostic_text_output_format (diagnostic_context &context) + diagnostic_text_output_format (diagnostic_context &context, + bool follows_reference_printer = false) : diagnostic_output_format (context), m_saved_output_buffer (nullptr), m_column_policy (context), m_last_module (nullptr), - m_includes_seen (nullptr) + m_includes_seen (nullptr), + m_follows_reference_printer (follows_reference_printer) {} ~diagnostic_text_output_format (); @@ -51,12 +53,16 @@ public: void on_end_group () override {} void on_report_diagnostic (const diagnostic_info &, diagnostic_t orig_diag_kind) override; + void on_report_verbatim (text_info &) final override; void on_diagram (const diagnostic_diagram &diagram) override; - void after_diagnostic (const diagnostic_info &) final override; + void after_diagnostic (const diagnostic_info &) override; bool machine_readable_stderr_p () const final override { return false; } + bool follows_reference_printer_p () const final override; + + void update_printer () override; /* Helpers for writing lang-specific starters/finalizers for text output. */ char *build_prefix (const diagnostic_info &) const; @@ -77,7 +83,7 @@ public: } diagnostic_location_print_policy get_location_print_policy () const; -private: +protected: void print_any_cwe (const diagnostic_info &diagnostic); void print_any_rules (const diagnostic_info &diagnostic); void print_option_information (const diagnostic_info &diagnostic, @@ -98,6 +104,14 @@ private: /* Include files that report_current_module has already listed the include path for. */ hash_set *m_includes_seen; + + /* If true, this is the initial default text output format created + when the diagnostic_context was created, and, in particular, before + initializations of color and m_url_format. Hence this should follow + the dc's reference printer for these. + If false, this text output was created after the dc was created, and + thus tracks its own values for color and m_url_format. */ + bool m_follows_reference_printer; }; #endif /* ! GCC_DIAGNOSTIC_FORMAT_TEXT_H */ diff --git a/gcc/diagnostic-format.h b/gcc/diagnostic-format.h index a38d177ce8d..11238cd9528 100644 --- a/gcc/diagnostic-format.h +++ b/gcc/diagnostic-format.h @@ -55,12 +55,21 @@ public: virtual void on_report_diagnostic (const diagnostic_info &, diagnostic_t orig_diag_kind) = 0; + virtual void on_report_verbatim (text_info &); + virtual void on_diagram (const diagnostic_diagram &diagram) = 0; virtual void after_diagnostic (const diagnostic_info &) = 0; virtual bool machine_readable_stderr_p () const = 0; + virtual bool follows_reference_printer_p () const = 0; + + /* Vfunc called when the diagnostic_context changes its + reference printer (either to a new subclass of pretty_printer + or when color/url options change). + Subclasses should update their m_printer accordingly. */ + virtual void update_printer () = 0; diagnostic_context &get_context () const { return m_context; } - pretty_printer *get_printer () const { return m_context.m_printer; } + pretty_printer *get_printer () const { return m_printer.get (); } text_art::theme *get_diagram_theme () const { @@ -71,10 +80,13 @@ public: protected: diagnostic_output_format (diagnostic_context &context) - : m_context (context) + : m_context (context), + m_printer (context.clone_printer ()) {} +protected: diagnostic_context &m_context; + std::unique_ptr m_printer; }; extern void diff --git a/gcc/diagnostic-global-context.cc b/gcc/diagnostic-global-context.cc index e82588e5404..bd299d08429 100644 --- a/gcc/diagnostic-global-context.cc +++ b/gcc/diagnostic-global-context.cc @@ -37,7 +37,8 @@ diagnostic_context *global_dc = &global_diagnostic_context; /* Standard error reporting routines in increasing order of severity. */ /* Text to be emitted verbatim to the error message stream; this - produces no prefix and disables line-wrapping. Use rarely. */ + produces no prefix and disables line-wrapping. Use rarely. + It is ignored for machine-readable output formats. */ void verbatim (const char *gmsgid, ...) { @@ -45,8 +46,7 @@ verbatim (const char *gmsgid, ...) va_start (ap, gmsgid); text_info text (_(gmsgid), &ap, errno); - pp_format_verbatim (global_dc->m_printer, &text); - pp_newline_and_flush (global_dc->m_printer); + global_dc->report_verbatim (text); va_end (ap); } @@ -551,10 +551,8 @@ fnotice (FILE *file, const char *cmsgid, ...) emitting free-form text on stderr will lead to corrupt output. Skip the message for such cases. */ if (file == stderr && global_dc) - if (const diagnostic_output_format *output_format - = global_dc->get_output_format ()) - if (output_format->machine_readable_stderr_p ()) - return; + if (!global_dc->supports_fnotice_on_stderr_p ()) + return; va_list ap; diff --git a/gcc/diagnostic-output-file.h b/gcc/diagnostic-output-file.h index f0ae5e1915e..6e7b41737cd 100644 --- a/gcc/diagnostic-output-file.h +++ b/gcc/diagnostic-output-file.h @@ -27,6 +27,12 @@ along with GCC; see the file COPYING3. If not see class diagnostic_output_file { public: + diagnostic_output_file () + : m_outf (nullptr), + m_owned (false), + m_filename () + { + } diagnostic_output_file (FILE *outf, bool owned, label_text filename) : m_outf (outf), m_owned (owned), @@ -60,7 +66,26 @@ public: diagnostic_output_file & operator= (const diagnostic_output_file &other) = delete; diagnostic_output_file & - operator= (diagnostic_output_file &&other) = delete; + operator= (diagnostic_output_file &&other) + { + if (m_owned) + { + gcc_assert (m_outf); + fclose (m_outf); + } + + m_outf = other.m_outf; + other.m_outf = nullptr; + + m_owned = other.m_owned; + other.m_owned = false; + + m_filename = std::move (other.m_filename); + + if (m_owned) + gcc_assert (m_outf); + return *this; + } operator bool () const { return m_outf != nullptr; } FILE *get_open_file () const { return m_outf; } diff --git a/gcc/diagnostic-path.cc b/gcc/diagnostic-path.cc index 8a6d516ce41..2b1a093f3f2 100644 --- a/gcc/diagnostic-path.cc +++ b/gcc/diagnostic-path.cc @@ -1297,7 +1297,7 @@ test_interprocedural_path_1 (pretty_printer *event_pp) { test_diagnostic_context dc; - diagnostic_text_output_format text_output (dc); + diagnostic_text_output_format text_output (dc, false); path_print_policy policy (text_output); path_summary summary (policy, path, false); ASSERT_EQ (summary.get_num_ranges (), 9); diff --git a/gcc/diagnostic-show-locus.cc b/gcc/diagnostic-show-locus.cc index 79499dc549d..a2a4b047ff9 100644 --- a/gcc/diagnostic-show-locus.cc +++ b/gcc/diagnostic-show-locus.cc @@ -3582,7 +3582,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_) linemap_position_for_column (line_table, emoji_col)); layout test_layout (policy, richloc, nullptr); - layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR); + layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); lp.print (policy); ASSERT_STREQ (" | 1 \n" " | 1 \n" @@ -3591,7 +3591,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_) "that occupies 8 bytes and 4 display columns, starting at " "column #102.\n" " | ^\n", - pp_formatted_text (dc.m_printer)); + pp_formatted_text (dc.get_reference_printer ())); } /* Similar to the previous example, but now the offset called for would split @@ -3609,7 +3609,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_) linemap_position_for_column (line_table, emoji_col + 2)); layout test_layout (dc, richloc, nullptr); - layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR); + layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); lp.print (policy); ASSERT_STREQ (" | 1 1 \n" " | 1 2 \n" @@ -3618,7 +3618,7 @@ test_layout_x_offset_display_utf8 (const line_table_case &case_) "that occupies 8 bytes and 4 display columns, starting at " "column #102.\n" " | ^\n", - pp_formatted_text (dc.m_printer)); + pp_formatted_text (dc.get_reference_printer ())); } } @@ -3690,9 +3690,9 @@ test_layout_x_offset_display_tab (const line_table_case &case_) dc.m_tabstop = tabstop; diagnostic_source_print_policy policy (dc); layout test_layout (policy, richloc, nullptr); - layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR); + layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); lp.print (policy); - const char *out = pp_formatted_text (dc.m_printer); + const char *out = pp_formatted_text (dc.get_reference_printer ()); ASSERT_EQ (NULL, strchr (out, '\t')); const char *left_quote = strchr (out, '`'); const char *right_quote = strchr (out, '\''); @@ -3715,7 +3715,7 @@ test_layout_x_offset_display_tab (const line_table_case &case_) dc.m_source_printing.show_line_numbers_p = true; diagnostic_source_print_policy policy (dc); layout test_layout (policy, richloc, nullptr); - layout_printer lp (*dc.m_printer, test_layout, richloc, DK_ERROR); + layout_printer lp (*dc.get_reference_printer (), test_layout, richloc, DK_ERROR); lp.print (policy); /* We have arranged things so that two columns will be printed before @@ -3731,7 +3731,7 @@ test_layout_x_offset_display_tab (const line_table_case &case_) "display columns, starting at column #103.\n" " | ^\n"; const char *expected_output = (extra_width[tabstop] ? output1 : output2); - ASSERT_STREQ (expected_output, pp_formatted_text (dc.m_printer)); + ASSERT_STREQ (expected_output, pp_formatted_text (dc.get_reference_printer ())); } } @@ -3887,8 +3887,8 @@ test_one_liner_fixit_remove () /* Test of adding a prefix. */ { test_diagnostic_context dc; - pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; - pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:")); + pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:")); ASSERT_STREQ ("TEST PREFIX: foo = bar.field;\n" "TEST PREFIX: ^~~~~~\n" "TEST PREFIX: ------\n", @@ -3914,8 +3914,8 @@ test_one_liner_fixit_remove () test_diagnostic_context dc; dc.m_source_printing.show_ruler_p = true; dc.m_source_printing.max_width = 50; - pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; - pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:")); + pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:")); ASSERT_STREQ ("TEST PREFIX: 1 2 3 4 5\n" "TEST PREFIX: 12345678901234567890123456789012345678901234567890\n" "TEST PREFIX: foo = bar.field;\n" @@ -3930,8 +3930,8 @@ test_one_liner_fixit_remove () dc.m_source_printing.show_ruler_p = true; dc.m_source_printing.max_width = 50; dc.m_source_printing.show_line_numbers_p = true; - pp_prefixing_rule (dc.m_printer) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; - pp_set_prefix (dc.m_printer, xstrdup ("TEST PREFIX:")); + pp_prefixing_rule (dc.get_reference_printer ()) = DIAGNOSTICS_SHOW_PREFIX_EVERY_LINE; + pp_set_prefix (dc.get_reference_printer (), xstrdup ("TEST PREFIX:")); ASSERT_STREQ ("TEST PREFIX: | 1 2 3 4 5\n" "TEST PREFIX: | 12345678901234567890123456789012345678901234567890\n" "TEST PREFIX: 1 | foo = bar.field;\n" diff --git a/gcc/diagnostic.cc b/gcc/diagnostic.cc index 4791af9c66e..477214c15f2 100644 --- a/gcc/diagnostic.cc +++ b/gcc/diagnostic.cc @@ -50,6 +50,7 @@ along with GCC; see the file COPYING3. If not see #include "pretty-print-urlifier.h" #include "logical-location.h" #include "diagnostic-buffer.h" +#include "make-unique.h" #ifdef HAVE_TERMIOS_H # include @@ -118,7 +119,7 @@ diagnostic_set_caret_max_width (diagnostic_context *context, int value) { /* One minus to account for the leading empty space. */ value = value ? value - 1 - : (isatty (fileno (pp_buffer (context->m_printer)->m_stream)) + : (isatty (fileno (pp_buffer (context->get_reference_printer ())->m_stream)) ? get_terminal_width () - 1 : INT_MAX); if (value <= 0) @@ -223,8 +224,7 @@ diagnostic_context::initialize (int n_opts) { /* Allocate a basic pretty-printer. Clients will replace this a much more elaborated pretty-printer if they wish. */ - m_printer = XNEW (pretty_printer); - new (m_printer) pretty_printer (); + m_reference_printer = ::make_unique ().release (); m_file_cache = new file_cache (); m_diagnostic_counters.clear (); @@ -232,7 +232,7 @@ diagnostic_context::initialize (int n_opts) m_n_opts = n_opts; m_option_classifier.init (n_opts); m_source_printing.enabled = false; - diagnostic_set_caret_max_width (this, pp_line_cutoff (m_printer)); + diagnostic_set_caret_max_width (this, 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; @@ -283,7 +283,7 @@ diagnostic_context::initialize (int n_opts) m_edit_context_ptr = nullptr; m_diagnostic_groups.m_nesting_depth = 0; m_diagnostic_groups.m_emission_count = 0; - m_output_format = new diagnostic_text_output_format (*this); + m_output_sinks.safe_push (new diagnostic_text_output_format (*this, true)); m_set_locations_cb = nullptr; m_client_data_hooks = nullptr; m_diagrams.m_theme = nullptr; @@ -326,8 +326,12 @@ diagnostic_context::color_init (int value) else value = DIAGNOSTICS_COLOR_DEFAULT; } - pp_show_color (m_printer) + pp_show_color (m_reference_printer) = colorize_init ((diagnostic_color_rule_t) value); + for (auto sink : m_output_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, @@ -354,8 +358,12 @@ diagnostic_context::urls_init (int value) value = DIAGNOSTICS_URLS_DEFAULT; } - m_printer->set_url_format + m_reference_printer->set_url_format (determine_url_format ((diagnostic_url_rule_t) value)); + for (auto sink : m_output_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 @@ -375,7 +383,7 @@ diagnostic_context::finish () { /* We might be handling a fatal error. Close any active diagnostic groups, which may trigger flushing - the output format. */ + sinks. */ while (m_diagnostic_groups.m_nesting_depth > 0) end_group (); @@ -383,8 +391,8 @@ diagnostic_context::finish () /* Clean ups. */ - delete m_output_format; - m_output_format= nullptr; + while (!m_output_sinks.is_empty ()) + delete m_output_sinks.pop (); if (m_diagrams.m_theme) { @@ -397,11 +405,8 @@ diagnostic_context::finish () m_option_classifier.fini (); - /* diagnostic_context::initialize allocates this->printer using XNEW - and placement-new. */ - m_printer->~pretty_printer (); - XDELETE (m_printer); - m_printer = nullptr; + delete m_reference_printer; + m_reference_printer = nullptr; if (m_edit_context_ptr) { @@ -429,10 +434,13 @@ diagnostic_context::dump (FILE *out) const { fprintf (out, "diagnostic_context:\n"); m_diagnostic_counters.dump (out, 2); - fprintf (out, " output format:\n"); - m_output_format->dump (out, 4); - fprintf (out, " printer:\n"); - m_printer->dump (out, 4); + fprintf (out, " reference printer:\n"); + m_reference_printer->dump (out, 4); + for (unsigned i = 0; i < m_output_sinks.length (); ++i) + { + fprintf (out, " sink %i:\n", i); + m_output_sinks[i]->dump (out, 4); + } fprintf (out, " diagnostic buffer:\n"); if (m_diagnostic_buffer) m_diagnostic_buffer->dump (out, 4); @@ -457,9 +465,34 @@ void diagnostic_context:: set_output_format (std::unique_ptr output_format) { - delete m_output_format; - /* Ideally this field would be a std::unique_ptr. */ - m_output_format = output_format.release (); + while (!m_output_sinks.is_empty ()) + delete m_output_sinks.pop (); + m_output_sinks.safe_push (output_format.release ()); +} + +diagnostic_output_format & +diagnostic_context::get_output_format (size_t idx) const +{ + gcc_assert (idx < m_output_sinks.length ()); + gcc_assert (m_output_sinks[idx]); + return *m_output_sinks[idx]; +} + +void +diagnostic_context::add_sink (std::unique_ptr sink) +{ + m_output_sinks.safe_push (sink.release ()); +} + +/* Return true if there are no machine-readable formats writing to stderr. */ + +bool +diagnostic_context::supports_fnotice_on_stderr_p () const +{ + for (auto sink : m_output_sinks) + if (sink->machine_readable_stderr_p ()) + return false; + return true; } void @@ -502,6 +535,55 @@ diagnostic_context::set_urlifier (std::unique_ptr urlifier) m_urlifier = urlifier.release (); } +/* Set PP as the reference printer for this context. + Refresh all output sinks. */ + +void +diagnostic_context::set_pretty_printer (std::unique_ptr pp) +{ + delete m_reference_printer; + m_reference_printer = pp.release (); + refresh_output_sinks (); +} + +/* Give all output sinks a chance to rebuild their pretty_printer. */ + +void +diagnostic_context::refresh_output_sinks () +{ + for (auto sink : m_output_sinks) + sink->update_printer (); +} + +/* Set FORMAT_DECODER on the reference printer and on the pretty_printer + of all output sinks. */ + +void +diagnostic_context::set_format_decoder (printer_fn format_decoder) +{ + pp_format_decoder (m_reference_printer) = format_decoder; + for (auto sink : m_output_sinks) + pp_format_decoder (sink->get_printer ()) = format_decoder; +} + +void +diagnostic_context::set_show_highlight_colors (bool val) +{ + pp_show_highlight_colors (m_reference_printer) = val; + for (auto sink : m_output_sinks) + if (sink->follows_reference_printer_p ()) + pp_show_highlight_colors (sink->get_printer ()) = val; +} + +void +diagnostic_context::set_prefixing_rule (diagnostic_prefixing_rule_t rule) +{ + pp_prefixing_rule (m_reference_printer) = rule; + for (auto sink : m_output_sinks) + if (sink->follows_reference_printer_p ()) + pp_prefixing_rule (sink->get_printer ()) = rule; +} + void diagnostic_context::create_edit_context () { @@ -1234,8 +1316,6 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic) { diagnostic_t orig_diag_kind = diagnostic->kind; - gcc_assert (m_output_format); - /* 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. */ @@ -1269,7 +1349,7 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic) through. Don't do this more than once. */ if ((diagnostic->kind == DK_ICE || diagnostic->kind == DK_ICE_NOBT) && m_lock == 1) - pp_newline_and_flush (m_printer); + pp_newline_and_flush (m_reference_printer); else error_recursion (); } @@ -1341,20 +1421,38 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic) /* Is this the initial diagnostic within the stack of groups? */ if (m_diagnostic_groups.m_emission_count == 0) - m_output_format->on_begin_group (); + for (auto sink : m_output_sinks) + sink->on_begin_group (); m_diagnostic_groups.m_emission_count++; - /* Run phases 1 and 2 of formatting the message. - In particular, some format codes may have side-effects here which need to - happen before sending the diagnostic to the output format. + va_list *orig_args = diagnostic->message.m_args_ptr; + for (auto sink : m_output_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. - For example, Fortran's %C and %L formatting codes populate the - rich_location. */ - pp_format (m_printer, &diagnostic->message); + 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. - /* Call vfunc in the output format. This is responsible for - phase 3 of formatting, and for printing the result. */ - m_output_format->on_report_diagnostic (*diagnostic, orig_diag_kind); + 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->message.m_args_ptr = &copied_args; + pp_format (sink->get_printer (), &diagnostic->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) { @@ -1362,17 +1460,17 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic) break; case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v1: print_parseable_fixits (get_file_cache (), - m_printer, diagnostic->richloc, + m_reference_printer, diagnostic->richloc, DIAGNOSTICS_COLUMN_UNIT_BYTE, m_tabstop); - pp_flush (m_printer); + pp_flush (m_reference_printer); break; case EXTRA_DIAGNOSTIC_OUTPUT_fixits_v2: print_parseable_fixits (get_file_cache (), - m_printer, diagnostic->richloc, + m_reference_printer, diagnostic->richloc, DIAGNOSTICS_COLUMN_UNIT_DISPLAY, m_tabstop); - pp_flush (m_printer); + pp_flush (m_reference_printer); break; } if (m_diagnostic_buffer == nullptr @@ -1389,11 +1487,26 @@ diagnostic_context::report_diagnostic (diagnostic_info *diagnostic) m_lock--; if (!m_diagnostic_buffer) - m_output_format->after_diagnostic (*diagnostic); + for (auto sink : m_output_sinks) + sink->after_diagnostic (*diagnostic); return true; } +void +diagnostic_context::report_verbatim (text_info &text) +{ + va_list *orig_args = text.m_args_ptr; + for (auto sink : m_output_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); + } +} + /* Get the number of digits in the decimal representation of VALUE. */ int @@ -1511,8 +1624,8 @@ diagnostic_context::emit_diagram (const diagnostic_diagram &diagram) if (m_diagrams.m_theme == nullptr) return; - gcc_assert (m_output_format); - m_output_format->on_diagram (diagram); + for (auto sink : m_output_sinks) + sink->on_diagram (diagram); } /* Inform the user that an error occurred while trying to report some @@ -1524,7 +1637,7 @@ void diagnostic_context::error_recursion () { if (m_lock < 3) - pp_newline_and_flush (m_printer); + pp_newline_and_flush (m_reference_printer); fnotice (stderr, "internal compiler error: error reporting routines re-entered.\n"); @@ -1554,7 +1667,7 @@ fancy_abort (const char *file, int line, const char *function) 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->m_printer == nullptr) + if (global_dc->get_reference_printer () == nullptr) { /* Print the error message. */ fnotice (stderr, diagnostic_kind_text[DK_ICE]); @@ -1597,15 +1710,23 @@ diagnostic_context::end_group () If any diagnostics were emitted, give the context a chance to do something. */ if (m_diagnostic_groups.m_emission_count > 0) - m_output_format->on_end_group (); + for (auto sink : m_output_sinks) + sink->on_end_group (); m_diagnostic_groups.m_emission_count = 0; } } void -diagnostic_output_format::dump (FILE *, int) const +diagnostic_output_format::dump (FILE *out, int indent) const { - /* No-op for now. */ + fprintf (out, "%*sprinter:\n", indent, ""); + m_printer->dump (out, indent + 2); +} + +void +diagnostic_output_format::on_report_verbatim (text_info &) +{ + /* No-op. */ } /* Set the output format for CONTEXT to FORMAT, using BASE_FILE_NAME for @@ -1653,15 +1774,6 @@ diagnostic_output_format_init (diagnostic_context &context, sarif_version::v2_1_0, base_file_name); break; - case DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE: - diagnostic_output_format_init_sarif_file - (context, - line_table, - main_input_filename_, - json_formatting, - sarif_version::v2_2_prerelease_2024_08_08, - base_file_name); - break; } } @@ -1713,15 +1825,22 @@ diagnostic_context::set_diagnostic_buffer (diagnostic_buffer *buffer) m_diagnostic_buffer = buffer; - gcc_assert (m_output_format); if (buffer) { - buffer->ensure_per_format_buffer (); - gcc_assert (buffer->m_per_format_buffer); - m_output_format->set_buffer (buffer->m_per_format_buffer.get ()); + buffer->ensure_per_format_buffers (); + gcc_assert (buffer->m_per_format_buffers); + gcc_assert (buffer->m_per_format_buffers->length () + == m_output_sinks.length ()); + for (unsigned idx = 0; idx < m_output_sinks.length (); ++idx) + { + auto sink = m_output_sinks[idx]; + auto per_format_buffer = (*buffer->m_per_format_buffers)[idx]; + sink->set_buffer (per_format_buffer); + } } else - m_output_format->set_buffer (nullptr); + for (auto sink : m_output_sinks) + sink->set_buffer (nullptr); } /* Clear BUFFER without flushing it. */ @@ -1729,8 +1848,10 @@ diagnostic_context::set_diagnostic_buffer (diagnostic_buffer *buffer) void diagnostic_context::clear_diagnostic_buffer (diagnostic_buffer &buffer) { - if (buffer.m_per_format_buffer) - buffer.m_per_format_buffer->clear (); + if (buffer.m_per_format_buffers) + for (auto per_format_buffer : *buffer.m_per_format_buffers) + per_format_buffer->clear (); + buffer.m_diagnostic_counters.clear (); /* We need to reset last_location, otherwise we may skip caret lines @@ -1746,8 +1867,9 @@ diagnostic_context::flush_diagnostic_buffer (diagnostic_buffer &buffer) bool had_errors = (buffer.m_diagnostic_counters.m_count_for_kind[DK_ERROR] > 0 || buffer.m_diagnostic_counters.m_count_for_kind[DK_WERROR] > 0); - if (buffer.m_per_format_buffer) - buffer.m_per_format_buffer->flush (); + if (buffer.m_per_format_buffers) + for (auto per_format_buffer : *buffer.m_per_format_buffers) + per_format_buffer->flush (); buffer.m_diagnostic_counters.move_to (m_diagnostic_counters); action_after_output (had_errors ? DK_ERROR : DK_WARNING); @@ -1796,17 +1918,29 @@ diagnostic_counters::clear () /* class diagnostic_buffer. */ diagnostic_buffer::diagnostic_buffer (diagnostic_context &ctxt) -: m_ctxt (ctxt) +: m_ctxt (ctxt), + m_per_format_buffers (nullptr) { } +diagnostic_buffer::~diagnostic_buffer () +{ + if (m_per_format_buffers) + { + for (auto iter : *m_per_format_buffers) + delete iter; + delete m_per_format_buffers; + } +} + void diagnostic_buffer::dump (FILE *out, int indent) const { - fprintf (out, "%*sm_per_format_buffer:\n", indent, ""); m_diagnostic_counters.dump (out, indent + 2); - if (m_per_format_buffer) - m_per_format_buffer->dump (out, indent + 2); + fprintf (out, "%*sm_per_format_buffers:\n", indent, ""); + if (m_per_format_buffers) + for (auto per_format_buffer : *m_per_format_buffers) + per_format_buffer->dump (out, indent + 2); else fprintf (out, "%*s(none)\n", indent + 2, ""); } @@ -1814,33 +1948,67 @@ diagnostic_buffer::dump (FILE *out, int indent) const bool diagnostic_buffer::empty_p () const { - if (m_per_format_buffer) - return m_per_format_buffer->empty_p (); - else - return true; + if (m_per_format_buffers) + for (auto per_format_buffer : *m_per_format_buffers) + /* Query initial buffer. */ + return per_format_buffer->empty_p (); + return true; } void diagnostic_buffer::move_to (diagnostic_buffer &dest) { - ensure_per_format_buffer (); - dest.ensure_per_format_buffer (); - m_per_format_buffer->move_to (*dest.m_per_format_buffer); + /* Bail if there's nothing to move. */ + if (!m_per_format_buffers) + return; + m_diagnostic_counters.move_to (dest.m_diagnostic_counters); + + if (!dest.m_per_format_buffers) + { + /* Optimization for the "move to empty" case: + simply move the vec to the dest. */ + dest.m_per_format_buffers = m_per_format_buffers; + m_per_format_buffers = nullptr; + return; + } + + dest.ensure_per_format_buffers (); + gcc_assert (m_per_format_buffers); + gcc_assert (m_per_format_buffers->length () + == m_ctxt.m_output_sinks.length ()); + gcc_assert (dest.m_per_format_buffers); + gcc_assert (dest.m_per_format_buffers->length () + == m_ctxt.m_output_sinks.length ()); + for (unsigned idx = 0; idx < m_ctxt.m_output_sinks.length (); ++idx) + { + auto per_format_buffer_src = (*m_per_format_buffers)[idx]; + auto per_format_buffer_dest = (*dest.m_per_format_buffers)[idx]; + per_format_buffer_src->move_to (*per_format_buffer_dest); + } } -/* Lazily get output format to create its own kind of buffer. */ +/* Lazily get the output formats to create their own kind of buffers. + We can't change the output sinks on a context once this has been called + on any diagnostic_buffer instances for that context, since there's no + way to update all diagnostic_buffer instances for that context. */ void -diagnostic_buffer::ensure_per_format_buffer () +diagnostic_buffer::ensure_per_format_buffers () { - if (!m_per_format_buffer) + if (!m_per_format_buffers) { - gcc_assert (m_ctxt.get_output_format ()); - m_per_format_buffer - = m_ctxt.get_output_format ()->make_per_format_buffer (); + m_per_format_buffers = new auto_vec (); + for (unsigned idx = 0; idx < m_ctxt.m_output_sinks.length (); ++idx) + { + auto sink = m_ctxt.m_output_sinks[idx]; + auto per_format_buffer = sink->make_per_format_buffer (); + m_per_format_buffers->safe_push (per_format_buffer.release ()); + } } - gcc_assert (m_per_format_buffer); + gcc_assert (m_per_format_buffers); + gcc_assert (m_per_format_buffers->length () + == m_ctxt.m_output_sinks.length ()); } /* Really call the system 'abort'. This has to go right at the end of diff --git a/gcc/diagnostic.h b/gcc/diagnostic.h index 3da1ae9c801..46478c9a52b 100644 --- a/gcc/diagnostic.h +++ b/gcc/diagnostic.h @@ -84,11 +84,7 @@ enum diagnostics_output_format DIAGNOSTICS_OUTPUT_FORMAT_SARIF_STDERR, /* SARIF-based output, to a file. */ - DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE, - - /* Undocumented, for use by test suite. - SARIF-based output, to a file, using a prerelease of the 2.2 schema. */ - DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE_2_2_PRERELEASE + DIAGNOSTICS_OUTPUT_FORMAT_SARIF_FILE }; /* An enum for controlling how diagnostic_paths should be printed. */ @@ -494,6 +490,7 @@ public: friend class diagnostic_source_print_policy; friend class diagnostic_text_output_format; + friend class diagnostic_buffer; typedef void (*set_locations_callback_t) (diagnostic_context *, diagnostic_info *); @@ -501,6 +498,8 @@ public: void initialize (int n_opts); void color_init (int value); void urls_init (int value); + void set_pretty_printer (std::unique_ptr pp); + void refresh_output_sinks (); void finish (); @@ -548,6 +547,7 @@ public: ATTRIBUTE_GCC_DIAG(6,0); bool report_diagnostic (diagnostic_info *); + void report_verbatim (text_info &); diagnostic_t classify_diagnostic (diagnostic_option_id option_id, @@ -576,11 +576,6 @@ public: void emit_diagram (const diagnostic_diagram &diagram); - diagnostic_output_format *get_output_format () const - { - return m_output_format; - } - /* Various setters for use by option-handling logic. */ void set_output_format (std::unique_ptr output_format); void set_text_art_charset (enum diagnostic_text_art_charset charset); @@ -598,10 +593,7 @@ public: } void set_show_cwe (bool val) { m_show_cwe = val; } void set_show_rules (bool val) { m_show_rules = val; } - void set_show_highlight_colors (bool val) - { - pp_show_highlight_colors (m_printer) = val; - } + void set_show_highlight_colors (bool val); void set_path_format (enum diagnostic_path_format val) { m_path_format = val; @@ -614,12 +606,16 @@ public: m_escape_format = val; } + void set_format_decoder (printer_fn format_decoder); + void set_prefixing_rule (diagnostic_prefixing_rule_t rule); + /* Various accessors. */ bool warning_as_error_requested_p () const { return m_warning_as_error_requested; } bool show_path_depths_p () const { return m_show_path_depths; } + diagnostic_output_format &get_output_format (size_t idx) const; enum diagnostic_path_format get_path_format () const { return m_path_format; } enum diagnostics_escape_format get_escape_format () const { @@ -719,9 +715,19 @@ public: std::unique_ptr clone_printer () const { - return m_printer->clone (); + return m_reference_printer->clone (); } + pretty_printer *get_reference_printer () const + { + return m_reference_printer; + } + + void + add_sink (std::unique_ptr); + + bool supports_fnotice_on_stderr_p () const; + private: void error_recursion () ATTRIBUTE_NORETURN; @@ -735,13 +741,14 @@ private: /* Data members. Ideally, all of these would be private. */ -public: - /* Where most of the diagnostic formatting work is done. +private: + /* A reference instance of pretty_printer created by the client + and owned by the context. Used for cloning when creating/adding + output formats. Owned by the context; this would be a std::unique_ptr if diagnostic_context had a proper ctor. */ - pretty_printer *m_printer; + pretty_printer *m_reference_printer; -private: /* Cache of source code. Owned by the context; this would be a std::unique_ptr if diagnostic_context had a proper ctor. */ @@ -902,11 +909,12 @@ private: int m_emission_count; } m_diagnostic_groups; - /* How to output diagnostics (text vs a structured format such as JSON). - Must be non-NULL; owned by context. - This would be a std::unique_ptr if diagnostic_context had a proper - ctor. */ - diagnostic_output_format *m_output_format; + /* The various sinks to which diagnostics are to be outputted + (text vs structured formats such as SARIF). + The sinks are owned by the context; this would be a + std::vector if diagnostic_context had a + proper ctor. */ + auto_vec m_output_sinks; /* Callback to set the locations of call sites along the inlining stack corresponding to a diagnostic location. Needed to traverse @@ -982,12 +990,6 @@ diagnostic_text_finalizer (diagnostic_context *context) #define diagnostic_context_auxiliary_data(DC) (DC)->m_client_aux_data #define diagnostic_info_auxiliary_data(DI) (DI)->x_data -/* Same as pp_format_decoder. Works on 'diagnostic_context *'. */ -#define diagnostic_format_decoder(DC) pp_format_decoder ((DC)->m_printer) - -/* Same as pp_prefixing_rule. Works on 'diagnostic_context *'. */ -#define diagnostic_prefixing_rule(DC) pp_prefixing_rule ((DC)->m_printer) - /* Raise SIGABRT on any diagnostic of severity DK_ERROR or higher. */ inline void diagnostic_abort_on_error (diagnostic_context *context) @@ -1000,14 +1002,6 @@ diagnostic_abort_on_error (diagnostic_context *context) and similar functions. */ extern diagnostic_context *global_dc; -/* Returns whether the diagnostic framework has been intialized already and is - ready for use. */ -inline bool -diagnostic_ready_p () -{ - return global_dc->m_printer != nullptr; -} - /* The number of errors that have been issued so far. Ideally, these would take a diagnostic_context as an argument. */ #define errorcount global_dc->diagnostic_count (DK_ERROR) diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index ae52cc68c51..07920e07b4d 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -307,6 +307,8 @@ Objective-C and Objective-C++ Dialects}. -fdiagnostics-color=@r{[}auto@r{|}never@r{|}always@r{]} -fdiagnostics-urls=@r{[}auto@r{|}never@r{|}always@r{]} -fdiagnostics-format=@r{[}text@r{|}sarif-stderr@r{|}sarif-file@r{|}json@r{|}json-stderr@r{|}json-file@r{]} +-fdiagnostics-add-output=@var{DIAGNOSTICS-OUTPUT-SPEC} +-fdiagnostics-set-output=@var{DIAGNOSTICS-OUTPUT-SPEC} -fno-diagnostics-json-formatting -fno-diagnostics-show-option -fno-diagnostics-show-caret -fno-diagnostics-show-event-links @@ -5902,6 +5904,10 @@ Select a different format for printing diagnostics. @var{FORMAT} is @samp{text}, @samp{sarif-stderr}, @samp{sarif-file}, @samp{json}, @samp{json-stderr}, or @samp{json-file}. +Using this option replaces any additional ``output sinks'' added by +@option{-fdiagnostics-add-output=}, or that set by +@option{-fdiagnostics-set-output=}. + The default is @samp{text}. The @samp{sarif-stderr} and @samp{sarif-file} formats both emit @@ -5916,6 +5922,97 @@ where the JSON is emitted to. With @samp{json-stderr}, the JSON is emitted to stderr, whereas with @samp{json-file} it is written to @file{@var{source}.gcc.json}. +@opindex fdiagnostics-add-output +@item -fdiagnostics-add-output=@var{DIAGNOSTICS-OUTPUT-SPEC} +Add an additional ``output sink'' for emitting diagnostics. + +@var{DIAGNOSTICS-OUTPUT-SPEC} should specify a scheme, optionally followed +by @code{:} and one or more @var{KEY}=@var{VALUE} pairs, in this form: + +@smallexample +@var{SCHEME} +@var{SCHEME}:@var{KEY}=@var{VALUE} +@var{SCHEME}:@var{KEY}=@var{VALUE},@var{KEY2}=@var{VALUE2} +@end smallexample + +etc. + +@var{SCHEME} can be + +@table @gcctabopt + +@item text +Emit diagnostics to stderr using GCC's classic text output format. + +Supported keys are: + +@table @gcctabopt + +@item color=@r{[}yes@r{|}no@r{]} +Override colorization settings from @option{-fdiagnostics-color} for this +text output. + +@end table + +@item sarif +Emit diagnostics to a file in SARIF format. + +Supported keys are: + +@table @gcctabopt + +@item file=@var{FILENAME} +Specify the filename to write the SARIF output to, potentially with a +leading absolute or relative path. If not specified, it defaults to +@file{@var{source}.sarif}. + +@item version=@r{[}2.1@r{|}2.2-prerelease@r{]} +Specify the version of SARIF to use for the output. If not specified, +defaults to 2.1. @code{2.2-prerelease} uses an unofficial draft of the +future SARIF 2.2 specification and should only be used for experimentation +in this release. + +@end table + +@end table + +For example, + +@smallexample +-fdiagnostics-add-output=sarif:version=2.1,file=foo.2.1.sarif +-fdiagnostics-add-output=sarif:version=2.2-prerelease,file=foo.2.2.sarif +@end smallexample + +would add a pair of outputs, each writing to a different file, using +versions 2.1 and 2.2 of the SARIF standard respectively. + +In EBNF: + +@smallexample + +@var{diagnostics-output-specifier} = @var{diagnostics-output-name} + | @var{diagnostics-output-name}, ":", @var{key-value-pairs}; + +@var{diagnostics-output-name} = "text" | "sarif"; + +@var{key-value-pairs} = @var{key-value-pair} + | @var{key-value-pair} "," @var{key-value-pairs}; + +@var{key-value-pair} = @var{key} "=" @var{value}; + +@var{key} = ? string without a '=' ? ; +@var{value} = ? string without a ',' ? ; + +@end smallexample + +@opindex fdiagnostics-set-output +@item -fdiagnostics-set-output=@var{DIAGNOSTICS-OUTPUT-SPEC} +This works in a similar way to @option{-fdiagnostics-add-output=} except +that instead of adding an additional ``output sink'' for diagnostics, it +replaces all existing output sinks, such as from @option{-fdiagnostics-format=}, +@option{-fdiagnostics-add-output=}, or a prior call to +@option{-fdiagnostics-set-output=}. + @opindex fno-diagnostics-json-formatting @opindex fdiagnostics-json-formatting @item -fno-diagnostics-json-formatting diff --git a/gcc/fortran/error.cc b/gcc/fortran/error.cc index b1eda94ccb6..050a8f286ef 100644 --- a/gcc/fortran/error.cc +++ b/gcc/fortran/error.cc @@ -479,7 +479,7 @@ gfc_diagnostic_build_kind_prefix (diagnostic_context *context, gcc_assert (diagnostic->kind < DK_LAST_DIAGNOSTIC_KIND); const char *text = _(diagnostic_kind_text[diagnostic->kind]); const char *text_cs = "", *text_ce = ""; - pretty_printer *const pp = context->m_printer; + pretty_printer *const pp = context->get_reference_printer (); if (diagnostic_kind_color[diagnostic->kind]) { @@ -951,7 +951,7 @@ gfc_diagnostics_init (void) diagnostic_text_starter (global_dc) = gfc_diagnostic_text_starter; diagnostic_start_span (global_dc) = gfc_diagnostic_start_span; diagnostic_text_finalizer (global_dc) = gfc_diagnostic_text_finalizer; - diagnostic_format_decoder (global_dc) = gfc_format_decoder; + global_dc->set_format_decoder (gfc_format_decoder); global_dc->m_source_printing.caret_chars[0] = '1'; global_dc->m_source_printing.caret_chars[1] = '2'; pp_warning_buffer = new diagnostic_buffer (*global_dc); diff --git a/gcc/gcc.cc b/gcc/gcc.cc index 30f89a62f4f..5fe5fd86a98 100644 --- a/gcc/gcc.cc +++ b/gcc/gcc.cc @@ -50,6 +50,7 @@ compilation is specified by a string called a "spec". */ #include "opts-jobserver.h" #include "common/common-target.h" #include "gcc-urlifier.h" +#include "opts-diagnostic.h" #ifndef MATH_LIBRARY #define MATH_LIBRARY "m" @@ -4369,6 +4370,14 @@ driver_handle_option (struct gcc_options *opts, break; } + case OPT_fdiagnostics_add_output_: + handle_OPT_fdiagnostics_add_output_ (*opts, *dc, arg, loc); + break; + + case OPT_fdiagnostics_set_output_: + handle_OPT_fdiagnostics_set_output_ (*opts, *dc, arg, loc); + break; + case OPT_fdiagnostics_text_art_charset_: dc->set_text_art_charset ((enum diagnostic_text_art_charset)value); break; diff --git a/gcc/jit/dummy-frontend.cc b/gcc/jit/dummy-frontend.cc index 12167829b27..35475b5ad05 100644 --- a/gcc/jit/dummy-frontend.cc +++ b/gcc/jit/dummy-frontend.cc @@ -33,6 +33,7 @@ along with GCC; see the file COPYING3. If not see #include "cgraph.h" #include "target.h" #include "diagnostic-format-text.h" +#include "make-unique.h" #include @@ -981,6 +982,49 @@ struct ggc_root_tab jit_root_tab[] = LAST_GGC_ROOT_TAB }; +/* Subclass of diagnostic_output_format for libgccjit: like text + output, but capture the message and call add_diagnostic with it + on the active playback context. */ + +class jit_diagnostic_listener : public diagnostic_text_output_format +{ +public: + jit_diagnostic_listener (diagnostic_context &dc, + gcc::jit::playback::context &playback_ctxt) + : diagnostic_text_output_format (dc), + m_playback_ctxt (playback_ctxt) + { + } + + void dump (FILE *out, int indent) const final override + { + fprintf (out, "%*sjit_diagnostic_listener\n", indent, ""); + fprintf (out, "%*sm_playback_context: %p\n", + indent + 2, "", + (void *)&m_playback_ctxt); + } + + void on_report_diagnostic (const diagnostic_info &info, + diagnostic_t orig_diag_kind) + { + JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ()); + + /* Let the text output format do most of the work. */ + diagnostic_text_output_format::on_report_diagnostic (info, orig_diag_kind); + + const char *text = pp_formatted_text (get_printer ()); + + /* Delegate to the playback context (and thence to the + recording context). */ + gcc::jit::active_playback_ctxt->add_diagnostic (text, info); + + pp_clear_output_area (get_printer ()); + } + +private: + gcc::jit::playback::context &m_playback_ctxt; +}; + /* JIT-specific implementation of diagnostic callbacks. */ /* Implementation of "begin_diagnostic". */ @@ -992,24 +1036,22 @@ jit_begin_diagnostic (diagnostic_text_output_format &, gcc_assert (gcc::jit::active_playback_ctxt); JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ()); - /* No-op (apart from logging); the real error-handling is done in the - "end_diagnostic" hook. */ + /* No-op (apart from logging); the real error-handling is done by the + jit_diagnostic_listener. */ } /* Implementation of "end_diagnostic". */ static void -jit_end_diagnostic (diagnostic_text_output_format &text_output, - const diagnostic_info *diagnostic, +jit_end_diagnostic (diagnostic_text_output_format &, + const diagnostic_info *, diagnostic_t) { gcc_assert (gcc::jit::active_playback_ctxt); JIT_LOG_SCOPE (gcc::jit::active_playback_ctxt->get_logger ()); - /* Delegate to the playback context (and thence to the - recording context). */ - gcc_assert (diagnostic); - gcc::jit::active_playback_ctxt->add_diagnostic (&text_output.get_context (), *diagnostic); // FIXME + /* No-op (apart from logging); the real error-handling is done by the + jit_diagnostic_listener. */ } /* Language hooks. */ @@ -1030,6 +1072,10 @@ jit_langhook_init (void) gcc_assert (global_dc); diagnostic_text_starter (global_dc) = jit_begin_diagnostic; diagnostic_text_finalizer (global_dc) = jit_end_diagnostic; + auto sink + = ::make_unique (*global_dc, + *gcc::jit::active_playback_ctxt); + global_dc->set_output_format (std::move (sink)); build_common_tree_nodes (false); diff --git a/gcc/jit/jit-playback.cc b/gcc/jit/jit-playback.cc index e732c228751..8dd77e4d587 100644 --- a/gcc/jit/jit-playback.cc +++ b/gcc/jit/jit-playback.cc @@ -3687,14 +3687,9 @@ add_error_va (location *loc, const char *fmt, va_list ap) void playback::context:: -add_diagnostic (diagnostic_context *diag_context, +add_diagnostic (const char *text, const diagnostic_info &diagnostic) { - /* At this point the text has been formatted into the pretty-printer's - output buffer. */ - pretty_printer *pp = diag_context->m_printer; - const char *text = pp_formatted_text (pp); - /* Get location information (if any) from the diagnostic. The recording::context::add_error[_va] methods require a recording::location. We can't lookup the playback::location @@ -3714,7 +3709,6 @@ add_diagnostic (diagnostic_context *diag_context, } m_recording_ctxt->add_error (rec_loc, "%s", text); - pp_clear_output_area (pp); } /* Dealing with the linemap API. */ diff --git a/gcc/jit/jit-playback.h b/gcc/jit/jit-playback.h index 6e97b389cbb..77ae2add479 100644 --- a/gcc/jit/jit-playback.h +++ b/gcc/jit/jit-playback.h @@ -276,7 +276,7 @@ public: get_first_error () const; void - add_diagnostic (diagnostic_context *context, + add_diagnostic (const char *text, const diagnostic_info &diagnostic); void diff --git a/gcc/opts-diagnostic.cc b/gcc/opts-diagnostic.cc new file mode 100644 index 00000000000..8dd4234ab0f --- /dev/null +++ b/gcc/opts-diagnostic.cc @@ -0,0 +1,680 @@ +/* Support for -fdiagnostics-add-output= and -fdiagnostics-set-output=. + Copyright (C) 2024 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 +. */ + + +/* This file implements the options -fdiagnostics-add-output=, + -fdiagnostics-set-output=, and their domain-specific language. */ + +#include "config.h" +#define INCLUDE_ARRAY +#define INCLUDE_MEMORY +#define INCLUDE_STRING +#define INCLUDE_VECTOR +#include "system.h" +#include "coretypes.h" +#include "version.h" +#include "intl.h" +#include "diagnostic.h" +#include "diagnostic-color.h" +#include "diagnostic-format.h" +#include "diagnostic-format-text.h" +#include "diagnostic-format-sarif.h" +#include "selftest.h" +#include "selftest-diagnostic.h" +#include "pretty-print-markup.h" +#include "opts.h" +#include "options.h" +#include "make-unique.h" + +/* A namespace for handling the DSL of the arguments of + -fdiagnostics-add-output= and -fdiagnostics-set-output=. */ + +namespace gcc { +namespace diagnostics_output_spec { + +/* Decls. */ + +struct context +{ +public: + context (const gcc_options &opts, + diagnostic_context &dc, + line_maps *location_mgr, + location_t loc, + const char *option_name) + : m_opts (opts), m_dc (dc), m_location_mgr (location_mgr), m_loc (loc), + m_option_name (option_name) + {} + + void + report_error (const char *gmsgid, ...) const + ATTRIBUTE_GCC_DIAG(2,3); + + void + report_unknown_key (const char *unparsed_arg, + const std::string &key, + const std::string &format_name, + auto_vec &known_keys) const; + + void + report_missing_key (const char *unparsed_arg, + const std::string &key, + const std::string &format_name, + const char *metavar) const; + + diagnostic_output_file + open_output_file (label_text &&filename) const; + + const gcc_options &m_opts; + diagnostic_context &m_dc; + line_maps *m_location_mgr; + location_t m_loc; + const char *m_option_name; +}; + +struct name_and_params +{ + std::string m_format; + std::vector> m_kvs; +}; + +static std::unique_ptr +parse (const context &ctxt, const char *unparsed_arg); + +/* Class for parsing the arguments of -fdiagnostics-add-output= and + -fdiagnostics-set-output=, and making diagnostic_output_format + instances (or issuing errors). */ + +class output_factory +{ +public: + class handler + { + public: + handler (std::string name) : m_name (name) {} + virtual ~handler () {} + + const std::string &get_name () const { return m_name; } + + virtual std::unique_ptr + make_sink (const context &ctxt, + const char *unparsed_arg, + const name_and_params &parsed_arg) const = 0; + + protected: + bool + parse_bool_value (const context &ctxt, + const char *unparsed_arg, + const std::string &key, + const std::string &value, + bool &out) const + { + if (value == "yes") + { + out = true; + return true; + } + else if (value == "no") + { + out = false; + return true; + } + else + { + ctxt.report_error + ("%<%s%s%>:" + " unexpected value %qs for key %qs; expected %qs or %qs", + ctxt.m_option_name, unparsed_arg, + value.c_str (), + key.c_str (), + "yes", "no"); + + return false; + } + } + template + bool + parse_enum_value (const context &ctxt, + const char *unparsed_arg, + const std::string &key, + const std::string &value, + const std::array, NumValues> &value_names, + EnumType &out) const + { + for (auto &iter : value_names) + if (value == iter.first) + { + out = iter.second; + return true; + } + + auto_vec known_values; + for (auto iter : value_names) + known_values.safe_push (iter.first); + pp_markup::comma_separated_quoted_strings e (known_values); + ctxt.report_error + ("%<%s%s%>:" + " unexpected value %qs for key %qs; known values: %e", + ctxt.m_option_name, unparsed_arg, + value.c_str (), + key.c_str (), + &e); + return false; + } + + private: + const std::string m_name; + }; + + output_factory (); + + std::unique_ptr + make_sink (const context &ctxt, + const char *unparsed_arg, + const name_and_params &parsed_arg); + + const handler *get_handler (std::string name); + +private: + std::vector> m_handlers; +}; + +class text_handler : public output_factory::handler +{ +public: + text_handler () : handler ("text") {} + + std::unique_ptr + make_sink (const context &ctxt, + const char *unparsed_arg, + const name_and_params &parsed_arg) const final override; +}; + +class sarif_handler : public output_factory::handler +{ +public: + sarif_handler () : handler ("sarif") {} + + std::unique_ptr + make_sink (const context &ctxt, + const char *unparsed_arg, + const name_and_params &parsed_arg) const final override; +}; + +/* struct context. */ + +void +context::report_error (const char *gmsgid, ...) const +{ + m_dc.begin_group (); + va_list ap; + va_start (ap, gmsgid); + rich_location richloc (m_location_mgr, m_loc); + m_dc.diagnostic_impl (&richloc, nullptr, -1, gmsgid, &ap, DK_ERROR); + va_end (ap); + m_dc.end_group (); +} + +void +context::report_unknown_key (const char *unparsed_arg, + const std::string &key, + const std::string &format_name, + auto_vec &known_keys) const +{ + pp_markup::comma_separated_quoted_strings e (known_keys); + report_error + ("%<%s%s%>:" + " unknown key %qs for format %qs; known keys: %e", + m_option_name, unparsed_arg, + key.c_str (), format_name.c_str (), &e); +} + +void +context::report_missing_key (const char *unparsed_arg, + const std::string &key, + const std::string &format_name, + const char *metavar) const +{ + report_error + ("%<%s%s%>:" + " missing required key %qs for format %qs;" + " try %<%s%s:%s=%s%>", + m_option_name, unparsed_arg, + key.c_str (), format_name.c_str (), + m_option_name, format_name.c_str (), key.c_str (), metavar); +} + +std::unique_ptr +parse (const context &ctxt, const char *unparsed_arg) +{ + name_and_params result; + if (const char *const colon = strchr (unparsed_arg, ':')) + { + result.m_format = std::string (unparsed_arg, colon - unparsed_arg); + /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc .*/ + const char *iter = colon + 1; + const char *last_separator = ":"; + while (iter) + { + /* Look for a non-empty key string followed by '='. */ + const char *eq = strchr (iter, '='); + if (eq == nullptr || eq == iter) + { + /* Missing '='. */ + ctxt.report_error + ("%<%s%s%>:" + " expected KEY=VALUE-style parameter for format %qs" + " after %qs;" + " got %qs", + ctxt.m_option_name, unparsed_arg, + result.m_format.c_str (), + last_separator, + iter); + return nullptr; + } + std::string key = std::string (iter, eq - iter); + std::string value; + const char *comma = strchr (iter, ','); + if (comma) + { + value = std::string (eq + 1, comma - (eq + 1)); + iter = comma + 1; + last_separator = ","; + } + else + { + value = std::string (eq + 1); + iter = nullptr; + } + result.m_kvs.push_back ({std::move (key), std::move (value)}); + } + } + else + result.m_format = unparsed_arg; + return ::make_unique (std::move (result)); +} + +/* class output_factory::handler. */ + +/* class output_factory. */ + +output_factory::output_factory () +{ + m_handlers.push_back (::make_unique ()); + m_handlers.push_back (::make_unique ()); +} + +const output_factory::handler * +output_factory::get_handler (std::string name) +{ + for (auto &iter : m_handlers) + if (iter->get_name () == name) + return iter.get (); + return nullptr; +} + +std::unique_ptr +output_factory::make_sink (const context &ctxt, + const char *unparsed_arg, + const name_and_params &parsed_arg) +{ + auto handler = get_handler (parsed_arg.m_format); + if (!handler) + { + auto_vec strings; + for (auto &iter : m_handlers) + strings.safe_push (iter->get_name ().c_str ()); + pp_markup::comma_separated_quoted_strings e (strings); + ctxt.report_error ("%<%s%s%>:" + " unrecognized format %qs; known formats: %e", + ctxt.m_option_name, unparsed_arg, + parsed_arg.m_format.c_str (), &e); + return nullptr; + } + + return handler->make_sink (ctxt, unparsed_arg, parsed_arg); +} + +/* class text_handler : public output_factory::handler. */ + +std::unique_ptr +text_handler::make_sink (const context &ctxt, + const char *unparsed_arg, + const name_and_params &parsed_arg) const +{ + bool show_color = pp_show_color (ctxt.m_dc.get_reference_printer ()); + for (auto& iter : parsed_arg.m_kvs) + { + const std::string &key = iter.first; + const std::string &value = iter.second; + if (key == "color") + { + if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_color)) + return nullptr; + continue; + } + + /* Key not found. */ + auto_vec known_keys; + known_keys.safe_push ("color"); + ctxt.report_unknown_key (unparsed_arg, key, get_name (), known_keys); + return nullptr; + } + + std::unique_ptr sink; + sink = ::make_unique (ctxt.m_dc); + return sink; +} + +diagnostic_output_file +context::open_output_file (label_text &&filename) const +{ + FILE *outf = fopen (filename.get (), "w"); + if (!outf) + { + rich_location richloc (m_location_mgr, m_loc); + m_dc.emit_diagnostic_with_group + (DK_ERROR, richloc, nullptr, 0, + "unable to open %qs: %m", filename.get ()); + return diagnostic_output_file (nullptr, false, std::move (filename)); + } + return diagnostic_output_file (outf, true, std::move (filename)); +} + +/* class sarif_handler : public output_factory::handler. */ + +std::unique_ptr +sarif_handler::make_sink (const context &ctxt, + const char *unparsed_arg, + const name_and_params &parsed_arg) const +{ + enum sarif_version version = sarif_version::v2_1_0; + label_text filename; + for (auto& iter : parsed_arg.m_kvs) + { + const std::string &key = iter.first; + const std::string &value = iter.second; + if (key == "version") + { + static const std::array, + (size_t)sarif_version::num_versions> value_names + {{{"2.1", sarif_version::v2_1_0}, + {"2.2-prerelease", sarif_version::v2_2_prerelease_2024_08_08}}}; + + if (!parse_enum_value (ctxt, unparsed_arg, + key, value, + value_names, + version)) + return nullptr; + continue; + } + if (key == "file") + { + filename = label_text::take (xstrdup (value.c_str ())); + continue; + } + + /* Key not found. */ + auto_vec known_keys; + known_keys.safe_push ("file"); + known_keys.safe_push ("version"); + ctxt.report_unknown_key (unparsed_arg, key, get_name (), known_keys); + return nullptr; + } + + diagnostic_output_file output_file; + if (filename.get ()) + output_file = ctxt.open_output_file (std::move (filename)); + else + // Default filename + { + const char *basename = (ctxt.m_opts.x_dump_base_name + ? ctxt.m_opts.x_dump_base_name + : ctxt.m_opts.x_main_input_basename); + output_file = diagnostic_output_format_open_sarif_file (ctxt.m_dc, + line_table, + basename); + } + if (!output_file) + return nullptr; + + auto sink = make_sarif_sink (ctxt.m_dc, + *line_table, + ctxt.m_opts.x_main_input_filename, + version, + std::move (output_file)); + return sink; +} + +} // namespace diagnostics_output_spec +} // namespace gcc + +void +handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts, + diagnostic_context &dc, + const char *arg, + location_t loc) +{ + gcc_assert (arg); + gcc_assert (line_table); + + const char *const option_name = "-fdiagnostics-add-output="; + gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc, + option_name); + auto result = gcc::diagnostics_output_spec::parse (ctxt, arg); + if (!result) + return; + + gcc::diagnostics_output_spec::output_factory factory; + auto sink = factory.make_sink (ctxt, arg, *result); + if (!sink) + return; + + dc.add_sink (std::move (sink)); +} + +void +handle_OPT_fdiagnostics_set_output_ (const gcc_options &opts, + diagnostic_context &dc, + const char *arg, + location_t loc) +{ + gcc_assert (arg); + gcc_assert (line_table); + + const char *const option_name = "-fdiagnostics-set-output="; + gcc::diagnostics_output_spec::context ctxt (opts, dc, line_table, loc, + option_name); + auto result = gcc::diagnostics_output_spec::parse (ctxt, arg); + if (!result) + return; + + gcc::diagnostics_output_spec::output_factory factory; + auto sink = factory.make_sink (ctxt, arg, *result); + if (!sink) + return; + + dc.set_output_format (std::move (sink)); +} + +#if CHECKING_P + +namespace selftest { + +/* RAII class to temporarily override "progname" to the + string "PROGNAME". */ + +class auto_fix_progname +{ +public: + auto_fix_progname () + { + m_old_progname = progname; + progname = "PROGNAME"; + } + + ~auto_fix_progname () + { + progname = m_old_progname; + } + +private: + const char *m_old_progname; +}; + +struct parser_test +{ + parser_test () + : m_opts (), + m_dc (), + m_ctxt (m_opts, m_dc, line_table, UNKNOWN_LOCATION, "-fOPTION="), + m_fmt (m_dc.get_output_format (0)) + { + pp_buffer (m_fmt.get_printer ())->m_flush_p = false; + } + + std::unique_ptr + parse (const char *unparsed_arg) + { + return gcc::diagnostics_output_spec::parse (m_ctxt, unparsed_arg); + } + + bool execution_failed_p () const + { + return m_dc.execution_failed_p (); + } + + const char * + get_diagnostic_text () const + { + return pp_formatted_text (m_fmt.get_printer ()); + } + +private: + const gcc_options m_opts; + test_diagnostic_context m_dc; + gcc::diagnostics_output_spec::context m_ctxt; + diagnostic_output_format &m_fmt; +}; + +/* Selftests. */ + +static void +test_output_arg_parsing () +{ + auto_fix_quotes fix_quotes; + auto_fix_progname fix_progname; + + /* Minimal correct example. */ + { + parser_test pt; + auto result = pt.parse ("foo"); + ASSERT_EQ (result->m_format, "foo"); + ASSERT_EQ (result->m_kvs.size (), 0); + ASSERT_FALSE (pt.execution_failed_p ()); + } + + /* Stray trailing colon with no key/value pairs. */ + { + parser_test pt; + auto result = pt.parse ("foo:"); + ASSERT_EQ (result, nullptr); + ASSERT_TRUE (pt.execution_failed_p ()); + ASSERT_STREQ (pt.get_diagnostic_text (), + "PROGNAME: error: `-fOPTION=foo:':" + " expected KEY=VALUE-style parameter for format `foo'" + " after `:';" + " got `'\n"); + } + + /* No key before '='. */ + { + parser_test pt; + auto result = pt.parse ("foo:="); + ASSERT_EQ (result, nullptr); + ASSERT_TRUE (pt.execution_failed_p ()); + ASSERT_STREQ (pt.get_diagnostic_text (), + "PROGNAME: error: `-fOPTION=foo:=':" + " expected KEY=VALUE-style parameter for format `foo'" + " after `:';" + " got `='\n"); + } + + /* No value for key. */ + { + parser_test pt; + auto result = pt.parse ("foo:key,"); + ASSERT_EQ (result, nullptr); + ASSERT_TRUE (pt.execution_failed_p ()); + ASSERT_STREQ (pt.get_diagnostic_text (), + "PROGNAME: error: `-fOPTION=foo:key,':" + " expected KEY=VALUE-style parameter for format `foo'" + " after `:';" + " got `key,'\n"); + } + + /* Correct example, with one key/value pair. */ + { + parser_test pt; + auto result = pt.parse ("foo:key=value"); + ASSERT_EQ (result->m_format, "foo"); + ASSERT_EQ (result->m_kvs.size (), 1); + ASSERT_EQ (result->m_kvs[0].first, "key"); + ASSERT_EQ (result->m_kvs[0].second, "value"); + ASSERT_FALSE (pt.execution_failed_p ()); + } + + /* Stray trailing comma. */ + { + parser_test pt; + auto result = pt.parse ("foo:key=value,"); + ASSERT_EQ (result, nullptr); + ASSERT_TRUE (pt.execution_failed_p ()); + ASSERT_STREQ (pt.get_diagnostic_text (), + "PROGNAME: error: `-fOPTION=foo:key=value,':" + " expected KEY=VALUE-style parameter for format `foo'" + " after `,';" + " got `'\n"); + } + + /* Correct example, with two key/value pairs. */ + { + parser_test pt; + auto result = pt.parse ("foo:color=red,shape=circle"); + ASSERT_EQ (result->m_format, "foo"); + ASSERT_EQ (result->m_kvs.size (), 2); + ASSERT_EQ (result->m_kvs[0].first, "color"); + ASSERT_EQ (result->m_kvs[0].second, "red"); + ASSERT_EQ (result->m_kvs[1].first, "shape"); + ASSERT_EQ (result->m_kvs[1].second, "circle"); + ASSERT_FALSE (pt.execution_failed_p ()); + } +} + +/* Run all of the selftests within this file. */ + +void +opts_diagnostic_cc_tests () +{ + test_output_arg_parsing (); +} + +} // namespace selftest + +#endif /* #if CHECKING_P */ diff --git a/gcc/opts-diagnostic.h b/gcc/opts-diagnostic.h index 48cc21e31a4..95fc16bb323 100644 --- a/gcc/opts-diagnostic.h +++ b/gcc/opts-diagnostic.h @@ -59,4 +59,15 @@ private: void *m_opts; }; +extern void +handle_OPT_fdiagnostics_add_output_ (const gcc_options &opts, + diagnostic_context &dc, + const char *arg, + location_t loc); + +extern void +handle_OPT_fdiagnostics_set_output_ (const gcc_options &opts, + diagnostic_context &dc, + const char *arg, + location_t loc); #endif diff --git a/gcc/opts-global.cc b/gcc/opts-global.cc index 9593ec8a8fd..cbf4018b120 100644 --- a/gcc/opts-global.cc +++ b/gcc/opts-global.cc @@ -258,7 +258,7 @@ init_options_once (void) initial_lang_mask = lang_hooks.option_lang_mask (); const bool show_highlight_colors - = pp_show_highlight_colors (global_dc->m_printer); + = pp_show_highlight_colors (global_dc->get_reference_printer ()); lang_hooks.initialize_diagnostics (global_dc); /* ??? Ideally, we should do this earlier and the FEs will override @@ -269,6 +269,7 @@ init_options_once (void) diagnostic_color_init (global_dc); diagnostic_urls_init (global_dc); + global_dc->refresh_output_sinks (); } /* Decode command-line options to an array, like diff --git a/gcc/opts.cc b/gcc/opts.cc index 5d08e5ab2b5..64d130c46c0 100644 --- a/gcc/opts.cc +++ b/gcc/opts.cc @@ -2936,7 +2936,7 @@ common_handle_option (struct gcc_options *opts, break; case OPT_fdiagnostics_show_location_: - diagnostic_prefixing_rule (dc) = (diagnostic_prefixing_rule_t) value; + dc->set_prefixing_rule ((diagnostic_prefixing_rule_t) value); break; case OPT_fdiagnostics_show_caret: @@ -2975,6 +2975,14 @@ common_handle_option (struct gcc_options *opts, break; } + case OPT_fdiagnostics_add_output_: + handle_OPT_fdiagnostics_add_output_ (*opts, *dc, arg, loc); + break; + + case OPT_fdiagnostics_set_output_: + handle_OPT_fdiagnostics_set_output_ (*opts, *dc, arg, loc); + break; + case OPT_fdiagnostics_text_art_charset_: dc->set_text_art_charset ((enum diagnostic_text_art_charset)value); break; @@ -3059,7 +3067,7 @@ common_handle_option (struct gcc_options *opts, break; case OPT_fmessage_length_: - pp_set_line_maximum_length (dc->m_printer, value); + pp_set_line_maximum_length (dc->get_reference_printer (), value); diagnostic_set_caret_max_width (dc, value); break; diff --git a/gcc/selftest-diagnostic.cc b/gcc/selftest-diagnostic.cc index a066fe20066..a9118b55f18 100644 --- a/gcc/selftest-diagnostic.cc +++ b/gcc/selftest-diagnostic.cc @@ -37,7 +37,7 @@ namespace selftest { test_diagnostic_context::test_diagnostic_context () { diagnostic_initialize (this, 0); - pp_show_color (m_printer) = false; + pp_show_color (get_reference_printer ()) = false; m_source_printing.enabled = true; m_source_printing.show_labels_p = true; m_show_column = true; @@ -86,10 +86,11 @@ test_diagnostic_context::report (diagnostic_t kind, const char * test_diagnostic_context::test_show_locus (rich_location &richloc) { - gcc_assert (m_printer); + pretty_printer *pp = get_reference_printer (); + gcc_assert (pp); diagnostic_source_print_policy source_policy (*this); - source_policy.print (*m_printer, richloc, DK_ERROR, nullptr); - return pp_formatted_text (m_printer); + source_policy.print (*pp, richloc, DK_ERROR, nullptr); + return pp_formatted_text (pp); } } // namespace selftest diff --git a/gcc/selftest-run-tests.cc b/gcc/selftest-run-tests.cc index d6c88f864ba..6085e334b91 100644 --- a/gcc/selftest-run-tests.cc +++ b/gcc/selftest-run-tests.cc @@ -106,6 +106,7 @@ selftest::run_tests () diagnostic_path_cc_tests (); simple_diagnostic_path_cc_tests (); attribs_cc_tests (); + opts_diagnostic_cc_tests (); /* This one relies on most of the above. */ function_tests_cc_tests (); diff --git a/gcc/selftest.h b/gcc/selftest.h index 5afc9399c61..5109128f7f3 100644 --- a/gcc/selftest.h +++ b/gcc/selftest.h @@ -241,6 +241,7 @@ extern void input_cc_tests (); extern void json_cc_tests (); extern void optinfo_emit_json_cc_tests (); extern void opts_cc_tests (); +extern void opts_diagnostic_cc_tests (); extern void ordered_hash_map_tests_cc_tests (); extern void predict_cc_tests (); extern void pretty_print_cc_tests (); diff --git a/gcc/simple-diagnostic-path.cc b/gcc/simple-diagnostic-path.cc index 0592080f3ae..6414cf2c82e 100644 --- a/gcc/simple-diagnostic-path.cc +++ b/gcc/simple-diagnostic-path.cc @@ -226,7 +226,7 @@ simple_diagnostic_path_cc_tests () { /* In a few places we use the global dc's printer to determine colorization so ensure this off during the tests. */ - pretty_printer *global_pp = global_dc->m_printer; + pretty_printer *global_pp = global_dc->get_reference_printer (); const bool saved_show_color = pp_show_color (global_pp); pp_show_color (global_pp) = false; diff --git a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c index 18153b0733c..467af16c3d1 100644 --- a/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c +++ b/gcc/testsuite/gcc.dg/plugin/analyzer_cpython_plugin.c @@ -529,7 +529,7 @@ dump_refcnt_info (const hash_map ®ion_to_refcnt, region_model_manager *mgr = model->get_manager (); pretty_printer pp; pp_format_decoder (&pp) = default_tree_printer; - pp_show_color (&pp) = pp_show_color (global_dc->m_printer); + pp_show_color (&pp) = pp_show_color (global_dc->get_reference_printer ()); pp.set_output_stream (stderr); for (const auto ®ion_refcnt : region_to_refcnt) diff --git a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c index b5328060c39..1c4cfbe0723 100644 --- a/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c +++ b/gcc/testsuite/gcc.dg/plugin/crash-test-ice-in-header-sarif-2.2.c @@ -1,7 +1,7 @@ /* Test of an ICE triggered within a header file with SARIF 2.2 */ /* { dg-do compile } */ -/* { dg-options "-fdiagnostics-format=sarif-file-2.2-prerelease" } */ +/* { dg-options "-fdiagnostics-set-output=sarif:version=2.2-prerelease" } */ /* { dg-additional-options "-fno-report-bug" } */ #include "crash-test-ice-in-header.h" /* { dg-ice "" } */ diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c index f7952e7d1d1..954538f962b 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_test_paths.c @@ -147,7 +147,7 @@ example_1 () { auto_diagnostic_group d; gcc_rich_location richloc (gimple_location (call_to_PyList_Append)); - simple_diagnostic_path path (global_dc->m_printer); + simple_diagnostic_path path (global_dc->get_reference_printer ()); diagnostic_event_id_t alloc_event_id = path.add_event (gimple_location (call_to_PyList_New), example_a_fun->decl, 0, @@ -335,7 +335,7 @@ example_2 () auto_diagnostic_group d; gcc_rich_location richloc (call_to_free.m_loc); - test_diagnostic_path path (global_dc->m_printer); + test_diagnostic_path path (global_dc->get_reference_printer ()); path.add_entry (entry_to_test, 0, "test"); path.add_call (call_to_make_boxed_int, 0, entry_to_make_boxed_int, "make_boxed_int"); @@ -420,7 +420,7 @@ example_3 () auto_diagnostic_group d; gcc_rich_location richloc (call_to_fprintf.m_loc); - test_diagnostic_path path (global_dc->m_printer); + test_diagnostic_path path (global_dc->get_reference_printer ()); path.add_entry (entry_to_test, 1, "test"); path.add_call (call_to_register_handler, 1, entry_to_register_handler, "register_handler"); @@ -495,7 +495,7 @@ example_4 () auto_diagnostic_group d; gcc_rich_location richloc (call_to_acquire_lock_a_in_bar.m_loc); - test_diagnostic_path path (global_dc->m_printer); + test_diagnostic_path path (global_dc->get_reference_printer ()); diagnostic_thread_id_t thread_1 = path.add_thread ("Thread 1"); diagnostic_thread_id_t thread_2 = path.add_thread ("Thread 2"); path.add_entry (entry_to_foo, 0, "foo", thread_1); diff --git a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c index 2f95e4a0ceb..aa03b7d5d2c 100644 --- a/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c +++ b/gcc/testsuite/gcc.dg/plugin/diagnostic_plugin_xhtml_format.c @@ -316,13 +316,18 @@ public: const xml::document &get_document () const { return *m_document; } + void set_printer (pretty_printer &pp) + { + m_printer = &pp; + } + private: std::unique_ptr make_element_for_diagnostic (const diagnostic_info &diagnostic, diagnostic_t orig_diag_kind); diagnostic_context &m_context; - pretty_printer &m_printer; + pretty_printer *m_printer; const line_maps *m_line_maps; std::unique_ptr m_document; @@ -400,7 +405,7 @@ xhtml_builder::xhtml_builder (diagnostic_context &context, pretty_printer &pp, const line_maps *line_maps) : m_context (context), - m_printer (pp), + m_printer (&pp), m_line_maps (line_maps) { gcc_assert (m_line_maps); @@ -565,10 +570,10 @@ xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, auto message_span = make_span (label_text::borrow ("gcc-message")); xhtml_token_printer tok_printer (*this, *message_span.get ()); - m_printer.set_token_printer (&tok_printer); - pp_output_formatted_text (&m_printer, m_context.get_urlifier ()); - m_printer.set_token_printer (nullptr); - pp_clear_output_area (&m_printer); + m_printer->set_token_printer (&tok_printer); + pp_output_formatted_text (m_printer, m_context.get_urlifier ()); + m_printer->set_token_printer (nullptr); + pp_clear_output_area (m_printer); diag_element->add_child (std::move (message_span)); if (diagnostic.metadata) @@ -626,10 +631,10 @@ xhtml_builder::make_element_for_diagnostic (const diagnostic_info &diagnostic, pre->set_attr ("class", label_text::borrow ("gcc-annotated-source")); // TODO: ideally we'd like to capture elements within the following: diagnostic_show_locus (&m_context, diagnostic.richloc, diagnostic.kind, - &m_printer); + m_printer); pre->add_text - (label_text::take (xstrdup (pp_formatted_text (&m_printer)))); - pp_clear_output_area (&m_printer); + (label_text::take (xstrdup (pp_formatted_text (m_printer)))); + pp_clear_output_area (m_printer); diag_element->add_child (std::move (pre)); } @@ -723,6 +728,23 @@ public: { /* No-op, but perhaps could show paths here. */ } + bool follows_reference_printer_p () const final override + { + return false; + } + void update_printer () final override + { + m_printer = m_context.clone_printer (); + + /* Don't colorize the text. */ + pp_show_color (m_printer.get ()) = false; + + /* No textual URLs. */ + m_printer->set_url_format (URL_FORMAT_NONE); + + /* Update the builder to use the new printer. */ + m_builder.set_printer (*get_printer ()); + } const xml::document &get_document () const { diff --git a/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c b/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c index 554dad6fa35..e96efe0bed5 100644 --- a/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c +++ b/gcc/testsuite/gcc.dg/plugin/expensive_selftests_plugin.c @@ -48,7 +48,7 @@ test_richloc (rich_location *richloc) { /* Run the diagnostic and fix-it printing code. */ test_diagnostic_context dc; - diagnostic_show_locus (&dc, richloc, DK_ERROR, dc.m_printer); + diagnostic_show_locus (&dc, richloc, DK_ERROR, dc.get_reference_printer ()); /* Generate a diff. */ edit_context ec (global_dc->get_file_cache ()); diff --git a/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c b/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c new file mode 100644 index 00000000000..ce650582488 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/add-output-sarif-defaults.c @@ -0,0 +1,16 @@ +/* Verify using -fdiagnostics-add-output=sarif with the defaults. */ + +/* { dg-do compile } */ +/* { dg-additional-options "-fdiagnostics-add-output=sarif" } */ + +/* Verify that SARIF output can capture secondary locations + relating to a diagnostic. */ + +int missing_semicolon (void) +{ + return 42 /* { dg-error "expected ';' before '.' token" } */ +} + +/* Verify that JSON was written to the output file with the + expected version and expected name: + { dg-final { verify-sarif-file "2.1" } } */ diff --git a/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c new file mode 100644 index 00000000000..1d87bb81e97 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.c @@ -0,0 +1,30 @@ +/* { dg-options "-fdiagnostics-show-caret -fdiagnostics-add-output=sarif" } */ + +struct s {}; +struct t {}; + +typedef struct s S; +typedef struct t T; + +extern S callee_4a (void); +extern T callee_4b (void); + +int test_4 (void) +{ + return callee_4a () + callee_4b (); /* { dg-error "invalid operands to binary \+" } */ + +/* { dg-begin-multiline-output "" } + return callee_4a () + callee_4b (); + ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~ + | | + | T {aka struct t} + S {aka struct s} + { dg-end-multiline-output "" } */ +} + +/* Verify that some JSON was written to a file with the expected name. */ +/* { dg-final { verify-sarif-file } } */ + +/* Use a Python script to verify various properties about the generated + .sarif file: + { dg-final { run-sarif-pytest bad-binary-op.c "bad-binary-op.py" } } */ diff --git a/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py new file mode 100644 index 00000000000..fe139e62e41 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/bad-binary-op.py @@ -0,0 +1,70 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_basics(sarif): + schema = sarif['$schema'] + assert schema == "https://docs.oasis-open.org/sarif/sarif/v2.1.0/errata01/os/schemas/sarif-schema-2.1.0.json" + + version = sarif['version'] + assert version == "2.1.0" + +def test_execution_unsuccessful(sarif): + runs = sarif['runs'] + run = runs[0] + + invocations = run['invocations'] + assert len(invocations) == 1 + invocation = invocations[0] + + # We expect the 'error' to make executionSuccessful be false + assert invocation['executionSuccessful'] == False + +def test_error_location(sarif): + runs = sarif['runs'] + run = runs[0] + results = run['results'] + + # We expect a single error with annotations. + # + # The textual form of the diagnostic looks like this: + # . PATH/bad-binary-ops.c: In function 'test_4': + # . PATH/bad-binary-ops.c:64:23: error: invalid operands to binary + (have 'S' {aka 'struct s'} and 'T' {aka 'struct t'}) + # . return callee_4a () + callee_4b (); /* { dg-error "invalid operands to binary \+" } */ + # . ~~~~~~~~~~~~ ^ ~~~~~~~~~~~~ + # . | | + # . | T {aka struct t} + # . S {aka struct s} + assert len(results) == 1 + + result = results[0] + assert result['level'] == 'error' + + assert result['message']['text'] \ + == "invalid operands to binary + (have 'S' {aka 'struct s'} and 'T' {aka 'struct t'})" + locations = result['locations'] + assert len(locations) == 1 + + location = locations[0] + assert get_location_artifact_uri(location).endswith('bad-binary-op.c') + assert get_location_snippet_text(location) \ + == " return callee_4a () + callee_4b (); /* { dg-error \"invalid operands to binary \\+\" } */\n" + EXPECTED_LINE = 14 + assert get_location_physical_region(location)['startLine'] == EXPECTED_LINE + assert get_location_physical_region(location)['startColumn'] == 23 + assert get_location_physical_region(location)['endColumn'] == 24 + + annotations = location['annotations'] + assert len(annotations) == 2 + assert annotations[0]['startLine'] == EXPECTED_LINE + assert annotations[0]['startColumn'] == 10 + assert annotations[0]['endColumn'] == 22 + assert annotations[0]['message']['text'] == "S {aka struct s}" + assert annotations[1]['startLine'] == EXPECTED_LINE + assert annotations[1]['startColumn'] == 25 + assert annotations[1]['endColumn'] == 37 + assert annotations[1]['message']['text'] == "T {aka struct t}" diff --git a/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c new file mode 100644 index 00000000000..6a673a5f808 --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.c @@ -0,0 +1,27 @@ +/* Verify that we can output multiple different versions of SARIF + with -fdiagnostics-add-output (specifying version and filename), + as well as usual text output. */ + +/* { dg-do compile } */ +/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=multiple-outputs-c.2.1.sarif,version=2.1" } */ +/* { dg-additional-options "-fdiagnostics-add-output=sarif:file=multiple-outputs-c.2.2.sarif,version=2.2-prerelease" } */ + +/* Verify that SARIF output can capture secondary locations + relating to a diagnostic. */ + +int missing_semicolon (void) +{ + return 42 /* { dg-error "expected ';' before '.' token" } */ +} + +/* Verify that JSON was written to the output files with the + expected version and expected names: + { dg-final { verify-sarif-file "2.1" "multiple-outputs-c.2.1.sarif" } } + { dg-final { verify-sarif-file "2.2" "multiple-outputs-c.2.2.sarif" } } +*/ + +/* Use a Python script to verify various properties about the generated + .sarif files: + { dg-final { run-sarif-pytest multiple-outputs-c.2.1 "multiple-outputs.py" } } + { dg-final { run-sarif-pytest multiple-outputs-c.2.2 "multiple-outputs.py" } } +*/ diff --git a/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py new file mode 100644 index 00000000000..8febfac4c7b --- /dev/null +++ b/gcc/testsuite/gcc.dg/sarif-output/multiple-outputs.py @@ -0,0 +1,50 @@ +from sarif import * + +import pytest + +@pytest.fixture(scope='function', autouse=True) +def sarif(): + return sarif_from_env() + +def test_execution_unsuccessful(sarif): + runs = sarif['runs'] + run = runs[0] + + invocations = run['invocations'] + assert len(invocations) == 1 + invocation = invocations[0] + + # We expect the 'error' to make executionSuccessful be false + assert invocation['executionSuccessful'] == False + +def test_result(sarif): + runs = sarif['runs'] + run = runs[0] + results = run['results'] + + # We expect a single error with a secondary location and a fix-it hint. + # + # The textual form of the diagnostic would look like this: + # . PATH/missing-semicolon.c: In function 'missing_semicolon': + # . PATH/missing-semicolon.c:19:12: error: expected ';' before '}' token + # . 19 | return 42 + # . | ^ + # . | ; + # . 20 | } + # . | ~ + assert len(results) == 1 + + result = results[0] + assert result['level'] == 'error' + assert result['message']['text'] == "expected ';' before '}' token" + locations = result['locations'] + assert len(locations) == 1 + + assert len(result['fixes']) == 1 + assert len(result['fixes'][0]['artifactChanges']) == 1 + change = result['fixes'][0]['artifactChanges'][0] + assert len(change['replacements']) == 1 + replacement = change['replacements'][0] + assert replacement['deletedRegion']['startColumn'] == 12 + assert replacement['deletedRegion']['endColumn'] == 12 + assert replacement['insertedContent']['text'] == ';' diff --git a/gcc/testsuite/lib/scansarif.exp b/gcc/testsuite/lib/scansarif.exp index f27f3ee2164..91801fc1157 100644 --- a/gcc/testsuite/lib/scansarif.exp +++ b/gcc/testsuite/lib/scansarif.exp @@ -63,17 +63,24 @@ proc scan-sarif-file-not { args } { # The first argument is the version of the SARIF schema to validate against # If present can be "2.1" or "2.2" # If absent, validate against 2.1 +# +# If present, the second argument is the expected filename of the .sarif file proc verify-sarif-file { args } { global srcdir subdir set testcase [testname-for-summary] set filename [lindex $testcase 0] - set output_file "[file tail $filename].sarif" set version [lindex $args 0] verbose "sarif version: $version" 2 + set output_file [lindex $args 1] + verbose "output_file: $output_file" 2 + if { $output_file == "" } { + set output_file "[file tail $filename].sarif" + } + if { ![check_effective_target_recent_python3] } { unsupported "$testcase verify-sarif-file: python3 is missing" return diff --git a/gcc/toplev.cc b/gcc/toplev.cc index c60616bccdb..779049674b4 100644 --- a/gcc/toplev.cc +++ b/gcc/toplev.cc @@ -231,7 +231,7 @@ announce_function (tree decl) fprintf (stderr, " %s", identifier_to_locale (lang_hooks.decl_printable_name (decl, 2))); fflush (stderr); - pp_needs_newline (global_dc->m_printer) = true; + pp_needs_newline (global_dc->get_reference_printer ()) = true; diagnostic_set_last_function (global_dc, (diagnostic_info *) NULL); } } @@ -2389,7 +2389,7 @@ toplev::main (int argc, char **argv) if (auto edit_context_ptr = global_dc->get_edit_context ()) { pretty_printer pp; - pp_show_color (&pp) = pp_show_color (global_dc->m_printer); + pp_show_color (&pp) = pp_show_color (global_dc->get_reference_printer ()); edit_context_ptr->print_diff (&pp, true); pp_flush (&pp); } diff --git a/gcc/tree-diagnostic.cc b/gcc/tree-diagnostic.cc index b7d2c51c984..84e7ae7721f 100644 --- a/gcc/tree-diagnostic.cc +++ b/gcc/tree-diagnostic.cc @@ -180,7 +180,7 @@ tree_diagnostics_defaults (diagnostic_context *context) { diagnostic_text_starter (context) = default_tree_diagnostic_text_starter; diagnostic_text_finalizer (context) = default_diagnostic_text_finalizer; - diagnostic_format_decoder (context) = default_tree_printer; + context->set_format_decoder (default_tree_printer); context->set_set_locations_callback (set_inlining_locations); context->set_client_data_hooks (make_compiler_data_hooks ()); } diff --git a/gcc/tree-diagnostic.h b/gcc/tree-diagnostic.h index dbc8ef6ae01..aee780f4b8e 100644 --- a/gcc/tree-diagnostic.h +++ b/gcc/tree-diagnostic.h @@ -66,7 +66,7 @@ public: { pp_format_decoder (this) = default_tree_printer; if (outf == stderr) - pp_show_color (this) = pp_show_color (global_dc->m_printer); + pp_show_color (this) = pp_show_color (global_dc->get_reference_printer ()); set_output_stream (outf); } ~tree_dump_pretty_printer () diff --git a/gcc/tree.cc b/gcc/tree.cc index 624c50db165..45f0474f9e3 100644 --- a/gcc/tree.cc +++ b/gcc/tree.cc @@ -12429,7 +12429,7 @@ escaped_string::escape (const char *unescaped) continue; } - if (c != '\n' || !pp_is_wrapping_line (global_dc->m_printer)) + if (c != '\n' || !pp_is_wrapping_line (global_dc->get_reference_printer ())) { if (escaped == NULL) { @@ -15901,7 +15901,7 @@ test_escaped_strings (void) ASSERT_STREQ ("foobar", (const char *) msg); /* Ensure that we have -fmessage-length set to 0. */ - pretty_printer *pp = global_dc->m_printer; + pretty_printer *pp = global_dc->get_reference_printer (); saved_cutoff = pp_line_cutoff (pp); pp_line_cutoff (pp) = 0;