Branch data Line data Source code
1 : : /* Support for the DSL of -fdiagnostics-add-output= and
2 : : -fdiagnostics-set-output=.
3 : : Copyright (C) 2024-2025 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 : 122 : 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 : 82 : 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 : 46 : key_handler::parse_bool_value (const context &ctxt,
91 : : const std::string &key,
92 : : const std::string &value,
93 : : bool &out) const
94 : : {
95 : 46 : if (value == "yes")
96 : : {
97 : 20 : out = true;
98 : 20 : return result::ok;
99 : : }
100 : 26 : else if (value == "no")
101 : : {
102 : 26 : out = false;
103 : 26 : 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 : 41 : text_scheme_handler (diagnostics::context &dc)
153 : 41 : : scheme_handler ("text"),
154 : 41 : m_show_color (pp_show_color (dc.get_reference_printer ())),
155 : 41 : m_show_nesting (true),
156 : 41 : m_show_locations_in_nesting (true),
157 : 82 : m_show_levels (false)
158 : : {
159 : 41 : }
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 : 41 : sarif_scheme_handler ()
184 : 41 : : scheme_handler ("sarif"),
185 : 82 : m_serialization_kind (sarif_serialization_kind::json)
186 : : {
187 : 41 : }
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 : 41 : 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 : 69 : parse (const context &ctxt)
307 : : {
308 : 69 : scheme_name_and_params result;
309 : 69 : const char *const unparsed_spec = ctxt.get_unparsed_spec ();
310 : 69 : if (const char *const colon = strchr (unparsed_spec, ':'))
311 : : {
312 : 58 : result.m_scheme_name = std::string (unparsed_spec, colon - unparsed_spec);
313 : : /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc .*/
314 : 58 : const char *iter = colon + 1;
315 : 58 : const char *last_separator = ":";
316 : 125 : while (iter)
317 : : {
318 : : /* Look for a non-empty key string followed by '='. */
319 : 83 : const char *eq = strchr (iter, '=');
320 : 83 : 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 : 67 : std::string key = std::string (iter, eq - iter);
335 : 67 : std::string value;
336 : 67 : const char *comma = strchr (iter, ',');
337 : 67 : if (comma)
338 : : {
339 : 25 : value = std::string (eq + 1, comma - (eq + 1));
340 : 25 : iter = comma + 1;
341 : 25 : last_separator = ",";
342 : : }
343 : : else
344 : : {
345 : 42 : value = std::string (eq + 1);
346 : 42 : iter = nullptr;
347 : : }
348 : 201 : result.m_kvs.push_back ({std::move (key), std::move (value)});
349 : 67 : }
350 : : }
351 : : else
352 : 11 : result.m_scheme_name = unparsed_spec;
353 : 53 : return std::make_unique<scheme_name_and_params> (std::move (result));
354 : 69 : }
355 : :
356 : : std::unique_ptr<sink>
357 : 41 : context::parse_and_make_sink (diagnostics::context &dc)
358 : : {
359 : 41 : auto parsed_arg = parse (*this);
360 : 41 : if (!parsed_arg)
361 : 0 : return nullptr;
362 : :
363 : 41 : output_factory factory (dc);
364 : 41 : return factory.make_sink (*this, dc, *parsed_arg);
365 : 41 : }
366 : :
367 : : /* class scheme_handler. */
368 : :
369 : : /* class output_factory. */
370 : :
371 : 41 : output_factory::output_factory (diagnostics::context &dc)
372 : : {
373 : 41 : m_scheme_handlers.push_back (std::make_unique<text_scheme_handler> (dc));
374 : 41 : m_scheme_handlers.push_back (std::make_unique<sarif_scheme_handler> ());
375 : 41 : m_scheme_handlers.push_back (std::make_unique<html_scheme_handler> ());
376 : 41 : }
377 : :
378 : : scheme_handler *
379 : 41 : output_factory::get_scheme_handler (const std::string &scheme_name)
380 : : {
381 : 79 : for (auto &iter : m_scheme_handlers)
382 : 79 : if (iter->get_scheme_name () == scheme_name)
383 : 41 : return iter.get ();
384 : : return nullptr;
385 : : }
386 : :
387 : : std::unique_ptr<sink>
388 : 41 : output_factory::make_sink (const context &ctxt,
389 : : diagnostics::context &dc,
390 : : const scheme_name_and_params &scheme_and_kvs)
391 : : {
392 : 41 : auto scheme_handler = get_scheme_handler (scheme_and_kvs.m_scheme_name);
393 : 41 : 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 : 92 : for (auto& iter : scheme_and_kvs.m_kvs)
408 : : {
409 : 51 : const std::string &key = iter.first;
410 : 51 : const std::string &value = iter.second;
411 : 51 : if (!ctxt.handle_kv (key, value, *scheme_handler))
412 : 0 : return nullptr;
413 : : }
414 : :
415 : 41 : return scheme_handler->make_sink (ctxt, dc);
416 : : }
417 : :
418 : : bool
419 : 51 : context::handle_kv (const std::string &key,
420 : : const std::string &value,
421 : : scheme_handler &scheme) const
422 : : {
423 : 51 : auto result = scheme.maybe_handle_kv (*this, key, value);
424 : 51 : 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 : 8 : case key_handler::result::unrecognized:
432 : : /* Key recognized by the scheme; try the client keys. */
433 : 8 : if (m_client_keys)
434 : : {
435 : 8 : result = m_client_keys->maybe_handle_kv (*this, key, value);
436 : 8 : 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 : 12 : sarif_scheme_handler::
497 : : make_sink (const context &ctxt,
498 : : diagnostics::context &dc)
499 : : {
500 : 12 : output_file output_file_;
501 : 12 : if (m_filename.get ())
502 : 2 : output_file_ = ctxt.open_output_file (std::move (m_filename));
503 : : else
504 : : // Default filename
505 : : {
506 : 10 : const char *basename = ctxt.get_base_filename ();
507 : 10 : if (!basename)
508 : : {
509 : 0 : ctxt.report_missing_key ("file",
510 : : get_scheme_name (),
511 : : "FILENAME");
512 : 0 : return nullptr;
513 : : }
514 : 10 : output_file_
515 : 20 : = open_sarif_output_file (dc,
516 : : ctxt.get_affected_location_mgr (),
517 : : basename,
518 : 10 : m_serialization_kind);
519 : : }
520 : 12 : if (!output_file_)
521 : 0 : return nullptr;
522 : :
523 : 12 : auto serialization_obj
524 : 12 : = make_sarif_serialization_object (m_serialization_kind);
525 : :
526 : 12 : auto sink = make_sarif_sink (dc,
527 : 12 : *ctxt.get_affected_location_mgr (),
528 : : std::move (serialization_obj),
529 : 12 : m_generation_opts,
530 : 12 : std::move (output_file_));
531 : :
532 : 12 : return sink;
533 : 12 : }
534 : :
535 : : enum key_handler::result
536 : 7 : sarif_scheme_handler::maybe_handle_kv (const context &ctxt,
537 : : const std::string &key,
538 : : const std::string &value)
539 : : {
540 : 7 : if (key == "file")
541 : : {
542 : 2 : m_filename = label_text::take (xstrdup (value.c_str ()));
543 : 2 : return result::ok;
544 : : }
545 : 5 : 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 : 5 : 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 : 2 : 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 : 12 : sarif_scheme_handler::
586 : : make_sarif_serialization_object (enum sarif_serialization_kind kind)
587 : : {
588 : 12 : switch (kind)
589 : : {
590 : 0 : default:
591 : 0 : gcc_unreachable ();
592 : 12 : case sarif_serialization_kind::json:
593 : 12 : 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 : 13 : html_scheme_handler::
602 : : make_sink (const context &ctxt,
603 : : diagnostics::context &dc)
604 : : {
605 : 13 : output_file output_file_;
606 : 13 : if (m_filename.get ())
607 : 0 : output_file_ = ctxt.open_output_file (std::move (m_filename));
608 : : else
609 : : // Default filename
610 : : {
611 : 13 : const char *basename = ctxt.get_base_filename ();
612 : 13 : if (!basename)
613 : : {
614 : 0 : ctxt.report_missing_key ("file",
615 : : get_scheme_name (),
616 : : "FILENAME");
617 : 0 : return nullptr;
618 : : }
619 : 13 : output_file_
620 : : = open_html_output_file
621 : 26 : (dc,
622 : : ctxt.get_affected_location_mgr (),
623 : 13 : basename);
624 : : }
625 : 13 : if (!output_file_)
626 : 0 : return nullptr;
627 : :
628 : 26 : auto sink = make_html_sink (dc,
629 : 13 : *ctxt.get_affected_location_mgr (),
630 : 13 : m_html_gen_opts,
631 : 13 : std::move (output_file_));
632 : 13 : return sink;
633 : 13 : }
634 : :
635 : : enum key_handler::result
636 : 14 : html_scheme_handler::maybe_handle_kv (const context &ctxt,
637 : : const std::string &key,
638 : : const std::string &value)
639 : : {
640 : 14 : if (key == "css")
641 : 0 : return parse_bool_value (ctxt, key, value, m_html_gen_opts.m_css);
642 : 14 : if (key == "file")
643 : : {
644 : 0 : m_filename = label_text::take (xstrdup (value.c_str ()));
645 : 0 : return result::ok;
646 : : }
647 : 14 : if (key == "javascript")
648 : 13 : return parse_bool_value (ctxt, key, value,
649 : 13 : m_html_gen_opts.m_javascript);
650 : 1 : 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 : 0 : 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 : 0 : 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
|