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 : :
47 : : namespace diagnostics {
48 : : namespace output_spec {
49 : :
50 : : /* Decls. */
51 : :
52 : 114 : struct scheme_name_and_params
53 : : {
54 : : std::string m_scheme_name;
55 : : std::vector<std::pair<std::string, std::string>> m_kvs;
56 : : };
57 : :
58 : : /* Class for parsing the arguments of -fdiagnostics-add-output= and
59 : : -fdiagnostics-set-output=, and making sink
60 : : instances (or issuing errors). */
61 : :
62 : 74 : class output_factory
63 : : {
64 : : public:
65 : : class scheme_handler
66 : : {
67 : : public:
68 : 111 : scheme_handler (std::string scheme_name)
69 : 111 : : m_scheme_name (std::move (scheme_name))
70 : : {}
71 : : virtual ~scheme_handler () {}
72 : :
73 : 75 : const std::string &get_scheme_name () const { return m_scheme_name; }
74 : :
75 : : virtual std::unique_ptr<sink>
76 : : make_sink (const context &ctxt,
77 : : diagnostics::context &dc,
78 : : const char *unparsed_arg,
79 : : const scheme_name_and_params &parsed_arg) const = 0;
80 : :
81 : : protected:
82 : : bool
83 : 38 : parse_bool_value (const context &ctxt,
84 : : const char *unparsed_arg,
85 : : const std::string &key,
86 : : const std::string &value,
87 : : bool &out) const
88 : : {
89 : 38 : if (value == "yes")
90 : : {
91 : 16 : out = true;
92 : 16 : return true;
93 : : }
94 : 22 : else if (value == "no")
95 : : {
96 : 22 : out = false;
97 : 22 : return true;
98 : : }
99 : : else
100 : : {
101 : 0 : ctxt.report_error
102 : 0 : ("%<%s%s%>:"
103 : : " unexpected value %qs for key %qs; expected %qs or %qs",
104 : : ctxt.get_option_name (), unparsed_arg,
105 : : value.c_str (),
106 : : key.c_str (),
107 : : "yes", "no");
108 : :
109 : 0 : return false;
110 : : }
111 : : }
112 : : template <typename EnumType, size_t NumValues>
113 : : bool
114 : 3 : parse_enum_value (const context &ctxt,
115 : : const char *unparsed_arg,
116 : : const std::string &key,
117 : : const std::string &value,
118 : : const std::array<std::pair<const char *, EnumType>, NumValues> &value_names,
119 : : EnumType &out) const
120 : : {
121 : 5 : for (auto &iter : value_names)
122 : 5 : if (value == iter.first)
123 : : {
124 : 3 : out = iter.second;
125 : 3 : return true;
126 : : }
127 : :
128 : 0 : auto_vec<const char *> known_values;
129 : 0 : for (auto iter : value_names)
130 : 0 : known_values.safe_push (iter.first);
131 : 0 : pp_markup::comma_separated_quoted_strings e (known_values);
132 : : ctxt.report_error
133 : 0 : ("%<%s%s%>:"
134 : : " unexpected value %qs for key %qs; known values: %e",
135 : : ctxt.get_option_name (), unparsed_arg,
136 : : value.c_str (),
137 : : key.c_str (),
138 : : &e);
139 : 0 : return false;
140 : 0 : }
141 : :
142 : : private:
143 : : const std::string m_scheme_name;
144 : : };
145 : :
146 : : output_factory ();
147 : :
148 : : std::unique_ptr<sink>
149 : : make_sink (const context &ctxt,
150 : : diagnostics::context &dc,
151 : : const char *unparsed_arg,
152 : : const scheme_name_and_params &parsed_arg);
153 : :
154 : : const scheme_handler *get_scheme_handler (const std::string &scheme_name);
155 : :
156 : : private:
157 : : std::vector<std::unique_ptr<scheme_handler>> m_scheme_handlers;
158 : : };
159 : :
160 : : class text_scheme_handler : public output_factory::scheme_handler
161 : : {
162 : : public:
163 : 37 : text_scheme_handler () : scheme_handler ("text") {}
164 : :
165 : : std::unique_ptr<sink>
166 : : make_sink (const context &ctxt,
167 : : diagnostics::context &dc,
168 : : const char *unparsed_arg,
169 : : const scheme_name_and_params &parsed_arg) const final override;
170 : : };
171 : :
172 : : class sarif_scheme_handler : public output_factory::scheme_handler
173 : : {
174 : : public:
175 : 37 : sarif_scheme_handler () : scheme_handler ("sarif") {}
176 : :
177 : : std::unique_ptr<sink>
178 : : make_sink (const context &ctxt,
179 : : diagnostics::context &dc,
180 : : const char *unparsed_arg,
181 : : const scheme_name_and_params &parsed_arg) const final override;
182 : :
183 : : private:
184 : : static std::unique_ptr<sarif_serialization_format>
185 : : make_sarif_serialization_object (enum sarif_serialization_kind);
186 : : };
187 : :
188 : : class html_scheme_handler : public output_factory::scheme_handler
189 : : {
190 : : public:
191 : 37 : html_scheme_handler () : scheme_handler ("experimental-html") {}
192 : :
193 : : std::unique_ptr<sink>
194 : : make_sink (const context &ctxt,
195 : : diagnostics::context &dc,
196 : : const char *unparsed_arg,
197 : : const scheme_name_and_params &parsed_arg) const final override;
198 : : };
199 : :
200 : : /* struct context. */
201 : :
202 : : void
203 : 16 : context::report_error (const char *gmsgid, ...) const
204 : : {
205 : 16 : va_list ap;
206 : 16 : va_start (ap, gmsgid);
207 : 16 : report_error_va (gmsgid, &ap);
208 : 16 : va_end (ap);
209 : 16 : }
210 : :
211 : : void
212 : 0 : context::report_unknown_key (const char *unparsed_arg,
213 : : const std::string &key,
214 : : const std::string &scheme_name,
215 : : auto_vec<const char *> &known_keys) const
216 : : {
217 : 0 : pp_markup::comma_separated_quoted_strings e (known_keys);
218 : 0 : report_error
219 : 0 : ("%<%s%s%>:"
220 : : " unknown key %qs for format %qs; known keys: %e",
221 : : get_option_name (), unparsed_arg,
222 : : key.c_str (), scheme_name.c_str (), &e);
223 : 0 : }
224 : :
225 : : void
226 : 0 : context::report_missing_key (const char *unparsed_arg,
227 : : const std::string &key,
228 : : const std::string &scheme_name,
229 : : const char *metavar) const
230 : : {
231 : 0 : report_error
232 : 0 : ("%<%s%s%>:"
233 : : " missing required key %qs for format %qs;"
234 : : " try %<%s%s:%s=%s%>",
235 : : get_option_name (), unparsed_arg,
236 : : key.c_str (), scheme_name.c_str (),
237 : : get_option_name (), scheme_name.c_str (), key.c_str (), metavar);
238 : 0 : }
239 : :
240 : : output_file
241 : 2 : context::open_output_file (label_text &&filename) const
242 : : {
243 : 2 : FILE *outf = fopen (filename.get (), "w");
244 : 2 : if (!outf)
245 : : {
246 : 0 : report_error ("unable to open %qs: %m", filename.get ());
247 : 0 : return output_file (nullptr, false, std::move (filename));
248 : : }
249 : 2 : return output_file (outf, true, std::move (filename));
250 : : }
251 : :
252 : : static std::unique_ptr<scheme_name_and_params>
253 : 65 : parse (const context &ctxt, const char *unparsed_arg)
254 : : {
255 : 65 : scheme_name_and_params result;
256 : 65 : if (const char *const colon = strchr (unparsed_arg, ':'))
257 : : {
258 : 54 : result.m_scheme_name = std::string (unparsed_arg, colon - unparsed_arg);
259 : : /* Expect zero of more of KEY=VALUE,KEY=VALUE, etc .*/
260 : 54 : const char *iter = colon + 1;
261 : 54 : const char *last_separator = ":";
262 : 113 : while (iter)
263 : : {
264 : : /* Look for a non-empty key string followed by '='. */
265 : 75 : const char *eq = strchr (iter, '=');
266 : 75 : if (eq == nullptr || eq == iter)
267 : : {
268 : : /* Missing '='. */
269 : 16 : ctxt.report_error
270 : 16 : ("%<%s%s%>:"
271 : : " expected KEY=VALUE-style parameter for format %qs"
272 : : " after %qs;"
273 : : " got %qs",
274 : : ctxt.get_option_name (), unparsed_arg,
275 : : result.m_scheme_name.c_str (),
276 : : last_separator,
277 : : iter);
278 : 16 : return nullptr;
279 : : }
280 : 59 : std::string key = std::string (iter, eq - iter);
281 : 59 : std::string value;
282 : 59 : const char *comma = strchr (iter, ',');
283 : 59 : if (comma)
284 : : {
285 : 21 : value = std::string (eq + 1, comma - (eq + 1));
286 : 21 : iter = comma + 1;
287 : 21 : last_separator = ",";
288 : : }
289 : : else
290 : : {
291 : 38 : value = std::string (eq + 1);
292 : 38 : iter = nullptr;
293 : : }
294 : 177 : result.m_kvs.push_back ({std::move (key), std::move (value)});
295 : 59 : }
296 : : }
297 : : else
298 : 11 : result.m_scheme_name = unparsed_arg;
299 : 49 : return std::make_unique<scheme_name_and_params> (std::move (result));
300 : 65 : }
301 : :
302 : : std::unique_ptr<sink>
303 : 37 : context::parse_and_make_sink (const char *unparsed_arg,
304 : : diagnostics::context &dc)
305 : : {
306 : 37 : auto parsed_arg = parse (*this, unparsed_arg);
307 : 37 : if (!parsed_arg)
308 : 0 : return nullptr;
309 : :
310 : 37 : output_factory factory;
311 : 37 : return factory.make_sink (*this, dc, unparsed_arg, *parsed_arg);
312 : 37 : }
313 : :
314 : : /* class output_factory::scheme_handler. */
315 : :
316 : : /* class output_factory. */
317 : :
318 : 37 : output_factory::output_factory ()
319 : : {
320 : 37 : m_scheme_handlers.push_back (std::make_unique<text_scheme_handler> ());
321 : 37 : m_scheme_handlers.push_back (std::make_unique<sarif_scheme_handler> ());
322 : 37 : m_scheme_handlers.push_back (std::make_unique<html_scheme_handler> ());
323 : 37 : }
324 : :
325 : : const output_factory::scheme_handler *
326 : 37 : output_factory::get_scheme_handler (const std::string &scheme_name)
327 : : {
328 : 75 : for (auto &iter : m_scheme_handlers)
329 : 75 : if (iter->get_scheme_name () == scheme_name)
330 : 37 : return iter.get ();
331 : : return nullptr;
332 : : }
333 : :
334 : : std::unique_ptr<sink>
335 : 37 : output_factory::make_sink (const context &ctxt,
336 : : diagnostics::context &dc,
337 : : const char *unparsed_arg,
338 : : const scheme_name_and_params &parsed_arg)
339 : : {
340 : 37 : auto scheme_handler = get_scheme_handler (parsed_arg.m_scheme_name);
341 : 37 : if (!scheme_handler)
342 : : {
343 : 0 : auto_vec<const char *> strings;
344 : 0 : for (auto &iter : m_scheme_handlers)
345 : 0 : strings.safe_push (iter->get_scheme_name ().c_str ());
346 : 0 : pp_markup::comma_separated_quoted_strings e (strings);
347 : 0 : ctxt.report_error ("%<%s%s%>:"
348 : : " unrecognized format %qs; known formats: %e",
349 : : ctxt.get_option_name (), unparsed_arg,
350 : : parsed_arg.m_scheme_name.c_str (), &e);
351 : 0 : return nullptr;
352 : 0 : }
353 : :
354 : 37 : return scheme_handler->make_sink (ctxt, dc, unparsed_arg, parsed_arg);
355 : : }
356 : :
357 : : /* class text_scheme_handler : public output_factory::scheme_handler. */
358 : :
359 : : std::unique_ptr<sink>
360 : 12 : text_scheme_handler::make_sink (const context &ctxt,
361 : : diagnostics::context &dc,
362 : : const char *unparsed_arg,
363 : : const scheme_name_and_params &parsed_arg) const
364 : : {
365 : 12 : bool show_color = pp_show_color (dc.get_reference_printer ());
366 : 12 : bool show_nesting = true;
367 : 12 : bool show_locations_in_nesting = true;
368 : 12 : bool show_levels = false;
369 : 34 : for (auto& iter : parsed_arg.m_kvs)
370 : : {
371 : 22 : const std::string &key = iter.first;
372 : 22 : const std::string &value = iter.second;
373 : 22 : if (key == "color")
374 : : {
375 : 0 : if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_color))
376 : 0 : return nullptr;
377 : 22 : continue;
378 : : }
379 : 22 : if (key == "show-nesting")
380 : : {
381 : 12 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
382 : : show_nesting))
383 : 0 : return nullptr;
384 : 12 : continue;
385 : : }
386 : 10 : if (key == "show-nesting-locations")
387 : : {
388 : 9 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
389 : : show_locations_in_nesting))
390 : 0 : return nullptr;
391 : 9 : continue;
392 : : }
393 : 1 : if (key == "show-nesting-levels")
394 : : {
395 : 1 : if (!parse_bool_value (ctxt, unparsed_arg, key, value, show_levels))
396 : 0 : return nullptr;
397 : 1 : continue;
398 : : }
399 : :
400 : : /* Key not found. */
401 : 0 : auto_vec<const char *> known_keys;
402 : 0 : known_keys.safe_push ("color");
403 : 0 : known_keys.safe_push ("show-nesting");
404 : 0 : known_keys.safe_push ("show-nesting-locations");
405 : 0 : known_keys.safe_push ("show-nesting-levels");
406 : 0 : ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
407 : : known_keys);
408 : 0 : return nullptr;
409 : 0 : }
410 : :
411 : 12 : auto sink = std::make_unique<diagnostics::text_sink> (dc);
412 : 12 : sink->set_show_nesting (show_nesting);
413 : 12 : sink->set_show_locations_in_nesting (show_locations_in_nesting);
414 : 12 : sink->set_show_nesting_levels (show_levels);
415 : 12 : return sink;
416 : 12 : }
417 : :
418 : : /* class sarif_scheme_handler : public output_factory::scheme_handler. */
419 : :
420 : : std::unique_ptr<sink>
421 : 12 : sarif_scheme_handler::make_sink (const context &ctxt,
422 : : diagnostics::context &dc,
423 : : const char *unparsed_arg,
424 : : const scheme_name_and_params &parsed_arg) const
425 : : {
426 : 12 : label_text filename;
427 : 12 : enum sarif_serialization_kind serialization_kind
428 : : = sarif_serialization_kind::json;
429 : 12 : sarif_generation_options sarif_gen_opts;
430 : 19 : for (auto& iter : parsed_arg.m_kvs)
431 : : {
432 : 7 : const std::string &key = iter.first;
433 : 7 : const std::string &value = iter.second;
434 : 7 : if (key == "file")
435 : : {
436 : 2 : filename = label_text::take (xstrdup (value.c_str ()));
437 : 7 : continue;
438 : : }
439 : 5 : if (key == "serialization")
440 : : {
441 : 0 : static const std::array<std::pair<const char *, enum sarif_serialization_kind>,
442 : : (size_t)sarif_serialization_kind::num_values> value_names
443 : : {{{"json", sarif_serialization_kind::json}}};
444 : :
445 : 0 : if (!parse_enum_value<enum sarif_serialization_kind>
446 : 0 : (ctxt, unparsed_arg,
447 : : key, value,
448 : : value_names,
449 : : serialization_kind))
450 : 0 : return nullptr;
451 : 0 : continue;
452 : 0 : }
453 : 5 : if (key == "version")
454 : : {
455 : 3 : static const std::array<std::pair<const char *, enum sarif_version>,
456 : : (size_t)sarif_version::num_versions> value_names
457 : : {{{"2.1", sarif_version::v2_1_0},
458 : : {"2.2-prerelease", sarif_version::v2_2_prerelease_2024_08_08}}};
459 : :
460 : 3 : if (!parse_enum_value<enum sarif_version>
461 : 3 : (ctxt, unparsed_arg,
462 : : key, value,
463 : : value_names,
464 : : sarif_gen_opts.m_version))
465 : 0 : return nullptr;
466 : 3 : continue;
467 : 3 : }
468 : 2 : if (key == "state-graphs")
469 : : {
470 : 2 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
471 : : sarif_gen_opts.m_state_graph))
472 : 0 : return nullptr;
473 : 2 : continue;
474 : : }
475 : :
476 : : /* Key not found. */
477 : 0 : auto_vec<const char *> known_keys;
478 : 0 : known_keys.safe_push ("file");
479 : 0 : known_keys.safe_push ("serialization");
480 : 0 : known_keys.safe_push ("state-graphs");
481 : 0 : known_keys.safe_push ("version");
482 : 0 : ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
483 : : known_keys);
484 : 0 : return nullptr;
485 : 0 : }
486 : :
487 : 12 : output_file output_file_;
488 : 12 : if (filename.get ())
489 : 2 : output_file_ = ctxt.open_output_file (std::move (filename));
490 : : else
491 : : // Default filename
492 : : {
493 : 10 : const char *basename = ctxt.get_base_filename ();
494 : 10 : if (!basename)
495 : : {
496 : 0 : ctxt.report_missing_key (unparsed_arg,
497 : 0 : "file",
498 : : get_scheme_name (),
499 : : "FILENAME");
500 : 0 : return nullptr;
501 : : }
502 : 10 : output_file_
503 : 20 : = open_sarif_output_file (dc,
504 : : ctxt.get_affected_location_mgr (),
505 : : basename,
506 : 10 : serialization_kind);
507 : : }
508 : 12 : if (!output_file_)
509 : 0 : return nullptr;
510 : :
511 : 12 : auto serialization_obj = make_sarif_serialization_object (serialization_kind);
512 : :
513 : 12 : auto sink = make_sarif_sink (dc,
514 : 12 : *ctxt.get_affected_location_mgr (),
515 : : std::move (serialization_obj),
516 : : sarif_gen_opts,
517 : 12 : std::move (output_file_));
518 : 12 : return sink;
519 : 12 : }
520 : :
521 : : std::unique_ptr<sarif_serialization_format>
522 : 12 : sarif_scheme_handler::
523 : : make_sarif_serialization_object (enum sarif_serialization_kind kind)
524 : : {
525 : 12 : switch (kind)
526 : : {
527 : 0 : default:
528 : 0 : gcc_unreachable ();
529 : 12 : case sarif_serialization_kind::json:
530 : 12 : return std::make_unique<sarif_serialization_format_json> (true);
531 : : break;
532 : : }
533 : : }
534 : :
535 : : /* class html_scheme_handler : public output_factory::scheme_handler. */
536 : :
537 : : std::unique_ptr<sink>
538 : 13 : html_scheme_handler::make_sink (const context &ctxt,
539 : : diagnostics::context &dc,
540 : : const char *unparsed_arg,
541 : : const scheme_name_and_params &parsed_arg) const
542 : : {
543 : 13 : label_text filename;
544 : 13 : html_generation_options html_gen_opts;
545 : 27 : for (auto& iter : parsed_arg.m_kvs)
546 : : {
547 : 14 : const std::string &key = iter.first;
548 : 14 : const std::string &value = iter.second;
549 : 14 : if (key == "css")
550 : : {
551 : 0 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
552 : : html_gen_opts.m_css))
553 : 0 : return nullptr;
554 : 14 : continue;
555 : : }
556 : 14 : if (key == "file")
557 : : {
558 : 0 : filename = label_text::take (xstrdup (value.c_str ()));
559 : 0 : continue;
560 : : }
561 : 14 : if (key == "javascript")
562 : : {
563 : 13 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
564 : : html_gen_opts.m_javascript))
565 : 0 : return nullptr;
566 : 13 : continue;
567 : : }
568 : 1 : if (key == "show-state-diagrams")
569 : : {
570 : 1 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
571 : : html_gen_opts.m_show_state_diagrams))
572 : 0 : return nullptr;
573 : 1 : continue;
574 : : }
575 : 0 : if (key == "show-state-diagrams-dot-src")
576 : : {
577 : 0 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
578 : : html_gen_opts.m_show_state_diagrams_dot_src))
579 : 0 : return nullptr;
580 : 0 : continue;
581 : : }
582 : 0 : if (key == "show-state-diagrams-sarif")
583 : : {
584 : 0 : if (!parse_bool_value (ctxt, unparsed_arg, key, value,
585 : : html_gen_opts.m_show_state_diagrams_sarif))
586 : 0 : return nullptr;
587 : 0 : continue;
588 : : }
589 : :
590 : : /* Key not found. */
591 : 0 : auto_vec<const char *> known_keys;
592 : 0 : known_keys.safe_push ("css");
593 : 0 : known_keys.safe_push ("file");
594 : 0 : known_keys.safe_push ("javascript");
595 : 0 : known_keys.safe_push ("show-state-diagrams");
596 : 0 : known_keys.safe_push ("show-state-diagram-dot-src");
597 : 0 : known_keys.safe_push ("show-state-diagram-sarif");
598 : 0 : ctxt.report_unknown_key (unparsed_arg, key, get_scheme_name (),
599 : : known_keys);
600 : 0 : return nullptr;
601 : 0 : }
602 : :
603 : 13 : output_file output_file_;
604 : 13 : if (filename.get ())
605 : 0 : output_file_ = ctxt.open_output_file (std::move (filename));
606 : : else
607 : : // Default filename
608 : : {
609 : 13 : const char *basename = ctxt.get_base_filename ();
610 : 13 : if (!basename)
611 : : {
612 : 0 : ctxt.report_missing_key (unparsed_arg,
613 : 0 : "file",
614 : : get_scheme_name (),
615 : : "FILENAME");
616 : 0 : return nullptr;
617 : : }
618 : 13 : output_file_
619 : : = open_html_output_file
620 : 26 : (dc,
621 : : ctxt.get_affected_location_mgr (),
622 : 13 : basename);
623 : : }
624 : 13 : if (!output_file_)
625 : 0 : return nullptr;
626 : :
627 : 13 : auto sink = make_html_sink (dc,
628 : 13 : *ctxt.get_affected_location_mgr (),
629 : : html_gen_opts,
630 : 13 : std::move (output_file_));
631 : 13 : return sink;
632 : 13 : }
633 : :
634 : : } // namespace output_spec
635 : :
636 : : #if CHECKING_P
637 : :
638 : : namespace selftest {
639 : :
640 : : using auto_fix_quotes = ::selftest::auto_fix_quotes;
641 : :
642 : : /* RAII class to temporarily override "progname" to the
643 : : string "PROGNAME". */
644 : :
645 : : class auto_fix_progname
646 : : {
647 : : public:
648 : 4 : auto_fix_progname ()
649 : 4 : {
650 : 4 : m_old_progname = progname;
651 : 4 : progname = "PROGNAME";
652 : : }
653 : :
654 : 4 : ~auto_fix_progname ()
655 : : {
656 : 4 : progname = m_old_progname;
657 : 4 : }
658 : :
659 : : private:
660 : : const char *m_old_progname;
661 : : };
662 : :
663 : 56 : struct parser_test
664 : : {
665 : 28 : class test_spec_context : public diagnostics::output_spec::dc_spec_context
666 : : {
667 : : public:
668 : 28 : test_spec_context (diagnostics::context &dc,
669 : : line_maps *location_mgr,
670 : : location_t loc,
671 : : const char *option_name)
672 : : : dc_spec_context (dc,
673 : : location_mgr,
674 : : location_mgr,
675 : : loc,
676 : 28 : option_name)
677 : : {
678 : : }
679 : :
680 : : const char *
681 : 0 : get_base_filename () const final override
682 : : {
683 : 0 : return "BASE_FILENAME";
684 : : }
685 : : };
686 : :
687 : 28 : parser_test ()
688 : 28 : : m_dc (),
689 : 28 : m_ctxt (m_dc, line_table, UNKNOWN_LOCATION, "-fOPTION="),
690 : 28 : m_fmt (m_dc.get_sink (0))
691 : : {
692 : 28 : pp_buffer (m_fmt.get_printer ())->m_flush_p = false;
693 : 28 : }
694 : :
695 : : std::unique_ptr<diagnostics::output_spec::scheme_name_and_params>
696 : 28 : parse (const char *unparsed_arg)
697 : : {
698 : 28 : return diagnostics::output_spec::parse (m_ctxt, unparsed_arg);
699 : : }
700 : :
701 : 28 : bool execution_failed_p () const
702 : : {
703 : 28 : return m_dc.execution_failed_p ();
704 : : }
705 : :
706 : : const char *
707 : 16 : get_diagnostic_text () const
708 : : {
709 : 16 : return pp_formatted_text (m_fmt.get_printer ());
710 : : }
711 : :
712 : : private:
713 : : diagnostics::selftest::test_context m_dc;
714 : : test_spec_context m_ctxt;
715 : : diagnostics::sink &m_fmt;
716 : : };
717 : :
718 : : /* Selftests. */
719 : :
720 : : static void
721 : 4 : test_output_arg_parsing ()
722 : : {
723 : 4 : auto_fix_quotes fix_quotes;
724 : 4 : auto_fix_progname fix_progname;
725 : :
726 : : /* Minimal correct example. */
727 : 4 : {
728 : 4 : parser_test pt;
729 : 4 : auto result = pt.parse ("foo");
730 : 4 : ASSERT_EQ (result->m_scheme_name, "foo");
731 : 4 : ASSERT_EQ (result->m_kvs.size (), 0);
732 : 4 : ASSERT_FALSE (pt.execution_failed_p ());
733 : 4 : }
734 : :
735 : : /* Stray trailing colon with no key/value pairs. */
736 : 4 : {
737 : 4 : parser_test pt;
738 : 4 : auto result = pt.parse ("foo:");
739 : 4 : ASSERT_EQ (result, nullptr);
740 : 4 : ASSERT_TRUE (pt.execution_failed_p ());
741 : 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
742 : : "PROGNAME: error: `-fOPTION=foo:':"
743 : : " expected KEY=VALUE-style parameter for format `foo'"
744 : : " after `:';"
745 : : " got `'\n");
746 : 4 : }
747 : :
748 : : /* No key before '='. */
749 : 4 : {
750 : 4 : parser_test pt;
751 : 4 : auto result = pt.parse ("foo:=");
752 : 4 : ASSERT_EQ (result, nullptr);
753 : 4 : ASSERT_TRUE (pt.execution_failed_p ());
754 : 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
755 : : "PROGNAME: error: `-fOPTION=foo:=':"
756 : : " expected KEY=VALUE-style parameter for format `foo'"
757 : : " after `:';"
758 : : " got `='\n");
759 : 4 : }
760 : :
761 : : /* No value for key. */
762 : 4 : {
763 : 4 : parser_test pt;
764 : 4 : auto result = pt.parse ("foo:key,");
765 : 4 : ASSERT_EQ (result, nullptr);
766 : 4 : ASSERT_TRUE (pt.execution_failed_p ());
767 : 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
768 : : "PROGNAME: error: `-fOPTION=foo:key,':"
769 : : " expected KEY=VALUE-style parameter for format `foo'"
770 : : " after `:';"
771 : : " got `key,'\n");
772 : 4 : }
773 : :
774 : : /* Correct example, with one key/value pair. */
775 : 4 : {
776 : 4 : parser_test pt;
777 : 4 : auto result = pt.parse ("foo:key=value");
778 : 4 : ASSERT_EQ (result->m_scheme_name, "foo");
779 : 4 : ASSERT_EQ (result->m_kvs.size (), 1);
780 : 4 : ASSERT_EQ (result->m_kvs[0].first, "key");
781 : 4 : ASSERT_EQ (result->m_kvs[0].second, "value");
782 : 4 : ASSERT_FALSE (pt.execution_failed_p ());
783 : 4 : }
784 : :
785 : : /* Stray trailing comma. */
786 : 4 : {
787 : 4 : parser_test pt;
788 : 4 : auto result = pt.parse ("foo:key=value,");
789 : 4 : ASSERT_EQ (result, nullptr);
790 : 4 : ASSERT_TRUE (pt.execution_failed_p ());
791 : 4 : ASSERT_STREQ (pt.get_diagnostic_text (),
792 : : "PROGNAME: error: `-fOPTION=foo:key=value,':"
793 : : " expected KEY=VALUE-style parameter for format `foo'"
794 : : " after `,';"
795 : : " got `'\n");
796 : 4 : }
797 : :
798 : : /* Correct example, with two key/value pairs. */
799 : 4 : {
800 : 4 : parser_test pt;
801 : 4 : auto result = pt.parse ("foo:color=red,shape=circle");
802 : 4 : ASSERT_EQ (result->m_scheme_name, "foo");
803 : 4 : ASSERT_EQ (result->m_kvs.size (), 2);
804 : 4 : ASSERT_EQ (result->m_kvs[0].first, "color");
805 : 4 : ASSERT_EQ (result->m_kvs[0].second, "red");
806 : 4 : ASSERT_EQ (result->m_kvs[1].first, "shape");
807 : 4 : ASSERT_EQ (result->m_kvs[1].second, "circle");
808 : 4 : ASSERT_FALSE (pt.execution_failed_p ());
809 : 4 : }
810 : 4 : }
811 : :
812 : : /* Run all of the selftests within this file. */
813 : :
814 : : void
815 : 4 : output_spec_cc_tests ()
816 : : {
817 : 4 : test_output_arg_parsing ();
818 : 4 : }
819 : :
820 : : } // namespace diagnostics::selftest
821 : :
822 : : #endif /* #if CHECKING_P */
823 : :
824 : : } // namespace diagnostics
|