1 /* Support for -fdiagnostics-add-output= and -fdiagnostics-set-output=.
2 Copyright (C) 2024-2025 Free Software Foundation, Inc.
4 This file is part of GCC.
6 GCC is free software; you can redistribute it and/or modify it under
7 the terms of the GNU General Public License as published by the Free
8 Software Foundation; either version 3, or (at your option) any later
11 GCC is distributed in the hope that it will be useful, but WITHOUT ANY
12 WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
16 You should have received a copy of the GNU General Public License
17 along with GCC; see the file COPYING3. If not see
18 <http://www.gnu.org/licenses/>. */
21 /* This file implements the options -fdiagnostics-add-output=,
22 -fdiagnostics-set-output=, and their domain-specific language. */
26 #define INCLUDE_STRING
27 #define INCLUDE_VECTOR
29 #include "coretypes.h"
32 #include "diagnostic.h"
33 #include "diagnostic-color.h"
34 #include "diagnostic-format.h"
35 #include "diagnostic-format-text.h"
36 #include "diagnostic-format-sarif.h"
38 #include "selftest-diagnostic.h"
39 #include "pretty-print-markup.h"
42 #include "make-unique.h"
44 /* A namespace for handling the DSL of the arguments of
45 -fdiagnostics-add-output= and -fdiagnostics-set-output=. */
48 namespace diagnostics_output_spec
{
55 context (const gcc_options
&opts
,
56 diagnostic_context
&dc
,
57 line_maps
*location_mgr
,
59 const char *option_name
)
60 : m_opts (opts
), m_dc (dc
), m_location_mgr (location_mgr
), m_loc (loc
),
61 m_option_name (option_name
)
65 report_error (const char *gmsgid
, ...) const
66 ATTRIBUTE_GCC_DIAG(2,3);
69 report_unknown_key (const char *unparsed_arg
,
70 const std::string
&key
,
71 const std::string
&scheme_name
,
72 auto_vec
<const char *> &known_keys
) const;
75 report_missing_key (const char *unparsed_arg
,
76 const std::string
&key
,
77 const std::string
&scheme_name
,
78 const char *metavar
) const;
80 diagnostic_output_file
81 open_output_file (label_text
&&filename
) const;
83 const gcc_options
&m_opts
;
84 diagnostic_context
&m_dc
;
85 line_maps
*m_location_mgr
;
87 const char *m_option_name
;
90 struct scheme_name_and_params
92 std::string m_scheme_name
;
93 std::vector
<std::pair
<std::string
, std::string
>> m_kvs
;
96 static std::unique_ptr
<scheme_name_and_params
>
97 parse (const context
&ctxt
, const char *unparsed_arg
);
99 /* Class for parsing the arguments of -fdiagnostics-add-output= and
100 -fdiagnostics-set-output=, and making diagnostic_output_format
101 instances (or issuing errors). */
109 scheme_handler (std::string scheme_name
)
110 : m_scheme_name (std::move (scheme_name
))
112 virtual ~scheme_handler () {}
114 const std::string
&get_scheme_name () const { return m_scheme_name
; }
116 virtual std::unique_ptr
<diagnostic_output_format
>
117 make_sink (const context
&ctxt
,
118 const char *unparsed_arg
,
119 const scheme_name_and_params
&parsed_arg
) const = 0;
123 parse_bool_value (const context
&ctxt
,
124 const char *unparsed_arg
,
125 const std::string
&key
,
126 const std::string
&value
,
134 else if (value
== "no")
143 " unexpected value %qs for key %qs; expected %qs or %qs",
144 ctxt
.m_option_name
, unparsed_arg
,
152 template <typename EnumType
, size_t NumValues
>
154 parse_enum_value (const context
&ctxt
,
155 const char *unparsed_arg
,
156 const std::string
&key
,
157 const std::string
&value
,
158 const std::array
<std::pair
<const char *, EnumType
>, NumValues
> &value_names
,
161 for (auto &iter
: value_names
)
162 if (value
== iter
.first
)
168 auto_vec
<const char *> known_values
;
169 for (auto iter
: value_names
)
170 known_values
.safe_push (iter
.first
);
171 pp_markup::comma_separated_quoted_strings
e (known_values
);
174 " unexpected value %qs for key %qs; known values: %e",
175 ctxt
.m_option_name
, unparsed_arg
,
183 const std::string m_scheme_name
;
188 std::unique_ptr
<diagnostic_output_format
>
189 make_sink (const context
&ctxt
,
190 const char *unparsed_arg
,
191 const scheme_name_and_params
&parsed_arg
);
193 const scheme_handler
*get_scheme_handler (const std::string
&scheme_name
);
196 std::vector
<std::unique_ptr
<scheme_handler
>> m_scheme_handlers
;
199 class text_scheme_handler
: public output_factory::scheme_handler
202 text_scheme_handler () : scheme_handler ("text") {}
204 std::unique_ptr
<diagnostic_output_format
>
205 make_sink (const context
&ctxt
,
206 const char *unparsed_arg
,
207 const scheme_name_and_params
&parsed_arg
) const final override
;
210 class sarif_scheme_handler
: public output_factory::scheme_handler
213 sarif_scheme_handler () : scheme_handler ("sarif") {}
215 std::unique_ptr
<diagnostic_output_format
>
216 make_sink (const context
&ctxt
,
217 const char *unparsed_arg
,
218 const scheme_name_and_params
&parsed_arg
) const final override
;
221 /* struct context. */
224 context::report_error (const char *gmsgid
, ...) const
228 va_start (ap
, gmsgid
);
229 rich_location
richloc (m_location_mgr
, m_loc
);
230 m_dc
.diagnostic_impl (&richloc
, nullptr, -1, gmsgid
, &ap
, DK_ERROR
);
236 context::report_unknown_key (const char *unparsed_arg
,
237 const std::string
&key
,
238 const std::string
&scheme_name
,
239 auto_vec
<const char *> &known_keys
) const
241 pp_markup::comma_separated_quoted_strings
e (known_keys
);
244 " unknown key %qs for format %qs; known keys: %e",
245 m_option_name
, unparsed_arg
,
246 key
.c_str (), scheme_name
.c_str (), &e
);
250 context::report_missing_key (const char *unparsed_arg
,
251 const std::string
&key
,
252 const std::string
&scheme_name
,
253 const char *metavar
) const
257 " missing required key %qs for format %qs;"
258 " try %<%s%s:%s=%s%>",
259 m_option_name
, unparsed_arg
,
260 key
.c_str (), scheme_name
.c_str (),
261 m_option_name
, scheme_name
.c_str (), key
.c_str (), metavar
);
264 std::unique_ptr
<scheme_name_and_params
>
265 parse (const context
&ctxt
, const char *unparsed_arg
)
267 scheme_name_and_params result
;
268 if (const char *const colon
= strchr (unparsed_arg
, ':'))
270 result
.m_scheme_name
= std::string (unparsed_arg
, colon
- unparsed_arg
);
271 /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc .*/
272 const char *iter
= colon
+ 1;
273 const char *last_separator
= ":";
276 /* Look for a non-empty key string followed by '='. */
277 const char *eq
= strchr (iter
, '=');
278 if (eq
== nullptr || eq
== iter
)
283 " expected KEY=VALUE-style parameter for format %qs"
286 ctxt
.m_option_name
, unparsed_arg
,
287 result
.m_scheme_name
.c_str (),
292 std::string key
= std::string (iter
, eq
- iter
);
294 const char *comma
= strchr (iter
, ',');
297 value
= std::string (eq
+ 1, comma
- (eq
+ 1));
299 last_separator
= ",";
303 value
= std::string (eq
+ 1);
306 result
.m_kvs
.push_back ({std::move (key
), std::move (value
)});
310 result
.m_scheme_name
= unparsed_arg
;
311 return ::make_unique
<scheme_name_and_params
> (std::move (result
));
314 /* class output_factory::scheme_handler. */
316 /* class output_factory. */
318 output_factory::output_factory ()
320 m_scheme_handlers
.push_back (::make_unique
<text_scheme_handler
> ());
321 m_scheme_handlers
.push_back (::make_unique
<sarif_scheme_handler
> ());
324 const output_factory::scheme_handler
*
325 output_factory::get_scheme_handler (const std::string
&scheme_name
)
327 for (auto &iter
: m_scheme_handlers
)
328 if (iter
->get_scheme_name () == scheme_name
)
333 std::unique_ptr
<diagnostic_output_format
>
334 output_factory::make_sink (const context
&ctxt
,
335 const char *unparsed_arg
,
336 const scheme_name_and_params
&parsed_arg
)
338 auto scheme_handler
= get_scheme_handler (parsed_arg
.m_scheme_name
);
341 auto_vec
<const char *> strings
;
342 for (auto &iter
: m_scheme_handlers
)
343 strings
.safe_push (iter
->get_scheme_name ().c_str ());
344 pp_markup::comma_separated_quoted_strings
e (strings
);
345 ctxt
.report_error ("%<%s%s%>:"
346 " unrecognized format %qs; known formats: %e",
347 ctxt
.m_option_name
, unparsed_arg
,
348 parsed_arg
.m_scheme_name
.c_str (), &e
);
352 return scheme_handler
->make_sink (ctxt
, unparsed_arg
, parsed_arg
);
355 /* class text_scheme_handler : public output_factory::scheme_handler. */
357 std::unique_ptr
<diagnostic_output_format
>
358 text_scheme_handler::make_sink (const context
&ctxt
,
359 const char *unparsed_arg
,
360 const scheme_name_and_params
&parsed_arg
) const
362 bool show_color
= pp_show_color (ctxt
.m_dc
.get_reference_printer ());
363 bool show_nesting
= false;
364 bool show_locations_in_nesting
= true;
365 bool show_levels
= false;
366 for (auto& iter
: parsed_arg
.m_kvs
)
368 const std::string
&key
= iter
.first
;
369 const std::string
&value
= iter
.second
;
372 if (!parse_bool_value (ctxt
, unparsed_arg
, key
, value
, show_color
))
376 if (key
== "experimental-nesting")
378 if (!parse_bool_value (ctxt
, unparsed_arg
, key
, value
,
383 if (key
== "experimental-nesting-show-locations")
385 if (!parse_bool_value (ctxt
, unparsed_arg
, key
, value
,
386 show_locations_in_nesting
))
390 if (key
== "experimental-nesting-show-levels")
392 if (!parse_bool_value (ctxt
, unparsed_arg
, key
, value
, show_levels
))
398 auto_vec
<const char *> known_keys
;
399 known_keys
.safe_push ("color");
400 known_keys
.safe_push ("experimental-nesting");
401 known_keys
.safe_push ("experimental-nesting-show-locations");
402 known_keys
.safe_push ("experimental-nesting-show-levels");
403 ctxt
.report_unknown_key (unparsed_arg
, key
, get_scheme_name (),
408 auto sink
= ::make_unique
<diagnostic_text_output_format
> (ctxt
.m_dc
);
409 sink
->set_show_nesting (show_nesting
);
410 sink
->set_show_locations_in_nesting (show_locations_in_nesting
);
411 sink
->set_show_nesting_levels (show_levels
);
415 diagnostic_output_file
416 context::open_output_file (label_text
&&filename
) const
418 FILE *outf
= fopen (filename
.get (), "w");
421 rich_location
richloc (m_location_mgr
, m_loc
);
422 m_dc
.emit_diagnostic_with_group
423 (DK_ERROR
, richloc
, nullptr, 0,
424 "unable to open %qs: %m", filename
.get ());
425 return diagnostic_output_file (nullptr, false, std::move (filename
));
427 return diagnostic_output_file (outf
, true, std::move (filename
));
430 /* class sarif_scheme_handler : public output_factory::scheme_handler. */
432 std::unique_ptr
<diagnostic_output_format
>
433 sarif_scheme_handler::make_sink (const context
&ctxt
,
434 const char *unparsed_arg
,
435 const scheme_name_and_params
&parsed_arg
) const
437 enum sarif_version version
= sarif_version::v2_1_0
;
439 for (auto& iter
: parsed_arg
.m_kvs
)
441 const std::string
&key
= iter
.first
;
442 const std::string
&value
= iter
.second
;
443 if (key
== "version")
445 static const std::array
<std::pair
<const char *, enum sarif_version
>,
446 (size_t)sarif_version::num_versions
> value_names
447 {{{"2.1", sarif_version::v2_1_0
},
448 {"2.2-prerelease", sarif_version::v2_2_prerelease_2024_08_08
}}};
450 if (!parse_enum_value
<enum sarif_version
> (ctxt
, unparsed_arg
,
459 filename
= label_text::take (xstrdup (value
.c_str ()));
464 auto_vec
<const char *> known_keys
;
465 known_keys
.safe_push ("file");
466 known_keys
.safe_push ("version");
467 ctxt
.report_unknown_key (unparsed_arg
, key
, get_scheme_name (),
472 diagnostic_output_file output_file
;
474 output_file
= ctxt
.open_output_file (std::move (filename
));
478 const char *basename
= (ctxt
.m_opts
.x_dump_base_name
479 ? ctxt
.m_opts
.x_dump_base_name
480 : ctxt
.m_opts
.x_main_input_basename
);
481 output_file
= diagnostic_output_format_open_sarif_file (ctxt
.m_dc
,
488 auto sink
= make_sarif_sink (ctxt
.m_dc
,
490 ctxt
.m_opts
.x_main_input_filename
,
492 std::move (output_file
));
496 } // namespace diagnostics_output_spec
500 handle_OPT_fdiagnostics_add_output_ (const gcc_options
&opts
,
501 diagnostic_context
&dc
,
506 gcc_assert (line_table
);
508 const char *const option_name
= "-fdiagnostics-add-output=";
509 gcc::diagnostics_output_spec::context
ctxt (opts
, dc
, line_table
, loc
,
511 auto result
= gcc::diagnostics_output_spec::parse (ctxt
, arg
);
515 gcc::diagnostics_output_spec::output_factory factory
;
516 auto sink
= factory
.make_sink (ctxt
, arg
, *result
);
520 dc
.add_sink (std::move (sink
));
524 handle_OPT_fdiagnostics_set_output_ (const gcc_options
&opts
,
525 diagnostic_context
&dc
,
530 gcc_assert (line_table
);
532 const char *const option_name
= "-fdiagnostics-set-output=";
533 gcc::diagnostics_output_spec::context
ctxt (opts
, dc
, line_table
, loc
,
535 auto result
= gcc::diagnostics_output_spec::parse (ctxt
, arg
);
539 gcc::diagnostics_output_spec::output_factory factory
;
540 auto sink
= factory
.make_sink (ctxt
, arg
, *result
);
544 dc
.set_output_format (std::move (sink
));
551 /* RAII class to temporarily override "progname" to the
552 string "PROGNAME". */
554 class auto_fix_progname
559 m_old_progname
= progname
;
560 progname
= "PROGNAME";
563 ~auto_fix_progname ()
565 progname
= m_old_progname
;
569 const char *m_old_progname
;
577 m_ctxt (m_opts
, m_dc
, line_table
, UNKNOWN_LOCATION
, "-fOPTION="),
578 m_fmt (m_dc
.get_output_format (0))
580 pp_buffer (m_fmt
.get_printer ())->m_flush_p
= false;
583 std::unique_ptr
<gcc::diagnostics_output_spec::scheme_name_and_params
>
584 parse (const char *unparsed_arg
)
586 return gcc::diagnostics_output_spec::parse (m_ctxt
, unparsed_arg
);
589 bool execution_failed_p () const
591 return m_dc
.execution_failed_p ();
595 get_diagnostic_text () const
597 return pp_formatted_text (m_fmt
.get_printer ());
601 const gcc_options m_opts
;
602 test_diagnostic_context m_dc
;
603 gcc::diagnostics_output_spec::context m_ctxt
;
604 diagnostic_output_format
&m_fmt
;
610 test_output_arg_parsing ()
612 auto_fix_quotes fix_quotes
;
613 auto_fix_progname fix_progname
;
615 /* Minimal correct example. */
618 auto result
= pt
.parse ("foo");
619 ASSERT_EQ (result
->m_scheme_name
, "foo");
620 ASSERT_EQ (result
->m_kvs
.size (), 0);
621 ASSERT_FALSE (pt
.execution_failed_p ());
624 /* Stray trailing colon with no key/value pairs. */
627 auto result
= pt
.parse ("foo:");
628 ASSERT_EQ (result
, nullptr);
629 ASSERT_TRUE (pt
.execution_failed_p ());
630 ASSERT_STREQ (pt
.get_diagnostic_text (),
631 "PROGNAME: error: `-fOPTION=foo:':"
632 " expected KEY=VALUE-style parameter for format `foo'"
637 /* No key before '='. */
640 auto result
= pt
.parse ("foo:=");
641 ASSERT_EQ (result
, nullptr);
642 ASSERT_TRUE (pt
.execution_failed_p ());
643 ASSERT_STREQ (pt
.get_diagnostic_text (),
644 "PROGNAME: error: `-fOPTION=foo:=':"
645 " expected KEY=VALUE-style parameter for format `foo'"
650 /* No value for key. */
653 auto result
= pt
.parse ("foo:key,");
654 ASSERT_EQ (result
, nullptr);
655 ASSERT_TRUE (pt
.execution_failed_p ());
656 ASSERT_STREQ (pt
.get_diagnostic_text (),
657 "PROGNAME: error: `-fOPTION=foo:key,':"
658 " expected KEY=VALUE-style parameter for format `foo'"
663 /* Correct example, with one key/value pair. */
666 auto result
= pt
.parse ("foo:key=value");
667 ASSERT_EQ (result
->m_scheme_name
, "foo");
668 ASSERT_EQ (result
->m_kvs
.size (), 1);
669 ASSERT_EQ (result
->m_kvs
[0].first
, "key");
670 ASSERT_EQ (result
->m_kvs
[0].second
, "value");
671 ASSERT_FALSE (pt
.execution_failed_p ());
674 /* Stray trailing comma. */
677 auto result
= pt
.parse ("foo:key=value,");
678 ASSERT_EQ (result
, nullptr);
679 ASSERT_TRUE (pt
.execution_failed_p ());
680 ASSERT_STREQ (pt
.get_diagnostic_text (),
681 "PROGNAME: error: `-fOPTION=foo:key=value,':"
682 " expected KEY=VALUE-style parameter for format `foo'"
687 /* Correct example, with two key/value pairs. */
690 auto result
= pt
.parse ("foo:color=red,shape=circle");
691 ASSERT_EQ (result
->m_scheme_name
, "foo");
692 ASSERT_EQ (result
->m_kvs
.size (), 2);
693 ASSERT_EQ (result
->m_kvs
[0].first
, "color");
694 ASSERT_EQ (result
->m_kvs
[0].second
, "red");
695 ASSERT_EQ (result
->m_kvs
[1].first
, "shape");
696 ASSERT_EQ (result
->m_kvs
[1].second
, "circle");
697 ASSERT_FALSE (pt
.execution_failed_p ());
701 /* Run all of the selftests within this file. */
704 opts_diagnostic_cc_tests ()
706 test_output_arg_parsing ();
709 } // namespace selftest
711 #endif /* #if CHECKING_P */