diff mbox

[PR,c/52952] More precise locations within format strings

Message ID CAESRpQCBPF9n7yHz=UJ9ykA+tPsR5d26JM_c_C-fW3=ByU0RyA@mail.gmail.com
State New
Headers show

Commit Message

Manuel López-Ibáñez Nov. 7, 2014, 5:27 p.m. UTC
This patch allows format warnings to point within the format string
for simple strings. There are a few limitations:

* It does not handle 'const char *' because the location of the
initializer is not available. The result is the same before and after
this patch.

* It does not handle non-concatenated tokens, since the preprocessor
does not keep their location yet. The result after the patch is that
we point to some arbitrary place between the first " and the first
newline. This is slightly worse than the current behavior (which
points to the first "), but I could not figure out a way to detect
this case and not generate an offset. It is a matter of implementing
this idea: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52952#c13 as a
follow-up. The line pointed at is the same before and after the patch,
only the column number is affected.

* It does not handle macros, but the behavior before and after this
patch is the same (and there is work-in-progress on this:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52952#c33)

The changes to libcpp are exactly the same as in this patch:
https://gcc.gnu.org/ml/gcc-patches/2014-11/msg00000.html, which is
still pending review.

Boot&tested on x86_64-linux-gnu.

OK?

libcpp/ChangeLog:

2014-11-07  Manuel López-Ibáñez  <manu@gcc.gnu.org>

    * include/line-map.h (linemap_position_for_loc_and_offset):
    Declare.
    * line-map.c (linemap_position_for_loc_and_offset): New.

gcc/c-family/ChangeLog:

2014-11-07  Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR c/52952
    * c-format.c (location_from_offset): New.
    (check_format_info): Use it.
    (check_format_arg): Likewise.
    (check_format_info_main): Likewise.
    (format_type_warning): Likewise.

gcc/testsuite/ChangeLog:

2014-11-07  Manuel López-Ibáñez  <manu@gcc.gnu.org>

    PR c/52952
    * gcc.dg/redecl-4.c: Update column info.
    * gcc.dg/format/bitfld-1.c: Likewise.
    * gcc.dg/format/attr-2.c: Likewise.
    * gcc.dg/format/attr-6.c: Likewise.
    * gcc.dg/format/attr-7.c: Likewise.
    * gcc.dg/format/asm_fprintf-1.c: Likewise.
    * gcc.dg/format/attr-4.c: Likewise.
    * gcc.dg/format/branch-1.c: Likewise.
    * gcc.dg/format/c90-printf-1.c: Likewise.

Comments

Joseph Myers Nov. 7, 2014, 5:57 p.m. UTC | #1
On Fri, 7 Nov 2014, Manuel López-Ibáñez wrote:

> This patch allows format warnings to point within the format string
> for simple strings. There are a few limitations:
> 
> * It does not handle 'const char *' because the location of the
> initializer is not available. The result is the same before and after
> this patch.
> 
> * It does not handle non-concatenated tokens, since the preprocessor
> does not keep their location yet. The result after the patch is that
> we point to some arbitrary place between the first " and the first
> newline. This is slightly worse than the current behavior (which
> points to the first "), but I could not figure out a way to detect
> this case and not generate an offset. It is a matter of implementing
> this idea: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52952#c13 as a
> follow-up. The line pointed at is the same before and after the patch,
> only the column number is affected.
> 
> * It does not handle macros, but the behavior before and after this
> patch is the same (and there is work-in-progress on this:
> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52952#c33)

Does it also not handle escape sequences in the strings?  You appear to 
compute offsets in terms of the number of bytes after the start of the 
string, then pass this to functions commented to take an offset in 
columns, and escape sequences before the point where the warning is being 
given are another case where those would differ.

Anyway, the front-end changes are OK with appropriate comments that make 
clear what the interface is at each point and where the (missing) 
conversion from a byte offset within a string to a column offset in the 
source file should take place, though someone else ought to review the 
line-map changes.
Manuel López-Ibáñez Nov. 7, 2014, 6:55 p.m. UTC | #2
On 7 November 2014 18:57, Joseph Myers <joseph@codesourcery.com> wrote:
> On Fri, 7 Nov 2014, Manuel López-Ibáñez wrote:
>
>> This patch allows format warnings to point within the format string
>> for simple strings. There are a few limitations:
>>
>> * It does not handle 'const char *' because the location of the
>> initializer is not available. The result is the same before and after
>> this patch.
>>
>> * It does not handle non-concatenated tokens, since the preprocessor
>> does not keep their location yet. The result after the patch is that
>> we point to some arbitrary place between the first " and the first
>> newline. This is slightly worse than the current behavior (which
>> points to the first "), but I could not figure out a way to detect
>> this case and not generate an offset. It is a matter of implementing
>> this idea: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52952#c13 as a
>> follow-up. The line pointed at is the same before and after the patch,
>> only the column number is affected.
>>
>> * It does not handle macros, but the behavior before and after this
>> patch is the same (and there is work-in-progress on this:
>> https://gcc.gnu.org/bugzilla/show_bug.cgi?id=52952#c33)
>
> Does it also not handle escape sequences in the strings?  You appear to
> compute offsets in terms of the number of bytes after the start of the
> string, then pass this to functions commented to take an offset in
> columns, and escape sequences before the point where the warning is being
> given are another case where those would differ.

Ops, no. For trivial escape sequences I could just count +1 when a
character that results from an escape sequence is found. But without
the original string around, I cannot distinguish between \n and \x012.

Maybe I can open the file and re-parse the string to find the right
column. Of course, this will not work when reading stdin, but in that
case the behavior will be the same as currently. It will also allow me
to gracefully degrade in the case of non-concatenated tokens. Do you
see any other alternative?

> Anyway, the front-end changes are OK with appropriate comments that make
> clear what the interface is at each point and where the (missing)
> conversion from a byte offset within a string to a column offset in the
> source file should take place, though someone else ought to review the
> line-map changes.

Thanks for the review. I will try first to fix the escape sequences
case. Nonetheless, the line-map changes are useful for the other patch
(the Fortran parts are approved). There are 3 entries for libcpp in
MAINTAINERS:

libcpp                  Per Bothner             <per@bothner.com>
libcpp                  All C and C++ front end maintainers
libcpp                  Tom Tromey              <tromey@redhat.com>

Neither Per nor Tom are active in GCC anymore. If the FE maintainers
do not feel comfortable reviewing line-map changes, could you nominate
Dodji as line-map maintainer if he is willing to accept it? I think he
is currently the person that understands that code best.

Cheers,

Manuel.
Joseph Myers Nov. 7, 2014, 9:39 p.m. UTC | #3
On Fri, 7 Nov 2014, Manuel López-Ibáñez wrote:

> Maybe I can open the file and re-parse the string to find the right
> column. Of course, this will not work when reading stdin, but in that
> case the behavior will be the same as currently. It will also allow me
> to gracefully degrade in the case of non-concatenated tokens. Do you
> see any other alternative?

I expect something like that will be needed (to avoid large overhead from 
tracking details of locations inside all strings when few will ever have 
those locations needed).

> Neither Per nor Tom are active in GCC anymore. If the FE maintainers
> do not feel comfortable reviewing line-map changes, could you nominate
> Dodji as line-map maintainer if he is willing to accept it? I think he
> is currently the person that understands that code best.

I think Dodji as diagnostics maintainer is better placed than I am to 
review line-map patches.
Manuel López-Ibáñez Nov. 8, 2014, 9:20 a.m. UTC | #4
On 7 November 2014 22:39, Joseph Myers <joseph@codesourcery.com> wrote:
>> Neither Per nor Tom are active in GCC anymore. If the FE maintainers
>> do not feel comfortable reviewing line-map changes, could you nominate
>> Dodji as line-map maintainer if he is willing to accept it? I think he
>> is currently the person that understands that code best.
>
> I think Dodji as diagnostics maintainer is better placed than I am to
> review line-map patches.

Then, do you mean that line-map falls under the reach of the
diagnostics maintainer? I agree, but Dodji himself does not seem to be
sure about this:

https://gcc.gnu.org/ml/gcc-patches/2014-10/msg02444.html

It would be helpful if one of the FE maintainers clarified this once
and for all.

Cheers,

Manuel.
Jeff Law Nov. 10, 2014, 6:55 a.m. UTC | #5
On 11/08/14 02:20, Manuel López-Ibáñez wrote:
> On 7 November 2014 22:39, Joseph Myers <joseph@codesourcery.com> wrote:
>>> Neither Per nor Tom are active in GCC anymore. If the FE maintainers
>>> do not feel comfortable reviewing line-map changes, could you nominate
>>> Dodji as line-map maintainer if he is willing to accept it? I think he
>>> is currently the person that understands that code best.
>>
>> I think Dodji as diagnostics maintainer is better placed than I am to
>> review line-map patches.
>
> Then, do you mean that line-map falls under the reach of the
> diagnostics maintainer? I agree, but Dodji himself does not seem to be
> sure about this:
>
> https://gcc.gnu.org/ml/gcc-patches/2014-10/msg02444.html
>
> It would be helpful if one of the FE maintainers clarified this once
> and for all.
If the front-end maintainers think Dodji is the best person to handle 
this stuff, and Dodji is willing, then I think we should make add the 
linemap stuff to Dodji's plate.

Jeff
Dodji Seketeli Nov. 10, 2014, 9:57 a.m. UTC | #6
Joseph Myers <joseph@codesourcery.com> writes:

[...]

>> Neither Per nor Tom are active in GCC anymore. If the FE maintainers
>> do not feel comfortable reviewing line-map changes, could you nominate
>> Dodji as line-map maintainer if he is willing to accept it? I think he
>> is currently the person that understands that code best.
>
> I think Dodji as diagnostics maintainer is better placed than I am to 
> review line-map patches.

Thanks Joseph,

As Manuel said at:

    Manuel López-Ibáñez <lopezibanez@gmail.com> writes:

    [...]

    > Then, do you mean that line-map falls under the reach of the
    > diagnostics maintainer? I agree, but Dodji himself does not seem to be
    > sure about this:
    >
    > https://gcc.gnu.org/ml/gcc-patches/2014-10/msg02444.html

I have already reviewed that line-map patch.  But as I thought Tom, the
FE maintainers and the global maintainers were the qualified person to
actually approve the patch, I didn't want to act beyond my prerogatives.

Jeff Law <law@redhat.com> writes:

> If the front-end maintainers think Dodji is the best person to handle
> this stuff, and Dodji is willing, then I think we should make add the
> linemap stuff to Dodji's plate.

Hey Jeff,

FWIW, I am willing to help with the line map sub-system, of course.
Thank you for your confidence.

Cheers,
Joseph Myers Nov. 10, 2014, 6:49 p.m. UTC | #7
On Sat, 8 Nov 2014, Manuel López-Ibáñez wrote:

> On 7 November 2014 22:39, Joseph Myers <joseph@codesourcery.com> wrote:
> >> Neither Per nor Tom are active in GCC anymore. If the FE maintainers
> >> do not feel comfortable reviewing line-map changes, could you nominate
> >> Dodji as line-map maintainer if he is willing to accept it? I think he
> >> is currently the person that understands that code best.
> >
> > I think Dodji as diagnostics maintainer is better placed than I am to
> > review line-map patches.
> 
> Then, do you mean that line-map falls under the reach of the
> diagnostics maintainer? I agree, but Dodji himself does not seem to be
> sure about this:

I think we should consider it to be covered by diagnostics maintainers.
Jason Merrill Nov. 11, 2014, 4:41 p.m. UTC | #8
On 11/10/2014 01:49 PM, Joseph Myers wrote:
>> Then, do you mean that line-map falls under the reach of the
>> diagnostics maintainer? I agree, but Dodji himself does not seem to be
>> sure about this:
>
> I think we should consider it to be covered by diagnostics maintainers.

That makes sense.

Jason
Andreas Schwab Nov. 12, 2014, 2:24 p.m. UTC | #9
Manuel López-Ibáñez <lopezibanez@gmail.com> writes:

> Index: libcpp/line-map.c
> ===================================================================
> --- libcpp/line-map.c	(revision 217191)
> +++ libcpp/line-map.c	(working copy)
> @@ -631,10 +631,54 @@ linemap_position_for_line_and_column (co
>  	  + ((line - ORDINARY_MAP_STARTING_LINE_NUMBER (map))
>  	     << ORDINARY_MAP_NUMBER_OF_COLUMN_BITS (map))
>  	  + (column & ((1 << ORDINARY_MAP_NUMBER_OF_COLUMN_BITS (map)) - 1)));
>  }
>  
> +/* Encode and return a source_location starting from location LOC and
> +   shifting it by OFFSET columns.  This function does not support
> +   virtual locations.  */
> +
> +source_location
> +linemap_position_for_loc_and_offset (struct line_maps *set,
> +				     source_location loc,
> +				     unsigned int offset)
> +{
> +  const struct line_map * map = NULL;
> +
> +  /* This function does not support virtual locations yet.  */
> +  linemap_assert (!linemap_location_from_macro_expansion_p (set, loc));
> +
> +  if (offset == 0
> +      /* Adding an offset to a reserved location (like
> +	 UNKNOWN_LOCATION for the C/C++ FEs) does not really make
> +	 sense.  So let's live the location intact in that case.  */
> +      || loc < RESERVED_LOCATION_COUNT)
> +    return loc;
> +
> +  /* First, we find the real location and shift it.  */
> +  loc = linemap_resolve_location (set, loc, LRK_SPELLING_LOCATION, &map);
> +  /* The new location (loc + offset) should be higher than the first
> +     location encoded by MAP.  */
> +  linemap_assert (MAP_START_LOCATION (map) < loc + offset);
> +
> +  /* If MAP is not the last line map of its set, then the new location
> +     (loc + offset) should be less than the first location encoded by
> +     the next line map of the set.  */
> +  if (map < LINEMAPS_LAST_ORDINARY_MAP (set))
> +    linemap_assert (MAP_START_LOCATION (&map[1]) < loc + offset);

../../libcpp/line-map.c:667:65: error: suggest braces around empty body in an 'if' statement [-Werror=empty-body]

Andreas.
Manuel López-Ibáñez Nov. 12, 2014, 2:35 p.m. UTC | #10
On 12 November 2014 15:24, Andreas Schwab <schwab@suse.de> wrote:
> Manuel López-Ibáñez <lopezibanez@gmail.com> writes:
>
>> Index: libcpp/line-map.c
>> ===================================================================
>> --- libcpp/line-map.c (revision 217191)
>> +++ libcpp/line-map.c (working copy)
>> @@ -631,10 +631,54 @@ linemap_position_for_line_and_column (co
>>         + ((line - ORDINARY_MAP_STARTING_LINE_NUMBER (map))
>>            << ORDINARY_MAP_NUMBER_OF_COLUMN_BITS (map))
>>         + (column & ((1 << ORDINARY_MAP_NUMBER_OF_COLUMN_BITS (map)) - 1)));
>>  }
>>
>> +/* Encode and return a source_location starting from location LOC and
>> +   shifting it by OFFSET columns.  This function does not support
>> +   virtual locations.  */
>> +
>> +source_location
>> +linemap_position_for_loc_and_offset (struct line_maps *set,
>> +                                  source_location loc,
>> +                                  unsigned int offset)
>> +{
>> +  const struct line_map * map = NULL;
>> +
>> +  /* This function does not support virtual locations yet.  */
>> +  linemap_assert (!linemap_location_from_macro_expansion_p (set, loc));
>> +
>> +  if (offset == 0
>> +      /* Adding an offset to a reserved location (like
>> +      UNKNOWN_LOCATION for the C/C++ FEs) does not really make
>> +      sense.  So let's live the location intact in that case.  */
>> +      || loc < RESERVED_LOCATION_COUNT)
>> +    return loc;
>> +
>> +  /* First, we find the real location and shift it.  */
>> +  loc = linemap_resolve_location (set, loc, LRK_SPELLING_LOCATION, &map);
>> +  /* The new location (loc + offset) should be higher than the first
>> +     location encoded by MAP.  */
>> +  linemap_assert (MAP_START_LOCATION (map) < loc + offset);
>> +
>> +  /* If MAP is not the last line map of its set, then the new location
>> +     (loc + offset) should be less than the first location encoded by
>> +     the next line map of the set.  */
>> +  if (map < LINEMAPS_LAST_ORDINARY_MAP (set))
>> +    linemap_assert (MAP_START_LOCATION (&map[1]) < loc + offset);
>
> ../../libcpp/line-map.c:667:65: error: suggest braces around empty body in an 'if' statement [-Werror=empty-body]

I just (r217418) bootstrapped this code and it did not produce this
error (or warning).  Could you give more details?

Cheers,

Manuel.
Andreas Schwab Nov. 12, 2014, 2:37 p.m. UTC | #11
See libcpp/include/line-map.h:589.

Andreas.
Marek Polacek Nov. 12, 2014, 2:38 p.m. UTC | #12
On Wed, Nov 12, 2014 at 03:35:06PM +0100, Manuel López-Ibáñez wrote:
> > ../../libcpp/line-map.c:667:65: error: suggest braces around empty body in an 'if' statement [-Werror=empty-body]
> 
> I just (r217418) bootstrapped this code and it did not produce this
> error (or warning).  Could you give more details?

Have you tried the bootstrap without checking enabled?

	Marek
Jeff Law Nov. 13, 2014, 5:29 p.m. UTC | #13
On 11/10/14 02:57, Dodji Seketeli wrote:
>
> FWIW, I am willing to help with the line map sub-system, of course.
> Thank you for your confidence.
OK, let's go ahead and make that official.  Please update MAINTAINERS ;-)
jeff
Dodji Seketeli Nov. 17, 2014, 9:29 a.m. UTC | #14
Jeff Law <law@redhat.com> writes:

> OK, let's go ahead and make that official.  Please update MAINTAINERS ;-)

I have just added a line for myself as a maintainer for the line map sub
system in the MAINTAINERS file.

Thanks!
diff mbox

Patch

Index: gcc/c-family/c-format.c
===================================================================
--- gcc/c-family/c-format.c	(revision 217191)
+++ gcc/c-family/c-format.c	(working copy)
@@ -66,10 +66,32 @@  static bool cmp_attribs (const char *tat
 
 static int first_target_format_type;
 static const char *format_name (int format_num);
 static int format_flags (int format_num);
 
+/* FIXME: This indicates that loc is not the location of the format
+   string, thus computing an offset is useless.  This happens, for
+   example, when the format string is a constant array.
+   Unfortunately, GCC does not keep track of the location of the
+   initializer of the array yet.  */
+static bool offset_is_invalid;
+
+/* Return a location that encodes the same location as LOC but shifted
+   by OFFSET columns.  */
+
+static location_t
+location_from_offset (location_t loc, int offset)
+{
+  gcc_checking_assert (offset >= 0);
+  if (offset_is_invalid
+      || linemap_location_from_macro_expansion_p (line_table, loc)
+      || offset < 0)
+    return loc;
+  return linemap_position_for_loc_and_offset (line_table,
+					      loc, (unsigned int) offset);
+}
+
 /* Check that we have a pointer to a string suitable for use as a format.
    The default is to check for a char type.
    For objective-c dialects, this is extended to include references to string
    objects validated by objc_string_ref_type_p ().  
    Targets may also provide a string object type that can be used within c and 
@@ -378,10 +400,13 @@  typedef struct format_wanted_type
   int format_length;
   /* The actual parameter to check against the wanted type.  */
   tree param;
   /* The argument number of that parameter.  */
   int arg_num;
+  /* The offset location of this argument with respect to the format
+     string location.  */
+  unsigned int offset_loc;
   /* The next type to check for this format conversion, or NULL if none.  */
   struct format_wanted_type *next;
 } format_wanted_type;
 
 /* Convenience macro for format_length_info meaning unused.  */
@@ -1346,12 +1371,10 @@  check_format_info (function_format_info
 
   check_function_arguments_recurse (check_format_arg, &format_ctx,
 				    format_tree, arg_num);
 
   location_t loc = format_ctx.res->format_string_loc;
-  if (res.extra_arg_loc == UNKNOWN_LOCATION)
-    res.extra_arg_loc = loc;
 
   if (res.number_non_literal > 0)
     {
       /* Functions taking a va_list normally pass a non-literal format
 	 string.  These functions typically are declared with
@@ -1393,12 +1416,16 @@  check_format_info (function_format_info
      arguments, but was otherwise OK (either non-literal or checked OK).
      If the format is an empty string, this should be counted similarly to the
      case of extra format arguments.  */
   if (res.number_extra_args > 0 && res.number_non_literal == 0
       && res.number_other == 0)
-    warning_at (res.extra_arg_loc, OPT_Wformat_extra_args,
-		"too many arguments for format");
+    {
+      if (res.extra_arg_loc == UNKNOWN_LOCATION)
+	res.extra_arg_loc = loc;
+      warning_at (res.extra_arg_loc, OPT_Wformat_extra_args,
+		  "too many arguments for format");
+    }
   if (res.number_dollar_extra_args > 0 && res.number_non_literal == 0
       && res.number_other == 0)
     warning_at (loc, OPT_Wformat_extra_args, "unused arguments in $-style format");
   if (res.number_empty > 0 && res.number_non_literal == 0
       && res.number_other == 0)
@@ -1485,10 +1512,11 @@  check_format_arg (void *ctx, tree format
     {
       res->number_non_literal++;
       return;
     }
   res->format_string_loc = EXPR_LOC_OR_LOC (format_tree, input_location);
+  offset_is_invalid = false;
   format_tree = TREE_OPERAND (format_tree, 0);
   if (format_types[info->format_type].flags 
       & (int) FMT_FLAG_PARSE_ARG_CONVERT_EXTERNAL)
     {
       bool objc_str = (info->format_type == gcc_objc_string_format_type);
@@ -1538,10 +1566,11 @@  check_format_arg (void *ctx, tree format
       /* Extract the string constant initializer.  Note that this may include
 	 a trailing NUL character that is not in the array (e.g.
 	 const char a[3] = "foo";).  */
       array_size = DECL_SIZE_UNIT (format_tree);
       format_tree = array_init;
+      offset_is_invalid = true;
     }
   if (TREE_CODE (format_tree) != STRING_CST)
     {
       res->number_non_literal++;
       return;
@@ -1663,11 +1692,13 @@  check_format_info_main (format_check_res
 
       if (*format_chars++ != '%')
 	continue;
       if (*format_chars == 0)
 	{
-          warning_at (format_string_loc, OPT_Wformat_,
+          warning_at (location_from_offset (format_string_loc,
+					    format_chars - orig_format_chars),
+		      OPT_Wformat_,
 		      "spurious trailing %<%%%> in format");
 	  continue;
 	}
       if (*format_chars == '%')
 	{
@@ -1708,11 +1739,14 @@  check_format_info_main (format_check_res
 	{
 	  const format_flag_spec *s = get_flag_spec (flag_specs,
 						     *format_chars, NULL);
 	  if (strchr (flag_chars, *format_chars) != 0)
 	    {
-	      warning_at (format_string_loc, OPT_Wformat_,
+	      warning_at (location_from_offset (format_string_loc,
+						format_chars + 1
+						- orig_format_chars),
+			  OPT_Wformat_,
 			  "repeated %s in format", _(s->name));
 	    }
 	  else
 	    {
 	      i = strlen (flag_chars);
@@ -1788,10 +1822,12 @@  check_format_info_main (format_check_res
                   width_wanted_type.kind = CF_KIND_FIELD_WIDTH;
 		  width_wanted_type.format_start = format_chars - 1;
 		  width_wanted_type.format_length = 1;
 		  width_wanted_type.param = cur_param;
 		  width_wanted_type.arg_num = arg_num;
+		  width_wanted_type.offset_loc =
+		    format_chars - orig_format_chars;
 		  width_wanted_type.next = NULL;
 		  if (last_wanted_type != 0)
 		    last_wanted_type->next = &width_wanted_type;
 		  if (first_wanted_type == 0)
 		    first_wanted_type = &width_wanted_type;
@@ -1830,11 +1866,13 @@  check_format_info_main (format_check_res
 	  ++format_chars;
 	  i = strlen (flag_chars);
 	  flag_chars[i++] = fki->left_precision_char;
 	  flag_chars[i] = 0;
 	  if (!ISDIGIT (*format_chars))
-	    warning_at (format_string_loc, OPT_Wformat_,
+	    warning_at (location_from_offset (format_string_loc,
+					      format_chars - orig_format_chars),
+			OPT_Wformat_,
 			"empty left precision in %s format", fki->name);
 	  while (ISDIGIT (*format_chars))
 	    ++format_chars;
 	}
 
@@ -1895,10 +1933,12 @@  check_format_info_main (format_check_res
                   precision_wanted_type.kind = CF_KIND_FIELD_PRECISION;
 		  precision_wanted_type.param = cur_param;
 		  precision_wanted_type.format_start = format_chars - 2;
 		  precision_wanted_type.format_length = 2;
 		  precision_wanted_type.arg_num = arg_num;
+		  precision_wanted_type.offset_loc =
+		    format_chars - orig_format_chars;
 		  precision_wanted_type.next = NULL;
 		  if (last_wanted_type != 0)
 		    last_wanted_type->next = &precision_wanted_type;
 		  if (first_wanted_type == 0)
 		    first_wanted_type = &precision_wanted_type;
@@ -1907,11 +1947,13 @@  check_format_info_main (format_check_res
 	    }
 	  else
 	    {
 	      if (!(fki->flags & (int) FMT_FLAG_EMPTY_PREC_OK)
 		  && !ISDIGIT (*format_chars))
-		warning_at (format_string_loc, OPT_Wformat_,
+		warning_at (location_from_offset (format_string_loc,
+						  format_chars - orig_format_chars),
+			    OPT_Wformat_,
 			    "empty precision in %s format", fki->name);
 	      while (ISDIGIT (*format_chars))
 		++format_chars;
 	    }
 	}
@@ -1993,11 +2035,14 @@  check_format_info_main (format_check_res
 	    {
 	      if (strchr (flag_chars, *format_chars) != 0)
 		{
 		  const format_flag_spec *s = get_flag_spec (flag_specs,
 							     *format_chars, NULL);
-		  warning_at (format_string_loc, OPT_Wformat_,
+		  warning_at (location_from_offset (format_string_loc,
+						    format_chars 
+						    - orig_format_chars),
+			      OPT_Wformat_,
 			      "repeated %s in format", _(s->name));
 		}
 	      else
 		{
 		  i = strlen (flag_chars);
@@ -2011,11 +2056,13 @@  check_format_info_main (format_check_res
       format_char = *format_chars;
       if (format_char == 0
 	  || (!(fki->flags & (int) FMT_FLAG_FANCY_PERCENT_OK)
 	      && format_char == '%'))
 	{
-	  warning_at (format_string_loc, OPT_Wformat_,
+	  warning_at (location_from_offset (format_string_loc,
+					    format_chars - orig_format_chars),
+		      OPT_Wformat_,
 		      "conversion lacks type at end of format");
 	  continue;
 	}
       format_chars++;
       fci = fki->conversion_specs;
@@ -2023,23 +2070,29 @@  check_format_info_main (format_check_res
 	     && strchr (fci->format_chars, format_char) == 0)
 	  ++fci;
       if (fci->format_chars == 0)
 	{
 	  if (ISGRAPH (format_char))
-	    warning_at (format_string_loc, OPT_Wformat_,
+	    warning_at (location_from_offset (format_string_loc,
+					      format_chars - orig_format_chars),
+			OPT_Wformat_,
 			"unknown conversion type character %qc in format",
 			format_char);
 	  else
-	    warning_at (format_string_loc, OPT_Wformat_,
+	    warning_at (location_from_offset (format_string_loc,
+					      format_chars - orig_format_chars),
+			OPT_Wformat_,
 			"unknown conversion type character 0x%x in format",
 			format_char);
 	  continue;
 	}
       if (pedantic)
 	{
 	  if (ADJ_STD (fci->std) > C_STD_VER)
-	    warning_at (format_string_loc, OPT_Wformat_,
+	    warning_at (location_from_offset (format_string_loc,
+					      format_chars - orig_format_chars),
+			OPT_Wformat_,
 			"%s does not support the %<%%%c%> %s format",
 			C_STD_NAME (fci->std), format_char, fki->name);
 	}
 
       /* Validate the individual flags used, removing any that are invalid.  */
@@ -2052,12 +2105,14 @@  check_format_info_main (format_check_res
 	    flag_chars[i - d] = flag_chars[i];
 	    if (flag_chars[i] == fki->length_code_char)
 	      continue;
 	    if (strchr (fci->flag_chars, flag_chars[i]) == 0)
 	      {
-		warning_at (format_string_loc, 
-                            OPT_Wformat_, "%s used with %<%%%c%> %s format",
+		warning_at (location_from_offset (format_string_loc,
+						  format_chars 
+						  - orig_format_chars),
+			    OPT_Wformat_, "%s used with %<%%%c%> %s format",
 			    _(s->name), format_char, fki->name);
 		d++;
 		continue;
 	      }
 	    if (pedantic)
@@ -2167,11 +2222,13 @@  check_format_info_main (format_check_res
 	    ++format_chars;
 	  while (*format_chars && *format_chars != ']')
 	    ++format_chars;
 	  if (*format_chars != ']')
 	    /* The end of the format string was reached.  */
-	    warning_at (format_string_loc, OPT_Wformat_,
+	    warning_at (location_from_offset (format_string_loc,
+					      format_chars - orig_format_chars),
+			OPT_Wformat_,
 			"no closing %<]%> for %<%%[%> format");
 	}
 
       wanted_type = 0;
       wanted_type_name = 0;
@@ -2181,12 +2238,15 @@  check_format_info_main (format_check_res
 			 ? *fci->types[length_chars_val].type : 0);
 	  wanted_type_name = fci->types[length_chars_val].name;
 	  wanted_type_std = fci->types[length_chars_val].std;
 	  if (wanted_type == 0)
 	    {
-	      warning_at (format_string_loc, OPT_Wformat_,
-			  "use of %qs length modifier with %qc type character",
+	      warning_at (location_from_offset (format_string_loc,
+						format_chars - orig_format_chars),
+			  OPT_Wformat_,
+			  "use of %qs length modifier with %qc type character"
+			  " has either no effect or undefined behavior",
 			  length_chars, format_char);
 	      /* Heuristic: skip one argument when an invalid length/type
 		 combination is encountered.  */
 	      arg_num++;
 	      if (params != 0)
@@ -2199,11 +2259,13 @@  check_format_info_main (format_check_res
 		      have been warned for.  */
 		   && ADJ_STD (wanted_type_std) > ADJ_STD (length_chars_std)
 		   && ADJ_STD (wanted_type_std) > ADJ_STD (fci->std))
 	    {
 	      if (ADJ_STD (wanted_type_std) > C_STD_VER)
-		warning_at (format_string_loc, OPT_Wformat_,
+		warning_at (location_from_offset (format_string_loc,
+						  format_chars - orig_format_chars),
+			    OPT_Wformat_,
 			    "%s does not support the %<%%%s%c%> %s format",
 			    C_STD_NAME (wanted_type_std), length_chars,
 			    format_char, fki->name);
 	    }
 	}
@@ -2284,10 +2346,11 @@  check_format_info_main (format_check_res
               wanted_type_ptr->kind = CF_KIND_FORMAT;
 	      wanted_type_ptr->param = cur_param;
 	      wanted_type_ptr->arg_num = arg_num;
 	      wanted_type_ptr->format_start = format_start;
 	      wanted_type_ptr->format_length = format_chars - format_start;
+	      wanted_type_ptr->offset_loc = format_chars - orig_format_chars;
 	      wanted_type_ptr->next = NULL;
 	      if (last_wanted_type != 0)
 		last_wanted_type->next = wanted_type_ptr;
 	      if (first_wanted_type == 0)
 		first_wanted_type = wanted_type_ptr;
@@ -2308,11 +2371,13 @@  check_format_info_main (format_check_res
       if (first_wanted_type != 0)
         check_format_types (format_string_loc, first_wanted_type);
     }
 
   if (format_chars - orig_format_chars != format_length)
-    warning_at (format_string_loc, OPT_Wformat_contains_nul,
+    warning_at (location_from_offset (format_string_loc,
+				      format_chars + 1 - orig_format_chars),
+		OPT_Wformat_contains_nul,
 		"embedded %<\\0%> in format");
   if (info->first_arg_num != 0 && params != 0
       && has_operand_number <= 0)
     {
       res->number_other--;
@@ -2502,10 +2567,11 @@  format_type_warning (location_t loc, for
   const char *wanted_type_name = type->wanted_type_name;
   const char *format_start = type->format_start;
   int format_length = type->format_length;
   int pointer_count = type->pointer_count;
   int arg_num = type->arg_num;
+  unsigned int offset_loc = type->offset_loc;
 
   char *p;
   /* If ARG_TYPE is a typedef with a misleading name (for example,
      size_t but not the standard size_t expected by printf %zu), avoid
      printing the typedef name.  */
@@ -2535,10 +2601,12 @@  format_type_warning (location_t loc, for
       p[0] = ' ';
       memset (p + 1, '*', pointer_count);
       p[pointer_count + 1] = 0;
     }
 
+  loc = location_from_offset (loc, offset_loc);
+		      
   if (wanted_type_name)
     {
       if (arg_type)
         warning_at (loc, OPT_Wformat_,
 		    "%s %<%s%.*s%> expects argument of type %<%s%s%>, "
Index: gcc/testsuite/gcc.dg/redecl-4.c
===================================================================
--- gcc/testsuite/gcc.dg/redecl-4.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/redecl-4.c	(working copy)
@@ -13,11 +13,11 @@  f (void)
     int printf (const char *, ...);
     int strcmp ();
     /* Should get format warnings even though the built-in declaration
        isn't "visible".  */
     printf (
-	    "%s", 1); /* { dg-warning "6:format" } */
+	    "%s", 1); /* { dg-warning "8:format" } */
     /* The type of strcmp here should have no prototype.  */
     if (0)
       strcmp (1);
     /* Likewise, implicitly declared memcmp.  */
     if (0)
Index: gcc/testsuite/gcc.dg/format/bitfld-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/bitfld-1.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/bitfld-1.c	(working copy)
@@ -44,8 +44,8 @@  foo (void)
   printf ("%d%u", x.s32, x.s32);
 #else
   printf ("%ld%lu", x.u32, x.u32);
   printf ("%ld%lu", x.s32, x.s32);
 #endif
-  printf ("%llu", x.u48); /* { dg-warning "11:has type '.*unsigned int:48'" } */
+  printf ("%llu", x.u48); /* { dg-warning "15:has type '.*unsigned int:48'" } */
   printf ("%llu", (unsigned long long)x.u48);
 }
Index: gcc/testsuite/gcc.dg/format/attr-2.c
===================================================================
--- gcc/testsuite/gcc.dg/format/attr-2.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/attr-2.c	(working copy)
@@ -28,17 +28,17 @@  extern char *t__format_arg__ (const char
 
 void
 foo (int i, int *ip, double d)
 {
   tformatprintf ("%d", i);
-  tformatprintf ("%"); /* { dg-warning "18:format" "attribute format printf" } */
+  tformatprintf ("%"); /* { dg-warning "19:format" "attribute format printf" } */
   tformat__printf__ ("%d", i);
   tformat__printf__ ("%"); /* { dg-warning "format" "attribute format __printf__" } */
   tformatscanf ("%d", ip);
   tformatscanf ("%"); /* { dg-warning "format" "attribute format scanf" } */
   tformat__scanf__ ("%d", ip);
-  tformat__scanf__ ("%"); /* { dg-warning "format" "attribute format __scanf__" } */
+  tformat__scanf__ ("%"); /* { dg-warning "22:format" "attribute format __scanf__" } */
   tformatstrftime ("%a");
   tformatstrftime ("%"); /* { dg-warning "format" "attribute format strftime" } */
   tformat__strftime__ ("%a");
   tformat__strftime__ ("%"); /* { dg-warning "format" "attribute format __strftime__" } */
   tformatstrfmon ("%n", d);
Index: gcc/testsuite/gcc.dg/format/attr-6.c
===================================================================
--- gcc/testsuite/gcc.dg/format/attr-6.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/attr-6.c	(working copy)
@@ -15,8 +15,8 @@ 
    of it as a built-in function.  */
 
 void
 foo (const char *s, int *p)
 {
-  scanf("%ld", p); /* { dg-warning "9:format" "implicit scanf" } */
+  scanf("%ld", p); /* { dg-warning "12:format" "implicit scanf" } */
   /* { dg-warning "implicit" "implicit decl warning" { target *-*-* } 20 } */
 }
Index: gcc/testsuite/gcc.dg/format/attr-7.c
===================================================================
--- gcc/testsuite/gcc.dg/format/attr-7.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/attr-7.c	(working copy)
@@ -16,11 +16,11 @@  char * (__attribute__((format_arg(1))) *
 void
 baz (int i)
 {
   (*tformatprintf0) ("%d", i);
   (*tformatprintf0) ((*tformat_arg) ("%d"), i);
-  (*tformatprintf0) ("%"); /* { dg-warning "22:format" "prefix" } */
+  (*tformatprintf0) ("%"); /* { dg-warning "23:format" "prefix" } */
   (*tformatprintf0) ((*tformat_arg) ("%")); /* { dg-warning "format" "prefix" } */
   (*tformatprintf1) ("%d", i);
   (*tformatprintf1) ((*tformat_arg) ("%d"), i);
   (*tformatprintf1) ("%"); /* { dg-warning "format" "postfix" } */
   (*tformatprintf1) ((*tformat_arg) ("%")); /* { dg-warning "format" "postfix" } */
Index: gcc/testsuite/gcc.dg/format/asm_fprintf-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/asm_fprintf-1.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/asm_fprintf-1.c	(working copy)
@@ -40,41 +40,42 @@  foo (int i, int i1, int i2, unsigned int
   asm_fprintf ("%O%R%I%L%U%@");
   asm_fprintf ("%r", i);
   asm_fprintf ("%wd%wi%wo%wu%wx%wX", ll, ll, ull, ull, ull, ull);
 
   /* Standard specifiers not accepted in asm_fprintf.  */
-  asm_fprintf ("%f\n", d); /* { dg-warning "16:format" "float" } */
-  asm_fprintf ("%e\n", d); /* { dg-warning "16:format" "float" } */
-  asm_fprintf ("%E\n", d); /* { dg-warning "16:format" "float" } */
-  asm_fprintf ("%g\n", d); /* { dg-warning "16:format" "float" } */
-  asm_fprintf ("%G\n", d); /* { dg-warning "16:format" "float" } */
-  asm_fprintf ("%p\n", p); /* { dg-warning "16:format" "pointer" } */
-  asm_fprintf ("%n\n", n); /* { dg-warning "16:format" "counter" } */
-  asm_fprintf ("%hd\n", i); /* { dg-warning "16:format" "conversion" } */
+  asm_fprintf ("%f\n", d); /* { dg-warning "18:format" "float" } */
+  asm_fprintf ("%e\n", d); /* { dg-warning "18:format" "float" } */
+  asm_fprintf ("%E\n", d); /* { dg-warning "18:format" "float" } */
+  asm_fprintf ("%g\n", d); /* { dg-warning "18:format" "float" } */
+  asm_fprintf ("%G\n", d); /* { dg-warning "18:format" "float" } */
+  asm_fprintf ("%p\n", p); /* { dg-warning "18:format" "pointer" } */
+  asm_fprintf ("%n\n", n); /* { dg-warning "18:format" "counter" } */
+  asm_fprintf ("%hd\n", i); /* { dg-warning "18:format" "conversion" } */
 
   /* Various tests of bad argument types.  */
-  asm_fprintf ("%d", l); /* { dg-warning "16:format" "bad argument types" } */
-  asm_fprintf ("%wd", l); /* { dg-warning "16:format" "bad argument types" } */
-  asm_fprintf ("%d", ll); /* { dg-warning "16:format" "bad argument types" } */
-  asm_fprintf ("%*d\n", i1, i); /* { dg-warning "16:format" "bad * argument types" } */
-  asm_fprintf ("%.*d\n", i2, i); /* { dg-warning "16:format" "bad * argument types" } */
-  asm_fprintf ("%*.*ld\n", i1, i2, l); /* { dg-warning "16:format" "bad * argument types" } */
-  asm_fprintf ("%ld", i); /* { dg-warning "16:format" "bad argument types" } */
-  asm_fprintf ("%s", n); /* { dg-warning "16:format" "bad argument types" } */
+  asm_fprintf ("%d", l); /* { dg-warning "18:format" "bad argument types" } */
+  asm_fprintf ("%wd", l); /* { dg-warning "19:format" "bad argument types" } */
+  asm_fprintf ("%d", ll); /* { dg-warning "18:format" "bad argument types" } */
+  asm_fprintf ("%*d\n", i1, i); /* { dg-warning "18:format" "bad * argument types" } */
+  asm_fprintf ("%.*d\n", i2, i); /* { dg-warning "19:format" "bad * argument types" } */
+  asm_fprintf ("%*.*ld\n", i1, i2, l); /* { dg-warning "18:format" "bad * argument types" } */
+  asm_fprintf ("%ld", i); /* { dg-warning "19:format" "bad argument types" } */
+  asm_fprintf ("%s", n); /* { dg-warning "18:format" "bad argument types" } */
 
   /* Wrong number of arguments.  */
-  asm_fprintf ("%d%d", i); /* { dg-warning "16:matching" "wrong number of args" } */
+  asm_fprintf ("%d%d", i); /* { dg-warning "20:matching" "wrong number of args" } */
   asm_fprintf ("%d", i, i); /* { dg-warning "16:arguments" "wrong number of args" } */
   /* Miscellaneous bogus constructions.  */
   asm_fprintf (""); /* { dg-warning "16:zero-length" "warning for empty format" } */
-  asm_fprintf ("\0"); /* { dg-warning "16:embedded" "warning for embedded NUL" } */
-  asm_fprintf ("%d\0", i); /* { dg-warning "16:embedded" "warning for embedded NUL" } */
-  asm_fprintf ("%d\0%d", i, i); /* { dg-warning "16:embedded|too many" "warning for embedded NUL" } */
+  asm_fprintf ("\0"); /* { dg-warning "17:embedded" "warning for embedded NUL" } */
+  asm_fprintf ("%d\0", i); /* { dg-warning "19:embedded" "warning for embedded NUL" } */
+  asm_fprintf ("%d\0%d", i, i); /* { dg-warning "19:embedded|too many" "warning for embedded NUL" } */
   asm_fprintf (NULL); /* { dg-warning "null" "null format string warning" } */
-  asm_fprintf ("%"); /* { dg-warning "16:trailing" "trailing % warning" } */
-  asm_fprintf ("%++d", i); /* { dg-warning "16:repeated" "repeated flag warning" } */
+  asm_fprintf ("%"); /* { dg-warning "17:trailing" "trailing % warning" } */
+  asm_fprintf ("%++d", i); /* { dg-warning "19:repeated" "repeated flag warning" } */
   asm_fprintf ((const char *)L"foo"); /* { dg-warning "30:wide" "wide string" } */
   asm_fprintf ("%s", (char *)0); /* { dg-warning "null" "%s with NULL" } */
 
   /* Make sure we still get warnings for regular printf.  */
-  printf ("%d\n", ll); /* { dg-warning "11:format" "bad argument types" } */
+  printf ("%d\n", ll); /* { dg-warning "13:format" "bad argument types" } */
 }
+/* { dg-warning "16:too many arguments for format" "too many arguments" { target *-*-* } 0 } */
Index: gcc/testsuite/gcc.dg/format/attr-4.c
===================================================================
--- gcc/testsuite/gcc.dg/format/attr-4.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/attr-4.c	(working copy)
@@ -14,11 +14,11 @@  extern __attribute__((noreturn)) void ba
 
 void
 baz (int i, int *ip, double d)
 {
   tformatprintf0 ("%d", i);
-  tformatprintf0 ("%"); /* { dg-warning "19:format" "attribute format printf case 0" } */
+  tformatprintf0 ("%"); /* { dg-warning "20:format" "attribute format printf case 0" } */
   tformatprintf1 ("%d", i);
   tformatprintf1 ("%"); /* { dg-warning "format" "attribute format printf case 1" } */
   tformatprintf2 ("%d", i);
   tformatprintf2 ("%"); /* { dg-warning "format" "attribute format printf case 2" } */
   tformatprintf3 ("%d", i);
Index: gcc/testsuite/gcc.dg/format/branch-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/branch-1.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/branch-1.c	(working copy)
@@ -7,22 +7,22 @@ 
 
 void
 foo (long l, int nfoo)
 {
   printf ((nfoo > 1) ? "%d foos" : "%d foo", nfoo);
-  printf ((l > 1) ? "%d foos" /* { dg-warning "21:int" "wrong type in conditional expr" } */
-	          : "%d foo", l); /* { dg-warning "14:int" "wrong type in conditional expr" } */
-  printf ((l > 1) ? "%ld foos" : "%d foo", l); /* { dg-warning "34:int" "wrong type in conditional expr" } */
-  printf ((l > 1) ? "%d foos" : "%ld foo", l); /* { dg-warning "21:int" "wrong type in conditional expr" } */
+  printf ((l > 1) ? "%d foos" /* { dg-warning "23:int" "wrong type in conditional expr" } */
+	          : "%d foo", l); /* { dg-warning "16:int" "wrong type in conditional expr" } */
+  printf ((l > 1) ? "%ld foos" : "%d foo", l); /* { dg-warning "36:int" "wrong type in conditional expr" } */
+  printf ((l > 1) ? "%d foos" : "%ld foo", l); /* { dg-warning "23:int" "wrong type in conditional expr" } */
   /* Should allow one case to have extra arguments.  */
   printf ((nfoo > 1) ? "%d foos" : "1 foo", nfoo);
   printf ((nfoo > 1) ? "many foos" : "1 foo", nfoo); /* { dg-warning "38:too many" "too many args in all branches" } */
   printf ((nfoo > 1) ? "%d foos" : "", nfoo);
   printf ((nfoo > 1) ? "%d foos" : ((nfoo > 0) ? "1 foo" : "no foos"), nfoo);
   printf ((nfoo > 1) ? "%d foos" : ((nfoo > 0) ? "%d foo" : "%d foos"), nfoo);
-  printf ((nfoo > 1) ? "%d foos" : ((nfoo > 0) ? "%d foo" : "%ld foos"), nfoo); /* { dg-warning "61:long int" "wrong type" } */
-  printf ((nfoo > 1) ? "%ld foos" : ((nfoo > 0) ? "%d foo" : "%d foos"), nfoo); /* { dg-warning "24:long int" "wrong type" } */
-  printf ((nfoo > 1) ? "%d foos" : ((nfoo > 0) ? "%ld foo" : "%d foos"), nfoo); /* { dg-warning "50:long int" "wrong type" } */
+  printf ((nfoo > 1) ? "%d foos" : ((nfoo > 0) ? "%d foo" : "%ld foos"), nfoo); /* { dg-warning "64:long int" "wrong type" } */
+  printf ((nfoo > 1) ? "%ld foos" : ((nfoo > 0) ? "%d foo" : "%d foos"), nfoo); /* { dg-warning "27:long int" "wrong type" } */
+  printf ((nfoo > 1) ? "%d foos" : ((nfoo > 0) ? "%ld foo" : "%d foos"), nfoo); /* { dg-warning "53:long int" "wrong type" } */
   /* Extra arguments to NULL should be complained about.  */
   printf (0, "foo"); /* { dg-warning "14:too many" "NULL extra args" } */
   /* { dg-warning "null" "null format arg" { target *-*-* } 26 } */
 }
Index: gcc/testsuite/gcc.dg/format/c90-printf-1.c
===================================================================
--- gcc/testsuite/gcc.dg/format/c90-printf-1.c	(revision 217191)
+++ gcc/testsuite/gcc.dg/format/c90-printf-1.c	(working copy)
@@ -32,32 +32,39 @@  foo (int i, int i1, int i2, unsigned int
   /* GCC has objected to the next one in the past, but it is a valid way
      of specifying zero precision.
   */
   printf ("%.e\n", d); /* { dg-bogus "precision" "bogus precision warning" } */
   /* Bogus use of width.  */
-  printf ("%5n\n", n); /* { dg-warning "11:width" "width with %n" } */
+  printf ("%5n\n", n); /* { dg-warning "14:width" "width with %n" } */
   /* Erroneous, ignored or pointless constructs with precision.  */
   /* Whether negative values for precision may be included in the format
      string is not entirely clear; presume not, following Clive Feather's
      proposed resolution to DR#220 against C99.  In any case, such a
      construct should be warned about.
   */
-  printf ("%.-5d\n", i); /* { dg-warning "11:format|precision" "negative precision warning" } */
-  printf ("%.-*d\n", i); /* { dg-warning "11:format" "broken %.-*d format" } */
-  printf ("%.3c\n", i); /* { dg-warning "11:precision" "precision with %c" } */
-  printf ("%.3p\n", p); /* { dg-warning "11:precision" "precision with %p" } */
-  printf ("%.3n\n", n); /* { dg-warning "11:precision" "precision with %n" } */
+  printf ("%.-5d\n", i); /* { dg-warning "14:format|precision" "negative precision warning" } */
+  /* { dg-warning "too many arguments for format" "too many arguments" { target *-*-* } 44 } */
+  printf ("%.-*d\n", i); /* { dg-warning "14:format" "broken %.-*d format" } */
+  /* { dg-warning "too many arguments for format" "too many arguments" { target *-*-* } 46 } */
+  printf ("%.3c\n", i); /* { dg-warning "15:precision" "precision with %c" } */
+  printf ("%.3p\n", p); /* { dg-warning "15:precision" "precision with %p" } */
+  printf ("%.3n\n", n); /* { dg-warning "15:precision" "precision with %n" } */
   /* Valid and invalid %% constructions.  Some of the warning messages
      are non-optimal, but they do detect the errorneous nature of the
      format string.
   */
   printf ("%%");
-  printf ("%.3%"); /* { dg-warning "11:format" "bogus %%" } */
-  printf ("%-%"); /* { dg-warning "11:format" "bogus %%" } */
-  printf ("%-%\n"); /* { dg-warning "11:format" "bogus %%" } */
-  printf ("%5%\n"); /* { dg-warning "11:format" "bogus %%" } */
-  printf ("%h%\n"); /* { dg-warning "11:format" "bogus %%" } */
+  printf ("%.3%"); /* { dg-warning "14:type" "missing type" } */
+  /* { dg-warning "15:trailing" "bogus %%" { target *-*-* } 56 } */
+  printf ("%-%"); /* { dg-warning "13:type" "missing type" } */
+  /* { dg-warning "14:trailing" "bogus %%" { target *-*-* } 58 } */
+  printf ("%-%\n"); /* { dg-warning "13:format" "bogus %%" } */
+  /* { dg-warning "15:format" "bogus %%" { target *-*-* } 60 } */
+  printf ("%5%\n"); /* { dg-warning "13:format" "bogus %%" } */
+  /* { dg-warning "15:format" "bogus %%" { target *-*-* } 62 } */
+  printf ("%h%\n"); /* { dg-warning "13:format" "bogus %%" } */
+  /* { dg-warning "15:format" "bogus %%" { target *-*-* } 64 } */
   /* Valid and invalid %h, %l, %L constructions.  */
   printf ("%hd", i);
   printf ("%hi", i);
   /* Strictly, these parameters should be int or unsigned int according to
      what unsigned short promotes to.  However, GCC ignores sign
@@ -65,98 +72,98 @@  foo (int i, int i1, int i2, unsigned int
      correct checking without print_char_table needing to know whether
      int and short are the same size.
   */
   printf ("%ho%hu%hx%hX", u, u, u, u);
   printf ("%hn", hn);
-  printf ("%hf", d); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%he", d); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%hE", d); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%hg", d); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%hG", d); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%hc", i); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%hs", s); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%hp", p); /* { dg-warning "11:length" "bad use of %h" } */
-  printf ("%h"); /* { dg-warning "11:conversion lacks type" "bare %h" } */
-  printf ("%h."); /* { dg-warning "11:conversion" "bogus %h." } */
-  printf ("%ld%li%lo%lu%lx%lX", l, l, ul, ul, ul, ul);
-  printf ("%ln", ln);
-  printf ("%lf", d); /* { dg-warning "11:length|C" "bad use of %l" } */
-  printf ("%le", d); /* { dg-warning "11:length|C" "bad use of %l" } */
-  printf ("%lE", d); /* { dg-warning "11:length|C" "bad use of %l" } */
-  printf ("%lg", d); /* { dg-warning "11:length|C" "bad use of %l" } */
-  printf ("%lG", d); /* { dg-warning "11:length|C" "bad use of %l" } */
-  printf ("%lp", p); /* { dg-warning "11:length|C" "bad use of %l" } */
+  printf (" %hf", d); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %he", d); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %hE", d); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %hg", d); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %hG", d); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %hc", i); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %hs", s); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %hp", p); /* { dg-warning "15:length" "bad use of %h" } */
+  printf (" %h"); /* { dg-warning "14:conversion lacks type" "bare %h" } */
+  printf (" %h."); /* { dg-warning "15:conversion" "bogus %h." } */
+  printf (" %ld%li%lo%lu%lx%lX", l, l, ul, ul, ul, ul);
+  printf (" %ln", ln);
+  printf (" %lf", d); /* { dg-warning "15:length|C" "bad use of %l" } */
+  printf (" %le", d); /* { dg-warning "15:length|C" "bad use of %l" } */
+  printf (" %lE", d); /* { dg-warning "15:length|C" "bad use of %l" } */
+  printf (" %lg", d); /* { dg-warning "15:length|C" "bad use of %l" } */
+  printf (" %lG", d); /* { dg-warning "15:length|C" "bad use of %l" } */
+  printf (" %lp", p); /* { dg-warning "15:length|C" "bad use of %l" } */
   /* These next two were added in C94, but should be objected to in C90.
      For the first one, GCC has wanted wchar_t instead of the correct C94
      and C99 wint_t.
   */
-  printf ("%lc", lc); /* { dg-warning "11:length|C" "C90 bad use of %l" } */
-  printf ("%ls", ls); /* { dg-warning "11:length|C" "C90 bad use of %l" } */
+  printf ("%lc", lc); /* { dg-warning "14:length|C" "C90 bad use of %l" } */
+  printf ("%ls", ls); /* { dg-warning "14:length|C" "C90 bad use of %l" } */
   /* These uses of %L are legitimate, though GCC has wrongly warned for
      them in the past.
   */
   printf ("%Le%LE%Lf%Lg%LG", ld, ld, ld, ld, ld);
   /* These next six are accepted by GCC as referring to long long,
      but -pedantic correctly warns.
   */
-  printf ("%Ld", ll); /* { dg-warning "11:does not support" "bad use of %L" } */
-  printf ("%Li", ll); /* { dg-warning "11:does not support" "bad use of %L" } */
-  printf ("%Lo", ull); /* { dg-warning "11:does not support" "bad use of %L" } */
-  printf ("%Lu", ull); /* { dg-warning "11:does not support" "bad use of %L" } */
-  printf ("%Lx", ull); /* { dg-warning "11:does not support" "bad use of %L" } */
-  printf ("%LX", ull); /* { dg-warning "11:does not support" "bad use of %L" } */
-  printf ("%Lc", i); /* { dg-warning "11:length" "bad use of %L" } */
-  printf ("%Ls", s); /* { dg-warning "11:length" "bad use of %L" } */
-  printf ("%Lp", p); /* { dg-warning "11:length" "bad use of %L" } */
-  printf ("%Ln", n); /* { dg-warning "11:length" "bad use of %L" } */
+  printf ("%Ld", ll); /* { dg-warning "14:does not support" "bad use of %L" } */
+  printf ("%Li", ll); /* { dg-warning "14:does not support" "bad use of %L" } */
+  printf ("%Lo", ull); /* { dg-warning "14:does not support" "bad use of %L" } */
+  printf ("%Lu", ull); /* { dg-warning "14:does not support" "bad use of %L" } */
+  printf ("%Lx", ull); /* { dg-warning "14:does not support" "bad use of %L" } */
+  printf ("%LX", ull); /* { dg-warning "14:does not support" "bad use of %L" } */
+  printf ("%Lc", i); /* { dg-warning "14:length" "bad use of %L" } */
+  printf ("%Ls", s); /* { dg-warning "14:length" "bad use of %L" } */
+  printf ("%Lp", p); /* { dg-warning "14:length" "bad use of %L" } */
+  printf ("%Ln", n); /* { dg-warning "14:length" "bad use of %L" } */
   /* Valid uses of each bare conversion.  */
   printf ("%d%i%o%u%x%X%f%e%E%g%G%c%s%p%n%%", i, i, u, u, u, u, d, d, d, d, d,
 	  i, s, p, n);
   /* Uses of the - flag (valid on all non-%, non-n conversions).  */
   printf ("%-d%-i%-o%-u%-x%-X%-f%-e%-E%-g%-G%-c%-s%-p", i, i, u, u, u, u,
 	  d, d, d, d, d, i, s, p);
-  printf ("%-n", n); /* { dg-warning "11:flag" "bad use of %-n" } */
+  printf ("%-n", n); /* { dg-warning "14:flag" "bad use of %-n" } */
   /* Uses of the + flag (valid on signed conversions only).  */
   printf ("%+d%+i%+f%+e%+E%+g%+G\n", i, i, d, d, d, d, d);
-  printf ("%+o", u); /* { dg-warning "11:flag" "bad use of + flag" } */
-  printf ("%+u", u); /* { dg-warning "11:flag" "bad use of + flag" } */
-  printf ("%+x", u); /* { dg-warning "11:flag" "bad use of + flag" } */
-  printf ("%+X", u); /* { dg-warning "11:flag" "bad use of + flag" } */
-  printf ("%+c", i); /* { dg-warning "11:flag" "bad use of + flag" } */
-  printf ("%+s", s); /* { dg-warning "11:flag" "bad use of + flag" } */
-  printf ("%+p", p); /* { dg-warning "11:flag" "bad use of + flag" } */
-  printf ("%+n", n); /* { dg-warning "11:flag" "bad use of + flag" } */
+  printf ("%+o", u); /* { dg-warning "14:flag" "bad use of + flag" } */
+  printf ("%+u", u); /* { dg-warning "14:flag" "bad use of + flag" } */
+  printf ("%+x", u); /* { dg-warning "14:flag" "bad use of + flag" } */
+  printf ("%+X", u); /* { dg-warning "14:flag" "bad use of + flag" } */
+  printf ("%+c", i); /* { dg-warning "14:flag" "bad use of + flag" } */
+  printf ("%+s", s); /* { dg-warning "14:flag" "bad use of + flag" } */
+  printf ("%+p", p); /* { dg-warning "14:flag" "bad use of + flag" } */
+  printf ("%+n", n); /* { dg-warning "14:flag" "bad use of + flag" } */
   /* Uses of the space flag (valid on signed conversions only, and ignored
      with +).
   */
   printf ("% +d", i); /* { dg-warning "11:use of both|ignored" "use of space and + flags" } */
   printf ("%+ d", i); /* { dg-warning "11:use of both|ignored" "use of space and + flags" } */
   printf ("% d% i% f% e% E% g% G\n", i, i, d, d, d, d, d);
-  printf ("% o", u); /* { dg-warning "11:flag" "bad use of space flag" } */
-  printf ("% u", u); /* { dg-warning "11:flag" "bad use of space flag" } */
-  printf ("% x", u); /* { dg-warning "11:flag" "bad use of space flag" } */
-  printf ("% X", u); /* { dg-warning "11:flag" "bad use of space flag" } */
-  printf ("% c", i); /* { dg-warning "11:flag" "bad use of space flag" } */
-  printf ("% s", s); /* { dg-warning "11:flag" "bad use of space flag" } */
-  printf ("% p", p); /* { dg-warning "11:flag" "bad use of space flag" } */
-  printf ("% n", n); /* { dg-warning "11:flag" "bad use of space flag" } */
+  printf ("% o", u); /* { dg-warning "14:flag" "bad use of space flag" } */
+  printf ("% u", u); /* { dg-warning "14:flag" "bad use of space flag" } */
+  printf ("% x", u); /* { dg-warning "14:flag" "bad use of space flag" } */
+  printf ("% X", u); /* { dg-warning "14:flag" "bad use of space flag" } */
+  printf ("% c", i); /* { dg-warning "14:flag" "bad use of space flag" } */
+  printf ("% s", s); /* { dg-warning "14:flag" "bad use of space flag" } */
+  printf ("% p", p); /* { dg-warning "14:flag" "bad use of space flag" } */
+  printf ("% n", n); /* { dg-warning "14:flag" "bad use of space flag" } */
   /* Uses of the # flag.  */
   printf ("%#o%#x%#X%#e%#E%#f%#g%#G", u, u, u, d, d, d, d, d);
-  printf ("%#d", i); /* { dg-warning "11:flag" "bad use of # flag" } */
-  printf ("%#i", i); /* { dg-warning "11:flag" "bad use of # flag" } */
-  printf ("%#u", u); /* { dg-warning "11:flag" "bad use of # flag" } */
-  printf ("%#c", i); /* { dg-warning "11:flag" "bad use of # flag" } */
-  printf ("%#s", s); /* { dg-warning "11:flag" "bad use of # flag" } */
-  printf ("%#p", p); /* { dg-warning "11:flag" "bad use of # flag" } */
-  printf ("%#n", n); /* { dg-warning "11:flag" "bad use of # flag" } */
+  printf ("%#d", i); /* { dg-warning "14:flag" "bad use of # flag" } */
+  printf ("%#i", i); /* { dg-warning "14:flag" "bad use of # flag" } */
+  printf ("%#u", u); /* { dg-warning "14:flag" "bad use of # flag" } */
+  printf ("%#c", i); /* { dg-warning "14:flag" "bad use of # flag" } */
+  printf ("%#s", s); /* { dg-warning "14:flag" "bad use of # flag" } */
+  printf ("%#p", p); /* { dg-warning "14:flag" "bad use of # flag" } */
+  printf ("%#n", n); /* { dg-warning "14:flag" "bad use of # flag" } */
   /* Uses of the 0 flag.  */
   printf ("%08d%08i%08o%08u%08x%08X%08e%08E%08f%08g%08G", i, i, u, u, u, u,
 	  d, d, d, d, d);
-  printf ("%0c", i); /* { dg-warning "11:flag" "bad use of 0 flag" } */
-  printf ("%0s", s); /* { dg-warning "11:flag" "bad use of 0 flag" } */
-  printf ("%0p", p); /* { dg-warning "11:flag" "bad use of 0 flag" } */
-  printf ("%0n", n); /* { dg-warning "11:flag" "bad use of 0 flag" } */
+  printf ("%0c", i); /* { dg-warning "14:flag" "bad use of 0 flag" } */
+  printf ("%0s", s); /* { dg-warning "14:flag" "bad use of 0 flag" } */
+  printf ("%0p", p); /* { dg-warning "14:flag" "bad use of 0 flag" } */
+  printf ("%0n", n); /* { dg-warning "14:flag" "bad use of 0 flag" } */
   /* 0 flag ignored with precision for certain types, not others.  */
   printf ("%08.5d", i); /* { dg-warning "11:ignored" "0 flag ignored with precision" } */
   printf ("%08.5i", i); /* { dg-warning "11:ignored" "0 flag ignored with precision" } */
   printf ("%08.5o", u); /* { dg-warning "11:ignored" "0 flag ignored with precision" } */
   printf ("%08.5u", u); /* { dg-warning "11:ignored" "0 flag ignored with precision" } */
@@ -174,17 +181,17 @@  foo (int i, int i1, int i2, unsigned int
   printf ("%-08E", d); /* { dg-warning "11:flags|ignored" "0 flag ignored with - flag" } */
   printf ("%-08f", d); /* { dg-warning "11:flags|ignored" "0 flag ignored with - flag" } */
   printf ("%-08g", d); /* { dg-warning "11:flags|ignored" "0 flag ignored with - flag" } */
   printf ("%-08G", d); /* { dg-warning "11:flags|ignored" "0 flag ignored with - flag" } */
   /* Various tests of bad argument types.  */
-  printf ("%d", l); /* { dg-warning "11:format" "bad argument types" } */
-  printf ("%*.*d", l, i2, i); /* { dg-warning "11:field" "bad * argument types" } */
-  printf ("%*.*d", i1, l, i); /* { dg-warning "11:field" "bad * argument types" } */
-  printf ("%ld", i); /* { dg-warning "11:format" "bad argument types" } */
-  printf ("%s", n); /* { dg-warning "11:format" "bad argument types" } */
-  printf ("%p", i); /* { dg-warning "11:format" "bad argument types" } */
-  printf ("%n", p); /* { dg-warning "11:format" "bad argument types" } */
+  printf ("%d", l); /* { dg-warning "13:format" "bad argument types" } */
+  printf ("%*.*d", l, i2, i); /* { dg-warning "13:field" "bad * argument types" } */
+  printf ("%*.*d", i1, l, i); /* { dg-warning "15:field" "bad * argument types" } */
+  printf ("%ld", i); /* { dg-warning "14:format" "bad argument types" } */
+  printf ("%s", n); /* { dg-warning "13:format" "bad argument types" } */
+  printf ("%p", i); /* { dg-warning "13:format" "bad argument types" } */
+  printf ("%n", p); /* { dg-warning "13:format" "bad argument types" } */
   /* With -pedantic, we want some further checks for pointer targets:
      %p should allow only pointers to void (possibly qualified) and
      to character types (possibly qualified), but not function pointers
      or pointers to other types.  (Whether, in fact, character types are
      allowed here is unclear; see thread on comp.std.c, July 2000 for
@@ -194,22 +201,22 @@  foo (int i, int i1, int i2, unsigned int
      pointer targets differ in signedness, except in some circumstances
      for character pointers.  (In C99 we should consider warning for
      char * or unsigned char * being passed to %hhn, even if strictly
      legitimate by the standard.)
   */
-  printf ("%p", foo); /* { dg-warning "11:format" "bad argument types" } */
-  printf ("%n", un); /* { dg-warning "11:format" "bad argument types" } */
-  printf ("%p", n); /* { dg-warning "11:format" "bad argument types" } */
+  printf ("%p", foo); /* { dg-warning "13:format" "bad argument types" } */
+  printf ("%n", un); /* { dg-warning "13:format" "bad argument types" } */
+  printf ("%p", n); /* { dg-warning "13:format" "bad argument types" } */
   /* Allow character pointers with %p.  */
   printf ("%p%p%p%p", s, ss, us, css);
   /* %s allows any character type.  */
   printf ("%s%s%s%s", s, ss, us, css);
   /* Warning for void * arguments for %s is GCC's historical behavior,
      and seems useful to keep, even if some standard versions might be
      read to permit it.
   */
-  printf ("%s", p); /* { dg-warning "11:format" "bad argument types" } */
+  printf ("%s", p); /* { dg-warning "13:format" "bad argument types" } */
   /* The historical behavior is to allow signed / unsigned types
      interchangeably as arguments.  For values representable in both types,
      such usage may be correct.  For now preserve the behavior of GCC
      in such cases.
   */
@@ -218,20 +225,20 @@  foo (int i, int i1, int i2, unsigned int
      GCC has been inconsistent and allowed unsigned for width but not
      precision.
   */
   printf ("%*.*d", u1, u2, i);
   /* Wrong number of arguments.  */
-  printf ("%d%d", i); /* { dg-warning "11:matching" "wrong number of args" } */
+  printf ("%d%d", i); /* { dg-warning "15:matching" "wrong number of args" } */
   printf ("%d", i, i); /* { dg-warning "11:arguments" "wrong number of args" } */
   /* Miscellaneous bogus constructions.  */
   printf (""); /* { dg-warning "11:zero-length" "warning for empty format" } */
-  printf ("\0"); /* { dg-warning "11:embedded" "warning for embedded NUL" } */
-  printf ("%d\0", i); /* { dg-warning "11:embedded" "warning for embedded NUL" } */
-  printf ("%d\0%d", i, i); /* { dg-warning "11:embedded|too many" "warning for embedded NUL" } */
+  printf ("\0"); /* { dg-warning "12:embedded" "warning for embedded NUL" } */
+  printf ("%d\0", i); /* { dg-warning "14:embedded" "warning for embedded NUL" } */
+  printf ("%d\0%d", i, i); /* { dg-warning "embedded|too many" "warning for embedded NUL" } */
   printf (NULL); /* { dg-warning "3:null" "null format string warning" } */
-  printf ("%"); /* { dg-warning "11:trailing" "trailing % warning" } */
-  printf ("%++d", i); /* { dg-warning "11:repeated" "repeated flag warning" } */
+  printf ("%"); /* { dg-warning "12:trailing" "trailing % warning" } */
+  printf ("%++d", i); /* { dg-warning "14:repeated" "repeated flag warning" } */
   printf ("%n", cn); /* { dg-warning "3:constant" "%n with const" } */
   printf ((const char *)L"foo"); /* { dg-warning "25:wide" "wide string" } */
   printf ("%n", (int *)0); /* { dg-warning "3:null" "%n with NULL" } */
   printf ("%s", (char *)0); /* { dg-warning "3:null" "%s with NULL" } */
 }
Index: libcpp/include/line-map.h
===================================================================
--- libcpp/include/line-map.h	(revision 217191)
+++ libcpp/include/line-map.h	(working copy)
@@ -601,10 +601,18 @@  linemap_position_for_column (struct line
    column.  */
 source_location
 linemap_position_for_line_and_column (const struct line_map *,
 				      linenum_type, unsigned int);
 
+/* Encode and return a source_location starting from location LOC and
+   shifting it by OFFSET columns.  This function does not support
+   virtual locations.  */
+source_location
+linemap_position_for_loc_and_offset (struct line_maps *set,
+				     source_location loc,
+				     unsigned int offset);
+
 /* Return the file this map is for.  */
 #define LINEMAP_FILE(MAP)					\
   (linemap_check_ordinary (MAP)->d.ordinary.to_file)
 
 /* Return the line number this map started encoding location from.  */
Index: libcpp/line-map.c
===================================================================
--- libcpp/line-map.c	(revision 217191)
+++ libcpp/line-map.c	(working copy)
@@ -631,10 +631,54 @@  linemap_position_for_line_and_column (co
 	  + ((line - ORDINARY_MAP_STARTING_LINE_NUMBER (map))
 	     << ORDINARY_MAP_NUMBER_OF_COLUMN_BITS (map))
 	  + (column & ((1 << ORDINARY_MAP_NUMBER_OF_COLUMN_BITS (map)) - 1)));
 }
 
+/* Encode and return a source_location starting from location LOC and
+   shifting it by OFFSET columns.  This function does not support
+   virtual locations.  */
+
+source_location
+linemap_position_for_loc_and_offset (struct line_maps *set,
+				     source_location loc,
+				     unsigned int offset)
+{
+  const struct line_map * map = NULL;
+
+  /* This function does not support virtual locations yet.  */
+  linemap_assert (!linemap_location_from_macro_expansion_p (set, loc));
+
+  if (offset == 0
+      /* Adding an offset to a reserved location (like
+	 UNKNOWN_LOCATION for the C/C++ FEs) does not really make
+	 sense.  So let's live the location intact in that case.  */
+      || loc < RESERVED_LOCATION_COUNT)
+    return loc;
+
+  /* First, we find the real location and shift it.  */
+  loc = linemap_resolve_location (set, loc, LRK_SPELLING_LOCATION, &map);
+  /* The new location (loc + offset) should be higher than the first
+     location encoded by MAP.  */
+  linemap_assert (MAP_START_LOCATION (map) < loc + offset);
+
+  /* If MAP is not the last line map of its set, then the new location
+     (loc + offset) should be less than the first location encoded by
+     the next line map of the set.  */
+  if (map < LINEMAPS_LAST_ORDINARY_MAP (set))
+    linemap_assert (MAP_START_LOCATION (&map[1]) < loc + offset);
+
+  offset += SOURCE_COLUMN (map, loc);
+  linemap_assert (offset < (1u << map->d.ordinary.column_bits));
+
+  source_location r = 
+    linemap_position_for_line_and_column (map,
+					  SOURCE_LINE (map, loc),
+					  offset);
+  linemap_assert (map == linemap_lookup (set, r));
+  return r;
+}
+
 /* Given a virtual source location yielded by a map (either an
    ordinary or a macro map), returns that map.  */
 
 const struct line_map*
 linemap_lookup (struct line_maps *set, source_location line)