Line data Source code
1 : /* Support for the DSL of -fdiagnostics-add-output= and
2 : -fdiagnostics-set-output=.
3 : Copyright (C) 2024-2026 Free Software Foundation, Inc.
4 :
5 : This file is part of GCC.
6 :
7 : GCC is free software; you can redistribute it and/or modify it under
8 : the terms of the GNU General Public License as published by the Free
9 : Software Foundation; either version 3, or (at your option) any later
10 : version.
11 :
12 : GCC is distributed in the hope that it will be useful, but WITHOUT ANY
13 : WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 : FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
15 : for more details.
16 :
17 : You should have received a copy of the GNU General Public License
18 : along with GCC; see the file COPYING3. If not see
19 : <http://www.gnu.org/licenses/>. */
20 :
21 : /* This file implements the domain-specific language for the options
22 : -fdiagnostics-add-output= and -fdiagnostics-set-output=, and for
23 : the "diagnostic_manager_add_sink_from_spec" entrypoint to
24 : libgdiagnostics. */
25 :
26 : #include "config.h"
27 : #define INCLUDE_ARRAY
28 : #define INCLUDE_STRING
29 : #define INCLUDE_VECTOR
30 : #include "system.h"
31 : #include "coretypes.h"
32 : #include "version.h"
33 : #include "intl.h"
34 : #include "diagnostics/color.h"
35 : #include "diagnostics/sink.h"
36 : #include "diagnostics/html-sink.h"
37 : #include "diagnostics/text-sink.h"
38 : #include "diagnostics/sarif-sink.h"
39 : #include "selftest.h"
40 : #include "diagnostics/selftest-context.h"
41 : #include "pretty-print-markup.h"
42 : #include "diagnostics/output-spec.h"
43 :
44 : /* A namespace for handling the DSL of the arguments of
45 : -fdiagnostics-add-output= and -fdiagnostics-set-output=
46 : which look like:
47 : SCHEME[:KEY=VALUE(,KEY=VALUE)*]
48 : We call this an output spec. */
49 :
50 : namespace diagnostics {
51 : namespace output_spec {
52 :
53 : class scheme_handler;
54 :
55 : /* Decls. */
56 :
57 : /* A class for the result of the first stage of parsing an output spec,
58 : where values are represented as untyped strings.
59 : The scheme might not exist.
60 : The keys have not been validated against the scheme.
61 : The values have not been validated against their keys. */
62 :
63 148 : struct scheme_name_and_params
64 : {
65 : std::string m_scheme_name;
66 : std::vector<std::pair<std::string, std::string>> m_kvs;
67 : };
68 :
69 : /* Class for parsing the arguments of -fdiagnostics-add-output= and
70 : -fdiagnostics-set-output=, and making sink
71 : instances (or issuing errors). */
72 :
73 108 : class output_factory
74 : {
75 : public:
76 : output_factory (diagnostics::context &dc);
77 :
78 : std::unique_ptr<sink>
79 : make_sink (const context &ctxt,
80 : diagnostics::context &dc,
81 : const scheme_name_and_params &scheme_and_kvs);
82 :
83 : scheme_handler *get_scheme_handler (const std::string &scheme_name);
84 :
85 : private:
86 : std::vector<std::unique_ptr<scheme_handler>> m_scheme_handlers;
87 : };
88 :
89 : enum key_handler::result
90 49 : key_handler::parse_bool_value (const context &ctxt,
91 : const std::string &key,
92 : const std::string &value,
93 : bool &out) const
94 : {
95 49 : if (value == "yes")
96 : {
97 22 : out = true;
98 22 : return result::ok;
99 : }
100 27 : else if (value == "no")
101 : {
102 27 : out = false;
103 27 : return result::ok;
104 : }
105 : else
106 : {
107 0 : ctxt.report_error
108 0 : ("%<%s%s%>:"
109 : " unexpected value %qs for key %qs; expected %qs or %qs",
110 : ctxt.get_option_name (), ctxt.get_unparsed_spec (),
111 : value.c_str (),
112 : key.c_str (),
113 : "yes", "no");
114 0 : return result::malformed_value;
115 : }
116 : }
117 :
118 : template <typename EnumType, size_t NumValues>
119 : key_handler::result
120 3 : key_handler::parse_enum_value (const context &ctxt,
121 : const std::string &key,
122 : const std::string &value,
123 : const std::array<std::pair<const char *,
124 : EnumType>,
125 : NumValues> &value_names,
126 : EnumType &out) const
127 : {
128 5 : for (auto &iter : value_names)
129 5 : if (value == iter.first)
130 : {
131 3 : out = iter.second;
132 3 : return result::ok;
133 : }
134 :
135 0 : auto_vec<const char *> known_values;
136 0 : for (auto iter : value_names)
137 0 : known_values.safe_push (iter.first);
138 0 : pp_markup::comma_separated_quoted_strings e (known_values);
139 : ctxt.report_error
140 0 : ("%<%s%s%>:"
141 : " unexpected value %qs for key %qs; known values: %e",
142 : ctxt.get_option_name (), ctxt.get_unparsed_spec (),
143 : value.c_str (),
144 : key.c_str (),
145 : &e);
146 0 : return result::malformed_value;
147 0 : }
148 :
149 : class text_scheme_handler : public scheme_handler
150 : {
151 : public:
152 54 : text_scheme_handler (diagnostics::context &dc)
153 54 : : scheme_handler ("text"),
154 54 : m_show_color (pp_show_color (dc.get_reference_printer ())),
155 54 : m_show_nesting (true),
156 54 : m_show_locations_in_nesting (true),
157 108 : m_show_levels (false)
158 : {
159 54 : }
160 :
161 : std::unique_ptr<sink>
162 : make_sink (const context &ctxt,
163 : diagnostics::context &dc) final override;
164 :
165 : enum result
166 : maybe_handle_kv (const context &ctxt,
167 : const std::string &key,
168 : const std::string &value) final override;
169 :
170 : void
171 : get_keys (auto_vec<const char *> &out) const final override;
172 :
173 : private:
174 : bool m_show_color;
175 : bool m_show_nesting;
176 : bool m_show_locations_in_nesting;
177 : bool m_show_levels;
178 : };
179 :
180 : class sarif_scheme_handler : public scheme_handler
181 : {
182 : public:
183 54 : sarif_scheme_handler ()
184 54 : : scheme_handler ("sarif"),
185 108 : m_serialization_kind (sarif_serialization_kind::json)
186 : {
187 54 : }
188 :
189 : std::unique_ptr<sink>
190 : make_sink (const context &ctxt,
191 : diagnostics::context &dc) final override;
192 :
193 : enum result
194 : maybe_handle_kv (const context &ctxt,
195 : const std::string &key,
196 : const std::string &value) final override;
197 :
198 : void
199 : get_keys (auto_vec<const char *> &out) const final override;
200 :
201 : private:
202 : static std::unique_ptr<sarif_serialization_format>
203 : make_sarif_serialization_object (enum sarif_serialization_kind);
204 :
205 : label_text m_filename;
206 : enum sarif_serialization_kind m_serialization_kind;
207 : sarif_generation_options m_generation_opts;
208 : };
209 :
210 : class html_scheme_handler : public scheme_handler
211 : {
212 : public:
213 54 : html_scheme_handler () : scheme_handler ("experimental-html") {}
214 :
215 : std::unique_ptr<sink>
216 : make_sink (const context &ctxt,
217 : diagnostics::context &dc) final override;
218 :
219 : enum result
220 : maybe_handle_kv (const context &ctxt,
221 : const std::string &key,
222 : const std::string &value) final override;
223 :
224 : void
225 : get_keys (auto_vec<const char *> &out) const final override;
226 :
227 : private:
228 : label_text m_filename;
229 : html_generation_options m_html_gen_opts;
230 : };
231 :
232 : /* struct context. */
233 :
234 : void
235 16 : context::report_error (const char *gmsgid, ...) const
236 : {
237 16 : va_list ap;
238 16 : va_start (ap, gmsgid);
239 16 : report_error_va (gmsgid, &ap);
240 16 : va_end (ap);
241 16 : }
242 :
243 : void
244 0 : context::report_unknown_key (const std::string &key,
245 : const scheme_handler &scheme) const
246 : {
247 0 : auto_vec<const char *> scheme_key_vec;
248 0 : scheme.get_keys (scheme_key_vec);
249 :
250 0 : pp_markup::comma_separated_quoted_strings e_scheme_keys (scheme_key_vec);
251 :
252 0 : const char *scheme_name = scheme.get_scheme_name ().c_str ();
253 :
254 0 : if (m_client_keys)
255 : {
256 0 : auto_vec<const char *> client_key_vec;
257 0 : m_client_keys->get_keys (client_key_vec);
258 0 : if (!client_key_vec.is_empty ())
259 : {
260 0 : pp_markup::comma_separated_quoted_strings e_client_keys
261 0 : (client_key_vec);
262 0 : report_error
263 0 : ("%<%s%s%>:"
264 : " unknown key %qs for output scheme %qs;"
265 : " scheme keys: %e; client keys: %e",
266 : get_option_name (), get_unparsed_spec (),
267 : key.c_str (), scheme_name,
268 : &e_scheme_keys, &e_client_keys);
269 0 : }
270 0 : }
271 :
272 0 : report_error
273 0 : ("%<%s%s%>:"
274 : " unknown key %qs for output scheme %qs; scheme keys: %e",
275 : get_option_name (), get_unparsed_spec (),
276 : key.c_str (), scheme_name, &e_scheme_keys);
277 0 : }
278 :
279 : void
280 0 : context::report_missing_key (const std::string &key,
281 : const std::string &scheme_name,
282 : const char *metavar) const
283 : {
284 0 : report_error
285 0 : ("%<%s%s%>:"
286 : " missing required key %qs for format %qs;"
287 : " try %<%s%s:%s=%s%>",
288 : get_option_name (), get_unparsed_spec (),
289 : key.c_str (), scheme_name.c_str (),
290 : get_option_name (), scheme_name.c_str (), key.c_str (), metavar);
291 0 : }
292 :
293 : output_file
294 2 : context::open_output_file (label_text &&filename) const
295 : {
296 2 : FILE *outf = fopen (filename.get (), "w");
297 2 : if (!outf)
298 : {
299 0 : report_error ("unable to open %qs: %m", filename.get ());
300 0 : return output_file (nullptr, false, std::move (filename));
301 : }
302 2 : return output_file (outf, true, std::move (filename));
303 : }
304 :
305 : static std::unique_ptr<scheme_name_and_params>
306 82 : parse (const context &ctxt)
307 : {
308 82 : scheme_name_and_params result;
309 82 : const char *const unparsed_spec = ctxt.get_unparsed_spec ();
310 82 : if (const char *const colon = strchr (unparsed_spec, ':'))
311 : {
312 60 : result.m_scheme_name = std::string (unparsed_spec, colon - unparsed_spec);
313 : /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc .*/
314 60 : const char *iter = colon + 1;
315 60 : const char *last_separator = ":";
316 130 : while (iter)
317 : {
318 : /* Look for a non-empty key string followed by '='. */
319 86 : const char *eq = strchr (iter, '=');
320 86 : if (eq == nullptr || eq == iter)
321 : {
322 : /* Missing '='. */
323 16 : ctxt.report_error
324 16 : ("%<%s%s%>:"
325 : " expected KEY=VALUE-style parameter for format %qs"
326 : " after %qs;"
327 : " got %qs",
328 : ctxt.get_option_name (), ctxt.get_unparsed_spec (),
329 : result.m_scheme_name.c_str (),
330 : last_separator,
331 : iter);
332 16 : return nullptr;
333 : }
334 70 : std::string key = std::string (iter, eq - iter);
335 70 : std::string value;
336 70 : const char *comma = strchr (iter, ',');
337 70 : if (comma)
338 : {
339 26 : value = std::string (eq + 1, comma - (eq + 1));
340 26 : iter = comma + 1;
341 26 : last_separator = ",";
342 : }
343 : else
344 : {
345 44 : value = std::string (eq + 1);
346 44 : iter = nullptr;
347 : }
348 210 : result.m_kvs.push_back ({std::move (key), std::move (value)});
349 70 : }
350 : }
351 : else
352 22 : result.m_scheme_name = unparsed_spec;
353 66 : return std::make_unique<scheme_name_and_params> (std::move (result));
354 82 : }
355 :
356 : std::unique_ptr<sink>
357 54 : context::parse_and_make_sink (diagnostics::context &dc)
358 : {
359 54 : auto parsed_arg = parse (*this);
360 54 : if (!parsed_arg)
361 0 : return nullptr;
362 :
363 54 : output_factory factory (dc);
364 54 : return factory.make_sink (*this, dc, *parsed_arg);
365 54 : }
366 :
367 : /* class scheme_handler. */
368 :
369 : /* class output_factory. */
370 :
371 54 : output_factory::output_factory (diagnostics::context &dc)
372 : {
373 54 : m_scheme_handlers.push_back (std::make_unique<text_scheme_handler> (dc));
374 54 : m_scheme_handlers.push_back (std::make_unique<sarif_scheme_handler> ());
375 54 : m_scheme_handlers.push_back (std::make_unique<html_scheme_handler> ());
376 54 : }
377 :
378 : scheme_handler *
379 54 : output_factory::get_scheme_handler (const std::string &scheme_name)
380 : {
381 106 : for (auto &iter : m_scheme_handlers)
382 106 : if (iter->get_scheme_name () == scheme_name)
383 54 : return iter.get ();
384 : return nullptr;
385 : }
386 :
387 : std::unique_ptr<sink>
388 54 : output_factory::make_sink (const context &ctxt,
389 : diagnostics::context &dc,
390 : const scheme_name_and_params &scheme_and_kvs)
391 : {
392 54 : auto scheme_handler = get_scheme_handler (scheme_and_kvs.m_scheme_name);
393 54 : if (!scheme_handler)
394 : {
395 0 : auto_vec<const char *> strings;
396 0 : for (auto &iter : m_scheme_handlers)
397 0 : strings.safe_push (iter->get_scheme_name ().c_str ());
398 0 : pp_markup::comma_separated_quoted_strings e (strings);
399 0 : ctxt.report_error ("%<%s%s%>:"
400 : " unrecognized format %qs; known formats: %e",
401 : ctxt.get_option_name (), ctxt.get_unparsed_spec (),
402 : scheme_and_kvs.m_scheme_name.c_str (), &e);
403 0 : return nullptr;
404 0 : }
405 :
406 : /* Parse key/value pairs. */
407 108 : for (auto& iter : scheme_and_kvs.m_kvs)
408 : {
409 54 : const std::string &key = iter.first;
410 54 : const std::string &value = iter.second;
411 54 : if (!ctxt.handle_kv (key, value, *scheme_handler))
412 0 : return nullptr;
413 : }
414 :
415 54 : return scheme_handler->make_sink (ctxt, dc);
416 : }
417 :
418 : bool
419 54 : context::handle_kv (const std::string &key,
420 : const std::string &value,
421 : scheme_handler &scheme) const
422 : {
423 54 : auto result = scheme.maybe_handle_kv (*this, key, value);
424 54 : switch (result)
425 : {
426 0 : default: gcc_unreachable ();
427 : case key_handler::result::ok:
428 : return true;
429 : case key_handler::result::malformed_value:
430 : return false;
431 10 : case key_handler::result::unrecognized:
432 : /* Key recognized by the scheme; try the client keys. */
433 10 : if (m_client_keys)
434 : {
435 10 : result = m_client_keys->maybe_handle_kv (*this, key, value);
436 10 : switch (result)
437 : {
438 0 : default: gcc_unreachable ();
439 : case key_handler::result::ok:
440 : return true;
441 : case key_handler::result::malformed_value:
442 : return false;
443 : case key_handler::result::unrecognized:
444 : break;
445 : }
446 : }
447 0 : report_unknown_key (key, scheme);
448 0 : return false;
449 : }
450 : }
451 :
452 : /* class text_scheme_handler : public scheme_handler. */
453 :
454 : std::unique_ptr<sink>
455 16 : text_scheme_handler::make_sink (const context &,
456 : diagnostics::context &dc)
457 : {
458 16 : auto sink = std::make_unique<diagnostics::text_sink> (dc);
459 16 : sink->set_show_nesting (m_show_nesting);
460 16 : sink->set_show_locations_in_nesting (m_show_locations_in_nesting);
461 16 : sink->set_show_nesting_levels (m_show_levels);
462 16 : pp_show_color (sink->get_printer ()) = m_show_color;
463 16 : return sink;
464 16 : }
465 :
466 : enum key_handler::result
467 30 : text_scheme_handler::maybe_handle_kv (const context &ctxt,
468 : const std::string &key,
469 : const std::string &value)
470 : {
471 30 : if (key == "color")
472 0 : return parse_bool_value (ctxt, key, value, m_show_color);
473 30 : if (key == "show-nesting")
474 12 : return parse_bool_value (ctxt, key, value, m_show_nesting);
475 18 : if (key == "show-nesting-locations")
476 9 : return parse_bool_value (ctxt, key, value,
477 9 : m_show_locations_in_nesting);
478 9 : if (key == "show-nesting-levels")
479 1 : return parse_bool_value (ctxt, key, value, m_show_levels);
480 :
481 : return result::unrecognized;
482 : }
483 :
484 : void
485 0 : text_scheme_handler::get_keys (auto_vec<const char *> &out) const
486 : {
487 0 : out.safe_push ("color");
488 0 : out.safe_push ("show-nesting");
489 0 : out.safe_push ("show-nesting-locations");
490 0 : out.safe_push ("show-nesting-levels");
491 0 : }
492 :
493 : /* class sarif_scheme_handler : public scheme_handler. */
494 :
495 : std::unique_ptr<sink>
496 24 : sarif_scheme_handler::
497 : make_sink (const context &ctxt,
498 : diagnostics::context &dc)
499 : {
500 24 : output_file output_file_;
501 24 : if (m_filename.get ())
502 2 : output_file_ = ctxt.open_output_file (std::move (m_filename));
503 : else
504 : // Default filename
505 : {
506 22 : const char *basename = ctxt.get_base_filename ();
507 22 : if (!basename)
508 : {
509 0 : ctxt.report_missing_key ("file",
510 : get_scheme_name (),
511 : "FILENAME");
512 0 : return nullptr;
513 : }
514 22 : output_file_
515 44 : = open_sarif_output_file (dc,
516 : ctxt.get_affected_location_mgr (),
517 : basename,
518 22 : m_serialization_kind);
519 : }
520 24 : if (!output_file_)
521 0 : return nullptr;
522 :
523 24 : auto serialization_obj
524 24 : = make_sarif_serialization_object (m_serialization_kind);
525 :
526 24 : auto sink = make_sarif_sink (dc,
527 24 : *ctxt.get_affected_location_mgr (),
528 : std::move (serialization_obj),
529 24 : m_generation_opts,
530 24 : std::move (output_file_));
531 :
532 24 : return sink;
533 24 : }
534 :
535 : enum key_handler::result
536 8 : sarif_scheme_handler::maybe_handle_kv (const context &ctxt,
537 : const std::string &key,
538 : const std::string &value)
539 : {
540 8 : if (key == "file")
541 : {
542 2 : m_filename = label_text::take (xstrdup (value.c_str ()));
543 2 : return result::ok;
544 : }
545 6 : if (key == "serialization")
546 : {
547 0 : static const std::array<std::pair<const char *, enum sarif_serialization_kind>,
548 : (size_t)sarif_serialization_kind::num_values> value_names
549 : {{{"json", sarif_serialization_kind::json}}};
550 0 : return parse_enum_value<enum sarif_serialization_kind>
551 0 : (ctxt,
552 : key, value,
553 : value_names,
554 0 : m_serialization_kind);
555 : }
556 6 : if (key == "version")
557 : {
558 3 : static const std::array<std::pair<const char *, enum sarif_version>,
559 : (size_t)sarif_version::num_versions> value_names
560 : {{{"2.1", sarif_version::v2_1_0},
561 : {"2.2-prerelease", sarif_version::v2_2_prerelease_2024_08_08}}};
562 3 : return parse_enum_value<enum sarif_version>
563 3 : (ctxt,
564 : key, value,
565 : value_names,
566 3 : m_generation_opts.m_version);
567 : }
568 3 : if (key == "state-graphs")
569 2 : return parse_bool_value (ctxt, key, value,
570 2 : m_generation_opts.m_state_graph);
571 :
572 : return result::unrecognized;
573 : }
574 :
575 : void
576 0 : sarif_scheme_handler::get_keys (auto_vec<const char *> &out) const
577 : {
578 0 : out.safe_push ("file");
579 0 : out.safe_push ("serialization");
580 0 : out.safe_push ("state-graphs");
581 0 : out.safe_push ("version");
582 0 : }
583 :
584 : std::unique_ptr<sarif_serialization_format>
585 24 : sarif_scheme_handler::
586 : make_sarif_serialization_object (enum sarif_serialization_kind kind)
587 : {
588 24 : switch (kind)
589 : {
590 0 : default:
591 0 : gcc_unreachable ();
592 24 : case sarif_serialization_kind::json:
593 24 : return std::make_unique<sarif_serialization_format_json> (true);
594 : break;
595 : }
596 : }
597 :
598 : /* class html_scheme_handler : public scheme_handler. */
599 :
600 : std::unique_ptr<sink>
601 14 : html_scheme_handler::
602 : make_sink (const context &ctxt,
603 : diagnostics::context &dc)
604 : {
605 14 : output_file output_file_;
606 14 : if (m_filename.get ())
607 0 : output_file_ = ctxt.open_output_file (std::move (m_filename));
608 : else
609 : // Default filename
610 : {
611 14 : const char *basename = ctxt.get_base_filename ();
612 14 : if (!basename)
613 : {
614 0 : ctxt.report_missing_key ("file",
615 : get_scheme_name (),
616 : "FILENAME");
617 0 : return nullptr;
618 : }
619 14 : output_file_
620 : = open_html_output_file
621 28 : (dc,
622 : ctxt.get_affected_location_mgr (),
623 14 : basename);
624 : }
625 14 : if (!output_file_)
626 0 : return nullptr;
627 :
628 28 : auto sink = make_html_sink (dc,
629 14 : *ctxt.get_affected_location_mgr (),
630 14 : m_html_gen_opts,
631 14 : std::move (output_file_));
632 14 : return sink;
633 14 : }
634 :
635 : enum key_handler::result
636 16 : html_scheme_handler::maybe_handle_kv (const context &ctxt,
637 : const std::string &key,
638 : const std::string &value)
639 : {
640 16 : if (key == "css")
641 0 : return parse_bool_value (ctxt, key, value, m_html_gen_opts.m_css);
642 16 : if (key == "file")
643 : {
644 0 : m_filename = label_text::take (xstrdup (value.c_str ()));
645 0 : return result::ok;
646 : }
647 16 : if (key == "javascript")
648 14 : return parse_bool_value (ctxt, key, value,
649 14 : m_html_gen_opts.m_javascript);
650 2 : if (key == "show-state-diagrams")
651 1 : return parse_bool_value (ctxt, key, value,
652 1 : m_html_gen_opts.m_show_state_diagrams);
653 1 : if (key == "show-graph-dot-src")
654 0 : return parse_bool_value (ctxt, key, value,
655 0 : m_html_gen_opts.m_show_graph_dot_src);
656 1 : if (key == "show-graph-sarif")
657 0 : return parse_bool_value (ctxt, key, value,
658 0 : m_html_gen_opts.m_show_graph_sarif);
659 : return result::unrecognized;
660 : }
661 :
662 : void
663 0 : html_scheme_handler::get_keys (auto_vec<const char *> &out) const
664 : {
665 0 : out.safe_push ("css");
666 0 : out.safe_push ("file");
667 0 : out.safe_push ("javascript");
668 0 : out.safe_push ("show-state-diagrams");
669 0 : out.safe_push ("show-graph-dot-src");
670 0 : out.safe_push ("show-graph-sarif");
671 0 : }
672 :
673 : } // namespace output_spec
674 :
675 : #if CHECKING_P
676 :
677 : namespace selftest {
678 :
679 : using auto_fix_quotes = ::selftest::auto_fix_quotes;
680 :
681 : /* RAII class to temporarily override "progname" to the
682 : string "PROGNAME". */
683 :
684 : class auto_fix_progname
685 : {
686 : public:
687 4 : auto_fix_progname ()
688 4 : {
689 4 : m_old_progname = progname;
690 4 : progname = "PROGNAME";
691 : }
692 :
693 4 : ~auto_fix_progname ()
694 : {
695 4 : progname = m_old_progname;
696 4 : }
697 :
698 : private:
699 : const char *m_old_progname;
700 : };
701 :
702 64 : struct parser_test
703 : {
704 32 : class test_spec_context : public diagnostics::output_spec::dc_spec_context
705 : {
706 : public:
707 32 : test_spec_context (const char *option_name,
708 : const char *unparsed_spec,
709 : diagnostics::output_spec::key_handler *client_keys,
710 : line_maps *location_mgr,
711 : diagnostics::context &dc,
712 : location_t loc)
713 : : dc_spec_context (option_name,
714 : unparsed_spec,
715 : client_keys,
716 : location_mgr,
717 : dc,
718 : location_mgr,
719 32 : loc)
720 : {
721 : }
722 :
723 : const char *
724 0 : get_base_filename () const final override
725 : {
726 0 : return "BASE_FILENAME";
727 : }
728 : };
729 :
730 32 : parser_test (const char *unparsed_spec,
731 : diagnostics::output_spec::key_handler *client_keys = nullptr)
732 32 : : m_dc (),
733 32 : m_ctxt ("-fOPTION=",
734 : unparsed_spec,
735 : client_keys,
736 : line_table,
737 : m_dc,
738 : UNKNOWN_LOCATION),
739 32 : m_fmt (m_dc.get_sink (0))
740 : {
741 32 : pp_buffer (m_fmt.get_printer ())->m_flush_p = false;
742 32 : }
743 :
744 : std::unique_ptr<diagnostics::output_spec::scheme_name_and_params>
745 28 : parse ()
746 : {
747 28 : return diagnostics::output_spec::parse (m_ctxt);
748 : }
749 :
750 : std::unique_ptr<diagnostics::sink>
751 4 : parse_and_make_sink ()
752 : {
753 4 : return m_ctxt.parse_and_make_sink (m_dc);
754 : }
755 :
756 28 : bool execution_failed_p () const
757 : {
758 28 : return m_dc.execution_failed_p ();
759 : }
760 :
761 : const char *
762 16 : get_diagnostic_text () const
763 : {
764 16 : return pp_formatted_text (m_fmt.get_printer ());
765 : }
766 :
767 : private:
768 : diagnostics::selftest::test_context m_dc;
769 : test_spec_context m_ctxt;
770 : diagnostics::sink &m_fmt;
771 : };
772 :
773 : /* Selftests. */
774 :
775 : static void
776 4 : test_output_arg_parsing ()
777 : {
778 : /* Minimal correct example. */
779 4 : {
780 4 : parser_test pt ("foo");
781 4 : auto result = pt.parse ();
782 4 : ASSERT_EQ (result->m_scheme_name, "foo");
783 4 : ASSERT_EQ (result->m_kvs.size (), 0);
784 4 : ASSERT_FALSE (pt.execution_failed_p ());
785 4 : }
786 :
787 : /* Stray trailing colon with no key/value pairs. */
788 4 : {
789 4 : parser_test pt ("foo:");
790 4 : auto result = pt.parse ();
791 4 : ASSERT_EQ (result, nullptr);
792 4 : ASSERT_TRUE (pt.execution_failed_p ());
793 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
794 : "PROGNAME: error: `-fOPTION=foo:':"
795 : " expected KEY=VALUE-style parameter for format `foo'"
796 : " after `:';"
797 : " got `'\n");
798 4 : }
799 :
800 : /* No key before '='. */
801 4 : {
802 4 : parser_test pt ("foo:=");
803 4 : auto result = pt.parse ();
804 4 : ASSERT_EQ (result, nullptr);
805 4 : ASSERT_TRUE (pt.execution_failed_p ());
806 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
807 : "PROGNAME: error: `-fOPTION=foo:=':"
808 : " expected KEY=VALUE-style parameter for format `foo'"
809 : " after `:';"
810 : " got `='\n");
811 4 : }
812 :
813 : /* No value for key. */
814 4 : {
815 4 : parser_test pt ("foo:key,");
816 4 : auto result = pt.parse ();
817 4 : ASSERT_EQ (result, nullptr);
818 4 : ASSERT_TRUE (pt.execution_failed_p ());
819 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
820 : "PROGNAME: error: `-fOPTION=foo:key,':"
821 : " expected KEY=VALUE-style parameter for format `foo'"
822 : " after `:';"
823 : " got `key,'\n");
824 4 : }
825 :
826 : /* Correct example, with one key/value pair. */
827 4 : {
828 4 : parser_test pt ("foo:key=value");
829 4 : auto result = pt.parse ();
830 4 : ASSERT_EQ (result->m_scheme_name, "foo");
831 4 : ASSERT_EQ (result->m_kvs.size (), 1);
832 4 : ASSERT_EQ (result->m_kvs[0].first, "key");
833 4 : ASSERT_EQ (result->m_kvs[0].second, "value");
834 4 : ASSERT_FALSE (pt.execution_failed_p ());
835 4 : }
836 :
837 : /* Stray trailing comma. */
838 4 : {
839 4 : parser_test pt ("foo:key=value,");
840 4 : auto result = pt.parse ();
841 4 : ASSERT_EQ (result, nullptr);
842 4 : ASSERT_TRUE (pt.execution_failed_p ());
843 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
844 : "PROGNAME: error: `-fOPTION=foo:key=value,':"
845 : " expected KEY=VALUE-style parameter for format `foo'"
846 : " after `,';"
847 : " got `'\n");
848 4 : }
849 :
850 : /* Correct example, with two key/value pairs. */
851 4 : {
852 4 : parser_test pt ("foo:color=red,shape=circle");
853 4 : auto result = pt.parse ();
854 4 : ASSERT_EQ (result->m_scheme_name, "foo");
855 4 : ASSERT_EQ (result->m_kvs.size (), 2);
856 4 : ASSERT_EQ (result->m_kvs[0].first, "color");
857 4 : ASSERT_EQ (result->m_kvs[0].second, "red");
858 4 : ASSERT_EQ (result->m_kvs[1].first, "shape");
859 4 : ASSERT_EQ (result->m_kvs[1].second, "circle");
860 4 : ASSERT_FALSE (pt.execution_failed_p ());
861 4 : }
862 4 : }
863 :
864 : class test_key_handler : public diagnostics::output_spec::key_handler
865 : {
866 : public:
867 4 : test_key_handler ()
868 4 : : m_verbose (false),
869 4 : m_strict (false)
870 : {
871 : }
872 :
873 : enum result
874 8 : maybe_handle_kv (const diagnostics::output_spec::context &ctxt,
875 : const std::string &key,
876 : const std::string &value) final override
877 : {
878 8 : if (key == "verbose")
879 4 : return parse_bool_value (ctxt, key, value, m_verbose);
880 4 : if (key == "strict")
881 4 : return parse_bool_value (ctxt, key, value, m_strict);
882 : return result::unrecognized;
883 : }
884 :
885 : void
886 0 : get_keys (auto_vec<const char *> &out_known_keys) const final override
887 : {
888 0 : out_known_keys.safe_push ("verbose");
889 0 : out_known_keys.safe_push ("strict");
890 0 : }
891 :
892 : bool m_verbose;
893 : bool m_strict;
894 : };
895 :
896 : static void
897 4 : test_client_arg_parsing ()
898 : {
899 4 : test_key_handler client_keys;
900 4 : parser_test pt ("text:verbose=yes,strict=no", &client_keys);
901 4 : auto result = pt.parse_and_make_sink ();
902 4 : ASSERT_TRUE (result.get ());
903 4 : ASSERT_TRUE (client_keys.m_verbose);
904 4 : ASSERT_FALSE (client_keys.m_strict);
905 4 : }
906 :
907 : /* Run all of the selftests within this file. */
908 :
909 : void
910 4 : output_spec_cc_tests ()
911 : {
912 4 : auto_fix_quotes fix_quotes;
913 4 : auto_fix_progname fix_progname;
914 :
915 4 : test_output_arg_parsing ();
916 4 : test_client_arg_parsing ();
917 4 : }
918 :
919 : } // namespace diagnostics::selftest
920 :
921 : #endif /* #if CHECKING_P */
922 :
923 : } // namespace diagnostics
|