816 lines
24 KiB
C
816 lines
24 KiB
C
|
/* Implementation of -Wmisleading-indentation
|
||
|
Copyright (C) 2015-2021 Free Software Foundation, Inc.
|
||
|
|
||
|
This file is part of GCC.
|
||
|
|
||
|
GCC is free software; you can redistribute it and/or modify it under
|
||
|
the terms of the GNU General Public License as published by the Free
|
||
|
Software Foundation; either version 3, or (at your option) any later
|
||
|
version.
|
||
|
|
||
|
GCC is distributed in the hope that it will be useful, but WITHOUT ANY
|
||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||
|
FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
||
|
for more details.
|
||
|
|
||
|
You should have received a copy of the GNU General Public License
|
||
|
along with GCC; see the file COPYING3. If not see
|
||
|
<http://www.gnu.org/licenses/>. */
|
||
|
|
||
|
#include "config.h"
|
||
|
#include "system.h"
|
||
|
#include "coretypes.h"
|
||
|
#include "tm.h"
|
||
|
#include "c-common.h"
|
||
|
#include "c-indentation.h"
|
||
|
#include "selftest.h"
|
||
|
#include "diagnostic.h"
|
||
|
|
||
|
/* Round up VIS_COLUMN to nearest tab stop. */
|
||
|
|
||
|
static unsigned int
|
||
|
next_tab_stop (unsigned int vis_column, unsigned int tab_width)
|
||
|
{
|
||
|
vis_column = ((vis_column + tab_width) / tab_width) * tab_width;
|
||
|
return vis_column;
|
||
|
}
|
||
|
|
||
|
/* Convert libcpp's notion of a column (a 1-based char count) to
|
||
|
the "visual column" (0-based column, respecting tabs), by reading the
|
||
|
relevant line.
|
||
|
|
||
|
Returns true if a conversion was possible, writing the result to OUT,
|
||
|
otherwise returns false. If FIRST_NWS is not NULL, then write to it
|
||
|
the visual column corresponding to the first non-whitespace character
|
||
|
on the line (up to or before EXPLOC). */
|
||
|
|
||
|
static bool
|
||
|
get_visual_column (expanded_location exploc,
|
||
|
unsigned int *out,
|
||
|
unsigned int *first_nws,
|
||
|
unsigned int tab_width)
|
||
|
{
|
||
|
char_span line = location_get_source_line (exploc.file, exploc.line);
|
||
|
if (!line)
|
||
|
return false;
|
||
|
if ((size_t)exploc.column > line.length ())
|
||
|
return false;
|
||
|
unsigned int vis_column = 0;
|
||
|
for (int i = 1; i < exploc.column; i++)
|
||
|
{
|
||
|
unsigned char ch = line[i - 1];
|
||
|
|
||
|
if (first_nws != NULL && !ISSPACE (ch))
|
||
|
{
|
||
|
*first_nws = vis_column;
|
||
|
first_nws = NULL;
|
||
|
}
|
||
|
|
||
|
if (ch == '\t')
|
||
|
vis_column = next_tab_stop (vis_column, tab_width);
|
||
|
else
|
||
|
vis_column++;
|
||
|
}
|
||
|
|
||
|
if (first_nws != NULL)
|
||
|
*first_nws = vis_column;
|
||
|
|
||
|
*out = vis_column;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Attempt to determine the first non-whitespace character in line LINE_NUM
|
||
|
of source line FILE.
|
||
|
|
||
|
If this is possible, return true and write its "visual column" to
|
||
|
*FIRST_NWS.
|
||
|
Otherwise, return false, leaving *FIRST_NWS untouched. */
|
||
|
|
||
|
static bool
|
||
|
get_first_nws_vis_column (const char *file, int line_num,
|
||
|
unsigned int *first_nws,
|
||
|
unsigned int tab_width)
|
||
|
{
|
||
|
gcc_assert (first_nws);
|
||
|
|
||
|
char_span line = location_get_source_line (file, line_num);
|
||
|
if (!line)
|
||
|
return false;
|
||
|
unsigned int vis_column = 0;
|
||
|
for (size_t i = 1; i < line.length (); i++)
|
||
|
{
|
||
|
unsigned char ch = line[i - 1];
|
||
|
|
||
|
if (!ISSPACE (ch))
|
||
|
{
|
||
|
*first_nws = vis_column;
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (ch == '\t')
|
||
|
vis_column = next_tab_stop (vis_column, tab_width);
|
||
|
else
|
||
|
vis_column++;
|
||
|
}
|
||
|
|
||
|
/* No non-whitespace characters found. */
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Determine if there is an unindent/outdent between
|
||
|
BODY_EXPLOC and NEXT_STMT_EXPLOC, to ensure that we don't
|
||
|
issue a warning for cases like the following:
|
||
|
|
||
|
(1) Preprocessor logic
|
||
|
|
||
|
if (flagA)
|
||
|
foo ();
|
||
|
^ BODY_EXPLOC
|
||
|
#if SOME_CONDITION_THAT_DOES_NOT_HOLD
|
||
|
if (flagB)
|
||
|
#endif
|
||
|
bar ();
|
||
|
^ NEXT_STMT_EXPLOC
|
||
|
|
||
|
"bar ();" is visually aligned below "foo ();" and
|
||
|
is (as far as the parser sees) the next token, but
|
||
|
this isn't misleading to a human reader.
|
||
|
|
||
|
(2) Empty macro with bad indentation
|
||
|
|
||
|
In the following, the
|
||
|
"if (i > 0)"
|
||
|
is poorly indented, and ought to be on the same column as
|
||
|
"engine_ref_debug(e, 0, -1)"
|
||
|
However, it is not misleadingly indented, due to the presence
|
||
|
of that macro.
|
||
|
|
||
|
#define engine_ref_debug(X, Y, Z)
|
||
|
|
||
|
if (locked)
|
||
|
i = foo (0);
|
||
|
else
|
||
|
i = foo (1);
|
||
|
engine_ref_debug(e, 0, -1)
|
||
|
if (i > 0)
|
||
|
return 1;
|
||
|
|
||
|
Return true if such an unindent/outdent is detected. */
|
||
|
|
||
|
static bool
|
||
|
detect_intervening_unindent (const char *file,
|
||
|
int body_line,
|
||
|
int next_stmt_line,
|
||
|
unsigned int vis_column,
|
||
|
unsigned int tab_width)
|
||
|
{
|
||
|
gcc_assert (file);
|
||
|
gcc_assert (next_stmt_line > body_line);
|
||
|
|
||
|
for (int line = body_line + 1; line < next_stmt_line; line++)
|
||
|
{
|
||
|
unsigned int line_vis_column;
|
||
|
if (get_first_nws_vis_column (file, line, &line_vis_column, tab_width))
|
||
|
if (line_vis_column < vis_column)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Not found. */
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
|
||
|
/* Helper function for warn_for_misleading_indentation; see
|
||
|
description of that function below. */
|
||
|
|
||
|
static bool
|
||
|
should_warn_for_misleading_indentation (const token_indent_info &guard_tinfo,
|
||
|
const token_indent_info &body_tinfo,
|
||
|
const token_indent_info &next_tinfo)
|
||
|
{
|
||
|
/* Don't attempt to compare indentation if #line or # 44 "file"-style
|
||
|
directives are present, suggesting generated code.
|
||
|
|
||
|
All bets are off if these are present: the file that the #line
|
||
|
directive could have an entirely different coding layout to C/C++
|
||
|
(e.g. .md files).
|
||
|
|
||
|
To determine if a #line is present, in theory we could look for a
|
||
|
map with reason == LC_RENAME_VERBATIM. However, if there has
|
||
|
subsequently been a long line requiring a column number larger than
|
||
|
that representable by the original LC_RENAME_VERBATIM map, then
|
||
|
we'll have a map with reason LC_RENAME.
|
||
|
Rather than attempting to search all of the maps for a
|
||
|
LC_RENAME_VERBATIM, instead we have libcpp set a flag whenever one
|
||
|
is seen, and we check for the flag here.
|
||
|
*/
|
||
|
if (line_table->seen_line_directive)
|
||
|
return false;
|
||
|
|
||
|
/* We can't usefully warn about do-while and switch statements since the
|
||
|
bodies of these statements are always explicitly delimited at both ends,
|
||
|
so control flow is quite obvious. */
|
||
|
if (guard_tinfo.keyword == RID_DO
|
||
|
|| guard_tinfo.keyword == RID_SWITCH)
|
||
|
return false;
|
||
|
|
||
|
/* If the token following the body is a close brace or an "else"
|
||
|
then while indentation may be sloppy, there is not much ambiguity
|
||
|
about control flow, e.g.
|
||
|
|
||
|
if (foo) <- GUARD
|
||
|
bar (); <- BODY
|
||
|
else baz (); <- NEXT
|
||
|
|
||
|
{
|
||
|
while (foo) <- GUARD
|
||
|
bar (); <- BODY
|
||
|
} <- NEXT
|
||
|
baz ();
|
||
|
*/
|
||
|
enum cpp_ttype next_tok_type = next_tinfo.type;
|
||
|
if (next_tok_type == CPP_CLOSE_BRACE
|
||
|
|| next_tinfo.keyword == RID_ELSE)
|
||
|
return false;
|
||
|
|
||
|
/* Likewise, if the body of the guard is a compound statement then control
|
||
|
flow is quite visually explicit regardless of the code's possibly poor
|
||
|
indentation, e.g.
|
||
|
|
||
|
while (foo) <- GUARD
|
||
|
{ <- BODY
|
||
|
bar ();
|
||
|
}
|
||
|
baz (); <- NEXT
|
||
|
|
||
|
Things only get muddy when the body of the guard does not have
|
||
|
braces, e.g.
|
||
|
|
||
|
if (foo) <- GUARD
|
||
|
bar (); <- BODY
|
||
|
baz (); <- NEXT
|
||
|
*/
|
||
|
enum cpp_ttype body_type = body_tinfo.type;
|
||
|
if (body_type == CPP_OPEN_BRACE)
|
||
|
return false;
|
||
|
|
||
|
/* Don't warn here about spurious semicolons. */
|
||
|
if (next_tok_type == CPP_SEMICOLON)
|
||
|
return false;
|
||
|
|
||
|
location_t guard_loc = guard_tinfo.location;
|
||
|
location_t body_loc = body_tinfo.location;
|
||
|
location_t next_stmt_loc = next_tinfo.location;
|
||
|
|
||
|
/* Resolve each token location to the respective macro expansion
|
||
|
point that produced the token. */
|
||
|
if (linemap_location_from_macro_expansion_p (line_table, guard_loc))
|
||
|
guard_loc = linemap_resolve_location (line_table, guard_loc,
|
||
|
LRK_MACRO_EXPANSION_POINT, NULL);
|
||
|
if (linemap_location_from_macro_expansion_p (line_table, body_loc))
|
||
|
body_loc = linemap_resolve_location (line_table, body_loc,
|
||
|
LRK_MACRO_EXPANSION_POINT, NULL);
|
||
|
if (linemap_location_from_macro_expansion_p (line_table, next_stmt_loc))
|
||
|
next_stmt_loc = linemap_resolve_location (line_table, next_stmt_loc,
|
||
|
LRK_MACRO_EXPANSION_POINT, NULL);
|
||
|
|
||
|
/* When all three tokens are produced from a single macro expansion, we
|
||
|
instead consider their loci inside that macro's definition. */
|
||
|
if (guard_loc == body_loc && body_loc == next_stmt_loc)
|
||
|
{
|
||
|
const line_map *guard_body_common_map
|
||
|
= first_map_in_common (line_table,
|
||
|
guard_tinfo.location, body_tinfo.location,
|
||
|
&guard_loc, &body_loc);
|
||
|
const line_map *body_next_common_map
|
||
|
= first_map_in_common (line_table,
|
||
|
body_tinfo.location, next_tinfo.location,
|
||
|
&body_loc, &next_stmt_loc);
|
||
|
|
||
|
/* Punt on complicated nesting of macros. */
|
||
|
if (guard_body_common_map != body_next_common_map)
|
||
|
return false;
|
||
|
|
||
|
guard_loc = linemap_resolve_location (line_table, guard_loc,
|
||
|
LRK_MACRO_DEFINITION_LOCATION, NULL);
|
||
|
body_loc = linemap_resolve_location (line_table, body_loc,
|
||
|
LRK_MACRO_DEFINITION_LOCATION, NULL);
|
||
|
next_stmt_loc = linemap_resolve_location (line_table, next_stmt_loc,
|
||
|
LRK_MACRO_DEFINITION_LOCATION,
|
||
|
NULL);
|
||
|
}
|
||
|
|
||
|
expanded_location body_exploc = expand_location (body_loc);
|
||
|
expanded_location next_stmt_exploc = expand_location (next_stmt_loc);
|
||
|
expanded_location guard_exploc = expand_location (guard_loc);
|
||
|
|
||
|
/* PR c++/68819: if the column number is zero, we presumably
|
||
|
had a location_t > LINE_MAP_MAX_LOCATION_WITH_COLS, and so
|
||
|
we have no column information. */
|
||
|
if (!guard_exploc.column || !body_exploc.column || !next_stmt_exploc.column)
|
||
|
{
|
||
|
static bool issued_note = false;
|
||
|
if (!issued_note)
|
||
|
{
|
||
|
/* Notify the user the first time this happens. */
|
||
|
issued_note = true;
|
||
|
inform (guard_loc,
|
||
|
"%<-Wmisleading-indentation%> is disabled from this point"
|
||
|
" onwards, since column-tracking was disabled due to"
|
||
|
" the size of the code/headers");
|
||
|
if (!flag_large_source_files)
|
||
|
inform (guard_loc,
|
||
|
"adding %<-flarge-source-files%> will allow for more"
|
||
|
" column-tracking support, at the expense of compilation"
|
||
|
" time and memory");
|
||
|
}
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Give up if the loci are not all distinct. */
|
||
|
if (guard_loc == body_loc || body_loc == next_stmt_loc)
|
||
|
return false;
|
||
|
|
||
|
const unsigned int tab_width = global_dc->tabstop;
|
||
|
|
||
|
/* They must be in the same file. */
|
||
|
if (next_stmt_exploc.file != body_exploc.file)
|
||
|
return false;
|
||
|
|
||
|
/* If NEXT_STMT_LOC and BODY_LOC are on the same line, consider
|
||
|
the location of the guard.
|
||
|
|
||
|
Cases where we want to issue a warning:
|
||
|
|
||
|
if (flag)
|
||
|
foo (); bar ();
|
||
|
^ WARN HERE
|
||
|
|
||
|
if (flag) foo (); bar ();
|
||
|
^ WARN HERE
|
||
|
|
||
|
|
||
|
if (flag) ; {
|
||
|
^ WARN HERE
|
||
|
|
||
|
if (flag)
|
||
|
; {
|
||
|
^ WARN HERE
|
||
|
|
||
|
Cases where we don't want to issue a warning:
|
||
|
|
||
|
various_code (); if (flag) foo (); bar (); more_code ();
|
||
|
^ DON'T WARN HERE. */
|
||
|
if (next_stmt_exploc.line == body_exploc.line)
|
||
|
{
|
||
|
if (guard_exploc.file != body_exploc.file)
|
||
|
return true;
|
||
|
if (guard_exploc.line < body_exploc.line)
|
||
|
/* The guard is on a line before a line that contains both
|
||
|
the body and the next stmt. */
|
||
|
return true;
|
||
|
else if (guard_exploc.line == body_exploc.line)
|
||
|
{
|
||
|
/* They're all on the same line. */
|
||
|
gcc_assert (guard_exploc.file == next_stmt_exploc.file);
|
||
|
gcc_assert (guard_exploc.line == next_stmt_exploc.line);
|
||
|
unsigned int guard_vis_column;
|
||
|
unsigned int guard_line_first_nws;
|
||
|
if (!get_visual_column (guard_exploc,
|
||
|
&guard_vis_column,
|
||
|
&guard_line_first_nws, tab_width))
|
||
|
return false;
|
||
|
/* Heuristic: only warn if the guard is the first thing
|
||
|
on its line. */
|
||
|
if (guard_vis_column == guard_line_first_nws)
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* If NEXT_STMT_LOC is on a line after BODY_LOC, consider
|
||
|
their relative locations, and of the guard.
|
||
|
|
||
|
Cases where we want to issue a warning:
|
||
|
if (flag)
|
||
|
foo ();
|
||
|
bar ();
|
||
|
^ WARN HERE
|
||
|
|
||
|
Cases where we don't want to issue a warning:
|
||
|
if (flag)
|
||
|
foo ();
|
||
|
bar ();
|
||
|
^ DON'T WARN HERE (autogenerated code?)
|
||
|
|
||
|
if (flagA)
|
||
|
foo ();
|
||
|
#if SOME_CONDITION_THAT_DOES_NOT_HOLD
|
||
|
if (flagB)
|
||
|
#endif
|
||
|
bar ();
|
||
|
^ DON'T WARN HERE
|
||
|
|
||
|
if (flag)
|
||
|
;
|
||
|
foo ();
|
||
|
^ DON'T WARN HERE
|
||
|
|
||
|
#define emit
|
||
|
if (flag)
|
||
|
foo ();
|
||
|
emit bar ();
|
||
|
^ DON'T WARN HERE
|
||
|
|
||
|
*/
|
||
|
if (next_stmt_exploc.line > body_exploc.line)
|
||
|
{
|
||
|
/* Determine if GUARD_LOC and NEXT_STMT_LOC are aligned on the same
|
||
|
"visual column"... */
|
||
|
unsigned int next_stmt_vis_column;
|
||
|
unsigned int next_stmt_line_first_nws;
|
||
|
unsigned int body_vis_column;
|
||
|
unsigned int body_line_first_nws;
|
||
|
unsigned int guard_vis_column;
|
||
|
unsigned int guard_line_first_nws;
|
||
|
/* If we can't determine it, don't issue a warning. This is sometimes
|
||
|
the case for input files containing #line directives, and these
|
||
|
are often for autogenerated sources (e.g. from .md files), where
|
||
|
it's not clear that it's meaningful to look at indentation. */
|
||
|
if (!get_visual_column (next_stmt_exploc,
|
||
|
&next_stmt_vis_column,
|
||
|
&next_stmt_line_first_nws, tab_width))
|
||
|
return false;
|
||
|
if (!get_visual_column (body_exploc,
|
||
|
&body_vis_column,
|
||
|
&body_line_first_nws, tab_width))
|
||
|
return false;
|
||
|
if (!get_visual_column (guard_exploc,
|
||
|
&guard_vis_column,
|
||
|
&guard_line_first_nws, tab_width))
|
||
|
return false;
|
||
|
|
||
|
/* If the line where the next stmt starts has non-whitespace
|
||
|
on it before the stmt, then don't warn:
|
||
|
#define emit
|
||
|
if (flag)
|
||
|
foo ();
|
||
|
emit bar ();
|
||
|
^ DON'T WARN HERE
|
||
|
(PR c/69122). */
|
||
|
if (next_stmt_line_first_nws < next_stmt_vis_column)
|
||
|
return false;
|
||
|
|
||
|
if ((body_type != CPP_SEMICOLON
|
||
|
&& next_stmt_vis_column == body_vis_column)
|
||
|
/* As a special case handle the case where the body is a semicolon
|
||
|
that may be hidden by a preceding comment, e.g. */
|
||
|
|
||
|
// if (p)
|
||
|
// /* blah */;
|
||
|
// foo (1);
|
||
|
|
||
|
/* by looking instead at the column of the first non-whitespace
|
||
|
character on the body line. */
|
||
|
|| (body_type == CPP_SEMICOLON
|
||
|
&& body_exploc.line > guard_exploc.line
|
||
|
&& body_line_first_nws != body_vis_column
|
||
|
&& next_stmt_vis_column > guard_line_first_nws))
|
||
|
{
|
||
|
/* Don't warn if they are aligned on the same column
|
||
|
as the guard itself (suggesting autogenerated code that doesn't
|
||
|
bother indenting at all).
|
||
|
For "else" clauses, we consider the column of the first
|
||
|
non-whitespace character on the guard line instead of the column
|
||
|
of the actual guard token itself because it is more sensible.
|
||
|
Consider:
|
||
|
|
||
|
if (p) {
|
||
|
foo (1);
|
||
|
} else // GUARD
|
||
|
foo (2); // BODY
|
||
|
foo (3); // NEXT
|
||
|
|
||
|
and:
|
||
|
|
||
|
if (p)
|
||
|
foo (1);
|
||
|
} else // GUARD
|
||
|
foo (2); // BODY
|
||
|
foo (3); // NEXT
|
||
|
|
||
|
If we just used the column of the "else" token, we would warn on
|
||
|
the first example and not warn on the second. But we want the
|
||
|
exact opposite to happen: to not warn on the first example (which
|
||
|
is probably autogenerated) and to warn on the second (whose
|
||
|
indentation is misleading). Using the column of the first
|
||
|
non-whitespace character on the guard line makes that
|
||
|
happen. */
|
||
|
unsigned int guard_column = (guard_tinfo.keyword == RID_ELSE
|
||
|
? guard_line_first_nws
|
||
|
: guard_vis_column);
|
||
|
if (guard_column == body_vis_column)
|
||
|
return false;
|
||
|
|
||
|
/* We may have something like:
|
||
|
|
||
|
if (p)
|
||
|
{
|
||
|
foo (1);
|
||
|
} else // GUARD
|
||
|
foo (2); // BODY
|
||
|
foo (3); // NEXT
|
||
|
|
||
|
in which case the columns are not aligned but the code is not
|
||
|
misleadingly indented. If the column of the body isn't indented
|
||
|
more than the guard line then don't warn. */
|
||
|
if (body_vis_column <= guard_line_first_nws)
|
||
|
return false;
|
||
|
|
||
|
/* Don't warn if there is an unindent between the two statements. */
|
||
|
int vis_column = MIN (next_stmt_vis_column, body_vis_column);
|
||
|
if (detect_intervening_unindent (body_exploc.file, body_exploc.line,
|
||
|
next_stmt_exploc.line,
|
||
|
vis_column, tab_width))
|
||
|
return false;
|
||
|
|
||
|
/* Otherwise, they are visually aligned: issue a warning. */
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
/* Also issue a warning for code having the form:
|
||
|
|
||
|
if (flag);
|
||
|
foo ();
|
||
|
|
||
|
while (flag);
|
||
|
{
|
||
|
...
|
||
|
}
|
||
|
|
||
|
for (...);
|
||
|
{
|
||
|
...
|
||
|
}
|
||
|
|
||
|
if (flag)
|
||
|
;
|
||
|
else if (flag);
|
||
|
foo ();
|
||
|
|
||
|
where the semicolon at the end of each guard is most likely spurious.
|
||
|
|
||
|
But do not warn on:
|
||
|
|
||
|
for (..);
|
||
|
foo ();
|
||
|
|
||
|
where the next statement is aligned with the guard.
|
||
|
*/
|
||
|
if (body_type == CPP_SEMICOLON)
|
||
|
{
|
||
|
if (body_exploc.line == guard_exploc.line)
|
||
|
{
|
||
|
if (next_stmt_vis_column > guard_line_first_nws
|
||
|
|| (next_tok_type == CPP_OPEN_BRACE
|
||
|
&& next_stmt_vis_column == guard_line_first_nws))
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
/* Return the string identifier corresponding to the given guard token. */
|
||
|
|
||
|
const char *
|
||
|
guard_tinfo_to_string (enum rid keyword)
|
||
|
{
|
||
|
switch (keyword)
|
||
|
{
|
||
|
case RID_FOR:
|
||
|
return "for";
|
||
|
case RID_ELSE:
|
||
|
return "else";
|
||
|
case RID_IF:
|
||
|
return "if";
|
||
|
case RID_WHILE:
|
||
|
return "while";
|
||
|
case RID_DO:
|
||
|
return "do";
|
||
|
case RID_SWITCH:
|
||
|
return "switch";
|
||
|
default:
|
||
|
gcc_unreachable ();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Called by the C/C++ frontends when we have a guarding statement at
|
||
|
GUARD_LOC containing a statement at BODY_LOC, where the block wasn't
|
||
|
written using braces, like this:
|
||
|
|
||
|
if (flag)
|
||
|
foo ();
|
||
|
|
||
|
along with the location of the next token, at NEXT_STMT_LOC,
|
||
|
so that we can detect followup statements that are within
|
||
|
the same "visual block" as the guarded statement, but which
|
||
|
aren't logically grouped within the guarding statement, such
|
||
|
as:
|
||
|
|
||
|
GUARD_LOC
|
||
|
|
|
||
|
V
|
||
|
if (flag)
|
||
|
foo (); <- BODY_LOC
|
||
|
bar (); <- NEXT_STMT_LOC
|
||
|
|
||
|
In the above, "bar ();" isn't guarded by the "if", but
|
||
|
is indented to misleadingly suggest that it is in the same
|
||
|
block as "foo ();".
|
||
|
|
||
|
GUARD_KIND identifies the kind of clause e.g. "if", "else" etc. */
|
||
|
|
||
|
void
|
||
|
warn_for_misleading_indentation (const token_indent_info &guard_tinfo,
|
||
|
const token_indent_info &body_tinfo,
|
||
|
const token_indent_info &next_tinfo)
|
||
|
{
|
||
|
/* Early reject for the case where -Wmisleading-indentation is disabled,
|
||
|
to avoid doing work only to have the warning suppressed inside the
|
||
|
diagnostic machinery. */
|
||
|
if (!warn_misleading_indentation)
|
||
|
return;
|
||
|
|
||
|
if (should_warn_for_misleading_indentation (guard_tinfo,
|
||
|
body_tinfo,
|
||
|
next_tinfo))
|
||
|
{
|
||
|
auto_diagnostic_group d;
|
||
|
if (warning_at (guard_tinfo.location, OPT_Wmisleading_indentation,
|
||
|
"this %qs clause does not guard...",
|
||
|
guard_tinfo_to_string (guard_tinfo.keyword)))
|
||
|
inform (next_tinfo.location,
|
||
|
"...this statement, but the latter is misleadingly indented"
|
||
|
" as if it were guarded by the %qs",
|
||
|
guard_tinfo_to_string (guard_tinfo.keyword));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#if CHECKING_P
|
||
|
|
||
|
namespace selftest {
|
||
|
|
||
|
/* Verify that next_tab_stop works as expected. */
|
||
|
|
||
|
static void
|
||
|
test_next_tab_stop ()
|
||
|
{
|
||
|
const unsigned int tab_width = 8;
|
||
|
|
||
|
ASSERT_EQ (next_tab_stop (0, tab_width), 8);
|
||
|
ASSERT_EQ (next_tab_stop (1, tab_width), 8);
|
||
|
ASSERT_EQ (next_tab_stop (7, tab_width), 8);
|
||
|
|
||
|
ASSERT_EQ (next_tab_stop (8, tab_width), 16);
|
||
|
ASSERT_EQ (next_tab_stop (9, tab_width), 16);
|
||
|
ASSERT_EQ (next_tab_stop (15, tab_width), 16);
|
||
|
|
||
|
ASSERT_EQ (next_tab_stop (16, tab_width), 24);
|
||
|
ASSERT_EQ (next_tab_stop (17, tab_width), 24);
|
||
|
ASSERT_EQ (next_tab_stop (23, tab_width), 24);
|
||
|
}
|
||
|
|
||
|
/* Verify that the given call to get_visual_column succeeds, with
|
||
|
the given results. */
|
||
|
|
||
|
static void
|
||
|
assert_get_visual_column_succeeds (const location &loc,
|
||
|
const char *file, int line, int column,
|
||
|
const unsigned int tab_width,
|
||
|
unsigned int expected_visual_column,
|
||
|
unsigned int expected_first_nws)
|
||
|
{
|
||
|
expanded_location exploc;
|
||
|
exploc.file = file;
|
||
|
exploc.line = line;
|
||
|
exploc.column = column;
|
||
|
exploc.data = NULL;
|
||
|
exploc.sysp = false;
|
||
|
unsigned int actual_visual_column;
|
||
|
unsigned int actual_first_nws;
|
||
|
bool result = get_visual_column (exploc,
|
||
|
&actual_visual_column,
|
||
|
&actual_first_nws, tab_width);
|
||
|
ASSERT_TRUE_AT (loc, result);
|
||
|
ASSERT_EQ_AT (loc, actual_visual_column, expected_visual_column);
|
||
|
ASSERT_EQ_AT (loc, actual_first_nws, expected_first_nws);
|
||
|
}
|
||
|
|
||
|
/* Verify that the given call to get_visual_column succeeds, with
|
||
|
the given results. */
|
||
|
|
||
|
#define ASSERT_GET_VISUAL_COLUMN_SUCCEEDS(FILENAME, LINE, COLUMN, \
|
||
|
TAB_WIDTH, \
|
||
|
EXPECTED_VISUAL_COLUMN, \
|
||
|
EXPECTED_FIRST_NWS) \
|
||
|
SELFTEST_BEGIN_STMT \
|
||
|
assert_get_visual_column_succeeds (SELFTEST_LOCATION, \
|
||
|
FILENAME, LINE, COLUMN, \
|
||
|
TAB_WIDTH, \
|
||
|
EXPECTED_VISUAL_COLUMN, \
|
||
|
EXPECTED_FIRST_NWS); \
|
||
|
SELFTEST_END_STMT
|
||
|
|
||
|
/* Verify that the given call to get_visual_column fails gracefully. */
|
||
|
|
||
|
static void
|
||
|
assert_get_visual_column_fails (const location &loc,
|
||
|
const char *file, int line, int column,
|
||
|
const unsigned int tab_width)
|
||
|
{
|
||
|
expanded_location exploc;
|
||
|
exploc.file = file;
|
||
|
exploc.line = line;
|
||
|
exploc.column = column;
|
||
|
exploc.data = NULL;
|
||
|
exploc.sysp = false;
|
||
|
unsigned int actual_visual_column;
|
||
|
unsigned int actual_first_nws;
|
||
|
bool result = get_visual_column (exploc,
|
||
|
&actual_visual_column,
|
||
|
&actual_first_nws, tab_width);
|
||
|
ASSERT_FALSE_AT (loc, result);
|
||
|
}
|
||
|
|
||
|
/* Verify that the given call to get_visual_column fails gracefully. */
|
||
|
|
||
|
#define ASSERT_GET_VISUAL_COLUMN_FAILS(FILENAME, LINE, COLUMN, \
|
||
|
TAB_WIDTH) \
|
||
|
SELFTEST_BEGIN_STMT \
|
||
|
assert_get_visual_column_fails (SELFTEST_LOCATION, \
|
||
|
FILENAME, LINE, COLUMN, \
|
||
|
TAB_WIDTH); \
|
||
|
SELFTEST_END_STMT
|
||
|
|
||
|
/* Verify that get_visual_column works as expected. */
|
||
|
|
||
|
static void
|
||
|
test_get_visual_column ()
|
||
|
{
|
||
|
/* Create a tempfile with a mixture of tabs and spaces.
|
||
|
|
||
|
Both lines have either a space or a tab, then " line N",
|
||
|
for 8 characters in total.
|
||
|
|
||
|
1-based "columns" (w.r.t. to line 1):
|
||
|
.....................0000000001111.
|
||
|
.....................1234567890123. */
|
||
|
const char *content = (" line 1\n"
|
||
|
"\t line 2\n");
|
||
|
line_table_test ltt;
|
||
|
temp_source_file tmp (SELFTEST_LOCATION, ".txt", content);
|
||
|
|
||
|
const unsigned int tab_width = 8;
|
||
|
const char *file = tmp.get_filename ();
|
||
|
|
||
|
/* Line 1 (space-based indentation). */
|
||
|
{
|
||
|
const int line = 1;
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 1, tab_width, 0, 0);
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 2, tab_width, 1, 1);
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 3, tab_width, 2, 2);
|
||
|
/* first_nws should have stopped increasing. */
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 4, tab_width, 3, 2);
|
||
|
/* Verify the end-of-line boundary. */
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 8, tab_width, 7, 2);
|
||
|
ASSERT_GET_VISUAL_COLUMN_FAILS (file, line, 9, tab_width);
|
||
|
}
|
||
|
|
||
|
/* Line 2 (tab-based indentation). */
|
||
|
{
|
||
|
const int line = 2;
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 1, tab_width, 0, 0);
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 2, tab_width, 8, 8);
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 3, tab_width, 9, 9);
|
||
|
/* first_nws should have stopped increasing. */
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 4, tab_width, 10, 9);
|
||
|
/* Verify the end-of-line boundary. */
|
||
|
ASSERT_GET_VISUAL_COLUMN_SUCCEEDS (file, line, 8, tab_width, 14, 9);
|
||
|
ASSERT_GET_VISUAL_COLUMN_FAILS (file, line, 9, tab_width);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/* Run all of the selftests within this file. */
|
||
|
|
||
|
void
|
||
|
c_indentation_c_tests ()
|
||
|
{
|
||
|
test_next_tab_stop ();
|
||
|
test_get_visual_column ();
|
||
|
}
|
||
|
|
||
|
} // namespace selftest
|
||
|
|
||
|
#endif /* CHECKING_P */
|