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