Line data Source code
1 : /* Source locations within string literals.
2 : Copyright (C) 2016-2026 Free Software Foundation, Inc.
3 :
4 : This file is part of GCC.
5 :
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
9 : version.
10 :
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
14 : for more details.
15 :
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/>. */
19 :
20 : #include "config.h"
21 : #include "system.h"
22 : #include "coretypes.h"
23 : #include "intl.h"
24 : #include "diagnostic.h"
25 : #include "cpplib.h"
26 : #include "tree.h"
27 : #include "langhooks.h"
28 : #include "substring-locations.h"
29 : #include "gcc-rich-location.h"
30 : #include "diagnostic-highlight-colors.h"
31 :
32 : const char *const
33 : format_string_diagnostic_t::highlight_color_format_string
34 : = highlight_colors::expected;
35 :
36 : const char *const
37 : format_string_diagnostic_t::highlight_color_param
38 : = highlight_colors::actual;
39 :
40 : /* format_string_diagnostic_t's ctor, giving information for use by
41 : the emit_warning* member functions, as follows:
42 :
43 : They attempt to obtain precise location information within a string
44 : literal from FMT_LOC.
45 :
46 : Case 1: if substring location is available, and is within the range of
47 : the format string itself, the primary location of the
48 : diagnostic is the substring range obtained from FMT_LOC, with the
49 : caret at the *end* of the substring range.
50 :
51 : For example:
52 :
53 : test.c:90:10: warning: problem with '%i' here [-Wformat=]
54 : printf ("hello %i", msg);
55 : ~^
56 :
57 : Case 2: if the substring location is available, but is not within
58 : the range of the format string, the primary location is that of the
59 : format string, and a note is emitted showing the substring location.
60 :
61 : For example:
62 : test.c:90:10: warning: problem with '%i' here [-Wformat=]
63 : printf("hello " INT_FMT " world", msg);
64 : ^~~~~~~~~~~~~~~~~~~~~~~~~
65 : test.c:19: note: format string is defined here
66 : #define INT_FMT "%i"
67 : ~^
68 :
69 : Case 3: if precise substring information is unavailable, the primary
70 : location is that of the whole string passed to FMT_LOC's constructor.
71 : For example:
72 :
73 : test.c:90:10: warning: problem with '%i' here [-Wformat=]
74 : printf(fmt, msg);
75 : ^~~
76 :
77 : For each of cases 1-3, if param_loc is not UNKNOWN_LOCATION, then it is used
78 : as a secondary range within the warning. For example, here it
79 : is used with case 1:
80 :
81 : test.c:90:16: warning: '%s' here but arg 2 has 'long' type [-Wformat=]
82 : printf ("foo %s bar", long_i + long_j);
83 : ~^ ~~~~~~~~~~~~~~~
84 :
85 : and here with case 2:
86 :
87 : test.c:90:16: warning: '%s' here but arg 2 has 'long' type [-Wformat=]
88 : printf ("foo " STR_FMT " bar", long_i + long_j);
89 : ^~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~
90 : test.c:89:16: note: format string is defined here
91 : #define STR_FMT "%s"
92 : ~^
93 :
94 : and with case 3:
95 :
96 : test.c:90:10: warning: '%i' here, but arg 2 is "const char *' [-Wformat=]
97 : printf(fmt, msg);
98 : ^~~ ~~~
99 :
100 : If non-NULL, then FMT_LABEL will be used to label the location within the
101 : string for cases 1 and 2; if non-NULL, then PARAM_LABEL will be used to label
102 : the parameter. For example with case 1:
103 :
104 : test.c:90:16: warning: '%s' here but arg 2 has 'long' type [-Wformat=]
105 : printf ("foo %s bar", long_i + long_j);
106 : ~^ ~~~~~~~~~~~~~~~
107 : |
108 : int
109 :
110 : and with case 2:
111 :
112 : test.c:90:10: warning: problem with '%i' here [-Wformat=]
113 : printf("hello " INT_FMT " world", msg);
114 : ^~~~~~~~~~~~~~~~~~~~~~~~~
115 : test.c:19: note: format string is defined here
116 : #define INT_FMT "%i"
117 : ~^
118 : |
119 : int
120 :
121 : If CORRECTED_SUBSTRING is non-NULL, use it for cases 1 and 2 to provide
122 : a fix-it hint, suggesting that it should replace the text within the
123 : substring range. For example:
124 :
125 : test.c:90:10: warning: problem with '%i' here [-Wformat=]
126 : printf ("hello %i", msg);
127 : ~^
128 : %s
129 :
130 : */
131 :
132 10649 : format_string_diagnostic_t::
133 : format_string_diagnostic_t (const substring_loc &fmt_loc,
134 : const range_label *fmt_label,
135 : location_t param_loc,
136 : const range_label *param_label,
137 10649 : const char *corrected_substring)
138 10649 : : m_fmt_loc (fmt_loc),
139 10649 : m_fmt_label (fmt_label),
140 10649 : m_param_loc (param_loc),
141 10649 : m_param_label (param_label),
142 10649 : m_corrected_substring (corrected_substring)
143 : {
144 10649 : }
145 :
146 : /* Emit a warning governed by option OPTION_ID, using SINGULAR_GMSGID as the
147 : format string (or if PLURAL_GMSGID is different from SINGULAR_GMSGID,
148 : using SINGULAR_GMSGID, PLURAL_GMSGID and N as arguments to ngettext)
149 : and AP as its arguments.
150 :
151 : Return true if a warning was emitted, false otherwise. */
152 :
153 : bool
154 10649 : format_string_diagnostic_t::emit_warning_n_va (diagnostics::option_id option_id,
155 : unsigned HOST_WIDE_INT n,
156 : const char *singular_gmsgid,
157 : const char *plural_gmsgid,
158 : va_list *ap) const
159 : {
160 10649 : bool substring_within_range = false;
161 10649 : location_t primary_loc;
162 10649 : location_t fmt_substring_loc = UNKNOWN_LOCATION;
163 10649 : source_range fmt_loc_range
164 10649 : = get_range_from_loc (line_table, m_fmt_loc.get_fmt_string_loc ());
165 10649 : const char *err = m_fmt_loc.get_location (&fmt_substring_loc);
166 10649 : source_range fmt_substring_range
167 10649 : = get_range_from_loc (line_table, fmt_substring_loc);
168 10649 : if (err)
169 : /* Case 3: unable to get substring location. */
170 3187 : primary_loc = m_fmt_loc.get_fmt_string_loc ();
171 : else
172 : {
173 7462 : if (fmt_substring_range.m_start >= fmt_loc_range.m_start
174 5851 : && fmt_substring_range.m_start <= fmt_loc_range.m_finish
175 5669 : && fmt_substring_range.m_finish >= fmt_loc_range.m_start
176 5659 : && fmt_substring_range.m_finish <= fmt_loc_range.m_finish)
177 : /* Case 1. */
178 : {
179 5657 : substring_within_range = true;
180 5657 : primary_loc = fmt_substring_loc;
181 : }
182 : else
183 : /* Case 2. */
184 : {
185 1805 : substring_within_range = false;
186 1805 : primary_loc = m_fmt_loc.get_fmt_string_loc ();
187 : }
188 : }
189 :
190 : /* Only use fmt_label in the initial warning for case 1. */
191 10649 : const range_label *primary_label = NULL;
192 4992 : if (substring_within_range)
193 5657 : primary_label = m_fmt_label;
194 :
195 10649 : auto_diagnostic_group d;
196 10649 : gcc_rich_location richloc (primary_loc, primary_label,
197 10649 : highlight_color_format_string);
198 :
199 10649 : if (m_param_loc != UNKNOWN_LOCATION)
200 1455 : richloc.add_range (m_param_loc, SHOW_RANGE_WITHOUT_CARET, m_param_label,
201 : highlight_color_param);
202 :
203 10649 : if (!err && m_corrected_substring && substring_within_range)
204 805 : richloc.add_fixit_replace (fmt_substring_range, m_corrected_substring);
205 :
206 10649 : diagnostics::diagnostic_info diagnostic;
207 10649 : if (singular_gmsgid != plural_gmsgid)
208 : {
209 548 : unsigned long gtn;
210 :
211 548 : if (sizeof n <= sizeof gtn)
212 548 : gtn = n;
213 : else
214 : /* Use the largest number ngettext can handle, otherwise
215 : preserve the six least significant decimal digits for
216 : languages where the plural form depends on them. */
217 : gtn = n <= ULONG_MAX ? n : n % 1000000LU + 1000000LU;
218 :
219 548 : const char *text = ngettext (singular_gmsgid, plural_gmsgid, gtn);
220 548 : diagnostic_set_info_translated (&diagnostic, text, ap, &richloc,
221 : diagnostics::kind::warning);
222 : }
223 : else
224 10101 : diagnostic_set_info (&diagnostic, singular_gmsgid, ap, &richloc,
225 : diagnostics::kind::warning);
226 10649 : diagnostic.m_option_id = option_id;
227 10649 : bool warned = diagnostic_report_diagnostic (global_dc, &diagnostic);
228 :
229 10649 : if (!err && fmt_substring_loc && !substring_within_range)
230 : /* Case 2. */
231 1805 : if (warned)
232 : {
233 : /* Use fmt_label in the note for case 2. */
234 71 : rich_location substring_richloc
235 : (line_table, fmt_substring_loc,
236 71 : m_fmt_label,
237 71 : highlight_color_format_string);
238 71 : if (m_corrected_substring)
239 45 : substring_richloc.add_fixit_replace (fmt_substring_range,
240 : m_corrected_substring);
241 71 : inform (&substring_richloc,
242 : "format string is defined here");
243 71 : }
244 :
245 10649 : return warned;
246 10649 : }
247 :
248 : /* Singular-only version of the above. */
249 :
250 : bool
251 10101 : format_string_diagnostic_t::emit_warning_va (diagnostics::option_id option_id,
252 : const char *gmsgid,
253 : va_list *ap) const
254 : {
255 10101 : return emit_warning_n_va (option_id, 0, gmsgid, gmsgid, ap);
256 : }
257 :
258 : /* Variadic version of the above (singular only). */
259 :
260 : bool
261 1087 : format_string_diagnostic_t::emit_warning (diagnostics::option_id option_id,
262 : const char *gmsgid,
263 : ...) const
264 : {
265 1087 : va_list ap;
266 1087 : va_start (ap, gmsgid);
267 1087 : bool warned = emit_warning_va (option_id, gmsgid, &ap);
268 1087 : va_end (ap);
269 :
270 1087 : return warned;
271 : }
272 :
273 : /* Variadic version of the above (singular vs plural). */
274 :
275 : bool
276 0 : format_string_diagnostic_t::emit_warning_n (diagnostics::option_id option_id,
277 : unsigned HOST_WIDE_INT n,
278 : const char *singular_gmsgid,
279 : const char *plural_gmsgid,
280 : ...) const
281 : {
282 0 : va_list ap;
283 0 : va_start (ap, plural_gmsgid);
284 0 : bool warned = emit_warning_n_va (option_id, n, singular_gmsgid, plural_gmsgid,
285 : &ap);
286 0 : va_end (ap);
287 :
288 0 : return warned;
289 : }
290 :
291 : /* Attempt to determine the source location of the substring.
292 : If successful, return NULL and write the source location to *OUT_LOC.
293 : Otherwise return an error message. Error messages are intended
294 : for GCC developers (to help debugging) rather than for end-users. */
295 :
296 : const char *
297 11664 : substring_loc::get_location (location_t *out_loc) const
298 : {
299 11664 : gcc_assert (out_loc);
300 11664 : return lang_hooks.get_substring_location (*this, out_loc);
301 : }
|