This is the mail archive of the
gcc-patches@gcc.gnu.org
mailing list for the GCC project.
C/C++ PATCH to implement -Wmultiline-expansion (PR c/80116)
- From: Marek Polacek <polacek at redhat dot com>
- To: GCC Patches <gcc-patches at gcc dot gnu dot org>, Joseph Myers <joseph at codesourcery dot com>, Jason Merrill <jason at redhat dot com>, David Malcolm <dmalcolm at redhat dot com>
- Date: Thu, 1 Jun 2017 18:45:17 +0200
- Subject: C/C++ PATCH to implement -Wmultiline-expansion (PR c/80116)
- Authentication-results: sourceware.org; auth=none
- Authentication-results: ext-mx06.extmail.prod.ext.phx2.redhat.com; dmarc=none (p=none dis=none) header.from=redhat.com
- Authentication-results: ext-mx06.extmail.prod.ext.phx2.redhat.com; spf=pass smtp.mailfrom=polacek at redhat dot com
- Dkim-filter: OpenDKIM Filter v2.11.0 mx1.redhat.com 2211438886F
- Dmarc-filter: OpenDMARC Filter v1.3.2 mx1.redhat.com 2211438886F
A motivating example for this warning can be found e.g. in
PRE10-C. Wrap multistatement macros in a do-while loop
https://www.securecoding.cert.org/confluence/x/jgL7
i.e.,
#define SWAP(x, y) \
tmp = x; \
x = y; \
y = tmp
used like this [1]
int x, y, z, tmp;
if (z == 0)
SWAP(x, y);
expands to the following [2], which is certainly not what the programmer intended:
int x, y, z, tmp;
if (z == 0)
tmp = x;
x = y;
y = tmp;
This has also happened in our codebase, see PR80063.
I tried to summarize the way I approached this problem in the commentary in
warn_for_multiline_expansion, but I'll try to explain the crux of the matter
here, too.
For code like [1], in the FEs we'll see [2], of course. When parsing the
then-branch we see that the body of the if isn't wrapped in { } so we create a
compound statement with just the first statement "tmp = x;", and the other two
will be executed unconditionally.
My idea was to look at the location info of the following token after the body
of the if has been parsed and determine if they come from the same macro expansion,
and if they do (and the if itself doesn't), warn (taking into account various
corner cases, as usually).
For this I had to dive into line_maps, macro maps, etc., so CCing David to check
if my understanding of that is reasonable (hadn't worked with them before).
I've included this warning in -Wall, because there should be no false positives
(fingers crossed) and for most cases the warning should be pretty cheap.
I probably should've added a fix-it hint for good measure, too ("you better wrap
the damn macro in do {} while (0)"), but that can be done as a follow-up.
Bootstrapped/regtested on x86_64-linux, ok for trunk?
2017-06-01 Marek Polacek <polacek@redhat.com>
PR c/80116
* c-common.h (warn_for_multiline_expansion): Declare.
* c-warn.c (warn_for_multiline_expansion): New function.
* c.opt (Wmultiline-expansion): New option.
* c-parser.c (c_parser_if_body): Set the location of the
body of the conditional after parsing all the labels. Call
warn_for_multiline_expansion.
(c_parser_else_body): Likewise.
* parser.c (cp_parser_statement): Add a default argument. Save the
location of the expression-statement after labels have been parsed.
(cp_parser_implicitly_scoped_statement): Set the location of the
body of the conditional after parsing all the labels. Call
warn_for_multiline_expansion.
* doc/invoke.texi: Document -Wmultiline-expansion.
* c-c++-common/Wmultiline-expansion-1.c: New test.
* c-c++-common/Wmultiline-expansion-2.c: New test.
* c-c++-common/Wmultiline-expansion-3.c: New test.
* c-c++-common/Wmultiline-expansion-4.c: New test.
* c-c++-common/Wmultiline-expansion-5.c: New test.
* c-c++-common/Wmultiline-expansion-6.c: New test.
diff --git gcc/c-family/c-common.h gcc/c-family/c-common.h
index 79072e6..6efbebc 100644
--- gcc/c-family/c-common.h
+++ gcc/c-family/c-common.h
@@ -1539,6 +1539,7 @@ extern bool maybe_warn_shift_overflow (location_t, tree, tree);
extern void warn_duplicated_cond_add_or_warn (location_t, tree, vec<tree> **);
extern bool diagnose_mismatched_attributes (tree, tree);
extern tree do_warn_duplicated_branches_r (tree *, int *, void *);
+extern void warn_for_multiline_expansion (location_t, location_t, location_t);
/* In c-attribs.c. */
extern bool attribute_takes_identifier_p (const_tree);
diff --git gcc/c-family/c-warn.c gcc/c-family/c-warn.c
index 012675b..16c6fc3 100644
--- gcc/c-family/c-warn.c
+++ gcc/c-family/c-warn.c
@@ -2392,3 +2392,105 @@ do_warn_duplicated_branches_r (tree *tp, int *, void *)
do_warn_duplicated_branches (*tp);
return NULL_TREE;
}
+
+/* Implementation of -Wmultiline-expansion. This warning warns about
+ cases when a macro expands to multiple statements not wrapped in
+ do {} while (0) or ({ }) and is used as a then branch or as an else
+ branch. For example,
+
+ #define DOIT x++; y++
+
+ if (c)
+ DOIT;
+
+ will increment y unconditionally.
+
+ BODY_LOC is the location of the if/else body, NEXT_LOC is the location
+ of the next token after the if/else body has been parsed, and IF_LOC
+ is the location of the if condition or of the "else" keyword. */
+
+void
+warn_for_multiline_expansion (location_t body_loc, location_t next_loc,
+ location_t if_loc)
+{
+ if (!warn_multiline_expansion)
+ return;
+
+ /* Ain't got time to waste. We only care about macros here. */
+ if (!from_macro_expansion_at (body_loc)
+ || !from_macro_expansion_at (next_loc))
+ return;
+
+ /* Let's skip macros defined in system headers. */
+ if (in_system_header_at (body_loc)
+ || in_system_header_at (next_loc))
+ return;
+
+ /* Find the actual tokens in the macro definition. BODY_LOC and
+ NEXT_LOC have to come from the same spelling location, but they
+ will resolve to different locations in the context of the macro
+ definition. */
+ location_t body_loc_exp
+ = linemap_resolve_location (line_table, body_loc,
+ LRK_MACRO_DEFINITION_LOCATION, NULL);
+ location_t next_loc_exp
+ = linemap_resolve_location (line_table, next_loc,
+ LRK_MACRO_DEFINITION_LOCATION, NULL);
+ location_t if_loc_exp
+ = linemap_resolve_location (line_table, if_loc,
+ LRK_MACRO_DEFINITION_LOCATION, NULL);
+
+ /* These are some funky cases we don't want to warn about. */
+ if (body_loc_exp == if_loc_exp
+ || next_loc_exp == if_loc_exp
+ || body_loc_exp == next_loc_exp)
+ return;
+
+ /* Find the macro map for the macro expansion BODY_LOC. Every
+ macro expansion gets its own macro map and we're looking for
+ the macro map containing the expansion of BODY_LOC. We can't
+ simply check if BODY_LOC == MAP_START_LOCATION, because the
+ macro can start with a label, and then the BODY_LOC is a bit
+ offset farther into the map. */
+ const line_map_macro *map = NULL;
+ for (const line_map_macro *cur_map = LINEMAPS_MACRO_MAPS (line_table);
+ cur_map && cur_map <= LINEMAPS_LAST_MACRO_MAP (line_table);
+ ++cur_map)
+ {
+ linemap_assert (linemap_macro_expansion_map_p (cur_map));
+ if (body_loc >= MAP_START_LOCATION (cur_map)
+ && body_loc < (MAP_START_LOCATION (cur_map)
+ + MACRO_MAP_NUM_MACRO_TOKENS (cur_map)))
+ map = cur_map;
+ }
+
+ /* We'd better find it. */
+ gcc_assert (map != NULL);
+
+ /* Now see if the following token is coming from the same macro
+ expansion. If it is, it's a problem, because it should've been
+ parsed at this point.
+
+ We're only looking at yN (i.e., the spelling locations) in
+ line_map_macro->macro_locations. */
+ bool found_if = false;
+ bool found_next = false;
+ for (unsigned int i = 1;
+ i < 2 * MACRO_MAP_NUM_MACRO_TOKENS (map);
+ i += 2)
+ {
+ if (MACRO_MAP_LOCATIONS (map)[i] == next_loc_exp)
+ found_next = true;
+ if (MACRO_MAP_LOCATIONS (map)[i] == if_loc_exp)
+ found_if = true;
+ }
+
+ /* The if/else itself must not come from the same expansion, because
+ we don't want to warn about
+ #define IF if (x) x++; y++
+ and similar. */
+ if (found_next && !found_if
+ && warning_at (body_loc, OPT_Wmultiline_expansion,
+ "multiline macro expansion"))
+ inform (if_loc, "statement not guarded by this conditional");
+}
diff --git gcc/c-family/c.opt gcc/c-family/c.opt
index a8543de..15a2681 100644
--- gcc/c-family/c.opt
+++ gcc/c-family/c.opt
@@ -698,6 +698,10 @@ Wmissing-field-initializers
C ObjC C++ ObjC++ Var(warn_missing_field_initializers) Warning EnabledBy(Wextra)
Warn about missing fields in struct initializers.
+Wmultiline-expansion
+C ObjC C++ ObjC++ Var(warn_multiline_expansion) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall)
+Warn about macros expanding to multiple statements in a body of a conditional.
+
Wmultiple-inheritance
C++ ObjC++ Var(warn_multiple_inheritance) Warning
Warn on direct multiple inheritance.
diff --git gcc/c/c-parser.c gcc/c/c-parser.c
index 03c711b..9460e51 100644
--- gcc/c/c-parser.c
+++ gcc/c/c-parser.c
@@ -5492,6 +5492,7 @@ c_parser_if_body (c_parser *parser, bool *if_p,
{
tree block = c_begin_compound_stmt (flag_isoc99);
location_t body_loc = c_parser_peek_token (parser)->location;
+ location_t body_loc_after_labels = UNKNOWN_LOCATION;
token_indent_info body_tinfo
= get_token_indent_info (c_parser_peek_token (parser));
@@ -5508,11 +5509,18 @@ c_parser_if_body (c_parser *parser, bool *if_p,
else if (c_parser_next_token_is (parser, CPP_OPEN_BRACE))
add_stmt (c_parser_compound_statement (parser));
else
- c_parser_statement_after_labels (parser, if_p);
+ {
+ body_loc_after_labels = c_parser_peek_token (parser)->location;
+ c_parser_statement_after_labels (parser, if_p);
+ }
token_indent_info next_tinfo
= get_token_indent_info (c_parser_peek_token (parser));
warn_for_misleading_indentation (if_tinfo, body_tinfo, next_tinfo);
+ if (body_loc_after_labels != UNKNOWN_LOCATION
+ && next_tinfo.type != CPP_SEMICOLON)
+ warn_for_multiline_expansion (body_loc_after_labels, next_tinfo.location,
+ if_tinfo.location);
return c_end_compound_stmt (body_loc, block, flag_isoc99);
}
@@ -5530,6 +5538,7 @@ c_parser_else_body (c_parser *parser, const token_indent_info &else_tinfo,
tree block = c_begin_compound_stmt (flag_isoc99);
token_indent_info body_tinfo
= get_token_indent_info (c_parser_peek_token (parser));
+ location_t body_loc_after_labels = UNKNOWN_LOCATION;
c_parser_all_labels (parser);
if (c_parser_next_token_is (parser, CPP_SEMICOLON))
@@ -5542,11 +5551,18 @@ c_parser_else_body (c_parser *parser, const token_indent_info &else_tinfo,
c_parser_consume_token (parser);
}
else
- c_parser_statement_after_labels (parser, NULL, chain);
+ {
+ body_loc_after_labels = c_parser_peek_token (parser)->location;
+ c_parser_statement_after_labels (parser, NULL, chain);
+ }
token_indent_info next_tinfo
= get_token_indent_info (c_parser_peek_token (parser));
warn_for_misleading_indentation (else_tinfo, body_tinfo, next_tinfo);
+ if (body_loc_after_labels != UNKNOWN_LOCATION
+ && next_tinfo.type != CPP_SEMICOLON)
+ warn_for_multiline_expansion (body_loc_after_labels, next_tinfo.location,
+ else_tinfo.location);
return c_end_compound_stmt (body_loc, block, flag_isoc99);
}
diff --git gcc/cp/parser.c gcc/cp/parser.c
index 313eebb..7eb8746 100644
--- gcc/cp/parser.c
+++ gcc/cp/parser.c
@@ -2102,7 +2102,7 @@ static void cp_parser_lambda_body
/* Statements [gram.stmt.stmt] */
static void cp_parser_statement
- (cp_parser *, tree, bool, bool *, vec<tree> * = NULL);
+ (cp_parser *, tree, bool, bool *, vec<tree> * = NULL, location_t * = NULL);
static void cp_parser_label_for_labeled_statement
(cp_parser *, tree);
static tree cp_parser_expression_statement
@@ -10531,7 +10531,8 @@ cp_parser_lambda_body (cp_parser* parser, tree lambda_expr)
static void
cp_parser_statement (cp_parser* parser, tree in_statement_expr,
- bool in_compound, bool *if_p, vec<tree> *chain)
+ bool in_compound, bool *if_p, vec<tree> *chain,
+ location_t *loc_after_labels)
{
tree statement, std_attrs = NULL_TREE;
cp_token *token;
@@ -10724,6 +10725,10 @@ cp_parser_statement (cp_parser* parser, tree in_statement_expr,
if (cp_parser_parse_definitely (parser))
return;
}
+ /* All preceding labels have been parsed at this point. */
+ if (loc_after_labels != NULL)
+ *loc_after_labels = statement_location;
+
/* Look for an expression-statement instead. */
statement = cp_parser_expression_statement (parser, in_statement_expr);
@@ -12264,6 +12269,7 @@ cp_parser_implicitly_scoped_statement (cp_parser* parser, bool *if_p,
{
tree statement;
location_t body_loc = cp_lexer_peek_token (parser->lexer)->location;
+ location_t body_loc_after_labels = UNKNOWN_LOCATION;
token_indent_info body_tinfo
= get_token_indent_info (cp_lexer_peek_token (parser->lexer));
@@ -12293,7 +12299,8 @@ cp_parser_implicitly_scoped_statement (cp_parser* parser, bool *if_p,
/* Create a compound-statement. */
statement = begin_compound_stmt (0);
/* Parse the dependent-statement. */
- cp_parser_statement (parser, NULL_TREE, false, if_p, chain);
+ cp_parser_statement (parser, NULL_TREE, false, if_p, chain,
+ &body_loc_after_labels);
/* Finish the dummy compound-statement. */
finish_compound_stmt (statement);
}
@@ -12302,6 +12309,11 @@ cp_parser_implicitly_scoped_statement (cp_parser* parser, bool *if_p,
= get_token_indent_info (cp_lexer_peek_token (parser->lexer));
warn_for_misleading_indentation (guard_tinfo, body_tinfo, next_tinfo);
+ if (body_loc_after_labels != UNKNOWN_LOCATION
+ && next_tinfo.type != CPP_SEMICOLON)
+ warn_for_multiline_expansion (body_loc_after_labels, next_tinfo.location,
+ guard_tinfo.location);
+
/* Return the statement. */
return statement;
}
diff --git gcc/doc/invoke.texi gcc/doc/invoke.texi
index 59563aa..ed59da4 100644
--- gcc/doc/invoke.texi
+++ gcc/doc/invoke.texi
@@ -292,7 +292,7 @@ Objective-C and Objective-C++ Dialects}.
-Wmain -Wmaybe-uninitialized -Wmemset-elt-size -Wmemset-transposed-args @gol
-Wmisleading-indentation -Wmissing-braces @gol
-Wmissing-field-initializers -Wmissing-include-dirs @gol
--Wno-multichar -Wnonnull -Wnonnull-compare @gol
+-Wno-multichar -Wmultiline-expansion -Wnonnull -Wnonnull-compare @gol
-Wnormalized=@r{[}none@r{|}id@r{|}nfc@r{|}nfkc@r{]} @gol
-Wnull-dereference -Wodr -Wno-overflow -Wopenmp-simd @gol
-Woverride-init-side-effects -Woverlength-strings @gol
@@ -3822,6 +3822,7 @@ Options} and @ref{Objective-C and Objective-C++ Dialect Options}.
-Wmemset-transposed-args @gol
-Wmisleading-indentation @r{(only for C/C++)} @gol
-Wmissing-braces @r{(only for C/ObjC)} @gol
+-Wmultiline-expansion @gol
-Wnarrowing @r{(only for C++)} @gol
-Wnonnull @gol
-Wnonnull-compare @gol
@@ -4493,6 +4494,22 @@ This warning is enabled by @option{-Wall}.
@opindex Wno-missing-include-dirs
Warn if a user-supplied include directory does not exist.
+@item -Wmultiline-expansion
+@opindex Wmultiline-expansion
+@opindex Wno-multiline-expansion
+Warn about macros expanding to multiple statements in a body of a conditional.
+For example:
+
+@smallexample
+#define DOIT x++; y++
+if (c)
+ DOIT;
+@end smallexample
+
+will increment @code{y} unconditionally, not just when @code{c} holds.
+
+This warning is enabled by @option{-Wall} in C and C++.
+
@item -Wparentheses
@opindex Wparentheses
@opindex Wno-parentheses
diff --git gcc/testsuite/c-c++-common/Wmultiline-expansion-1.c gcc/testsuite/c-c++-common/Wmultiline-expansion-1.c
index e69de29..419cb5b 100644
--- gcc/testsuite/c-c++-common/Wmultiline-expansion-1.c
+++ gcc/testsuite/c-c++-common/Wmultiline-expansion-1.c
@@ -0,0 +1,118 @@
+/* PR c/80116 */
+/* { dg-options "-Wmultiline-expansion" } */
+/* { dg-do compile } */
+
+#define SWAP(X, Y) \
+ tmp = X; /* { dg-warning "multiline macro expansion" } */ \
+ X = Y; \
+ Y = tmp
+
+#define STUFF \
+ if (0) x = y
+
+#define STUFF2 \
+ if (0) x = y; x++
+
+#define STUFF3 \
+ if (x) /* { dg-message "statement not guarded" } */ \
+ SWAP(x, y) /* { dg-message "in expansion of macro .SWAP." } */
+
+#define SET(X, Y) \
+ (X) = (Y)
+
+#define STUFF4 \
+ if (x) \
+ SET(x, y); \
+ SET(x, y)
+
+#define STUFF5 \
+ { tmp = x; x = y; }
+
+#define STUFF6 \
+ x++;;
+
+int x, y, tmp;
+
+void
+fn1 (void)
+{
+ if (x) /* { dg-message "statement not guarded" } */
+ SWAP(x, y); /* { dg-message "in expansion of macro .SWAP." } */
+}
+
+void
+fn2 (void)
+{
+ SWAP(x, y);
+}
+
+void
+fn3 (void)
+{
+ if (x)
+ {
+ SWAP(x, y);
+ }
+}
+
+void
+fn4 (void)
+{
+ if (x)
+ ({ x = 10; x++; });
+}
+
+void
+fn5 (void)
+{
+ if (x) /* { dg-message "statement not guarded" } */
+L1:
+ SWAP (x, y); /* { dg-message "in expansion of macro .SWAP." } */
+ goto L1;
+}
+
+void
+fn6 (void)
+{
+ if (x)
+ SET (x, y);
+ SET (tmp, x);
+}
+
+void
+fn7 (void)
+{
+ STUFF;
+}
+
+void
+fn8 (void)
+{
+ STUFF2;
+}
+
+void
+fn9 (void)
+{
+ STUFF3; /* { dg-message "in expansion of macro .STUFF3." } */
+}
+
+void
+fn10 (void)
+{
+ STUFF4;
+}
+
+void
+fn11 (void)
+{
+ if (x)
+ STUFF5;
+}
+
+void
+fn12 (void)
+{
+ if (x)
+ STUFF6;
+}
diff --git gcc/testsuite/c-c++-common/Wmultiline-expansion-2.c gcc/testsuite/c-c++-common/Wmultiline-expansion-2.c
index e69de29..3a523d0 100644
--- gcc/testsuite/c-c++-common/Wmultiline-expansion-2.c
+++ gcc/testsuite/c-c++-common/Wmultiline-expansion-2.c
@@ -0,0 +1,137 @@
+/* PR c/80116 */
+/* { dg-options "-Wmultiline-expansion" } */
+/* { dg-do compile } */
+
+#define SWAP(X, Y) \
+ tmp = X; /* { dg-warning "multiline macro expansion" } */ \
+ X = Y; \
+ Y = tmp
+
+#define STUFF \
+ if (0) {} else x = y
+
+#define STUFF2 \
+ if (0) {} else x = y; x++
+
+#define STUFF3 \
+ if (x) \
+ {} \
+ else /* { dg-message "statement not guarded" } */ \
+ SWAP(x, y) /* { dg-message "in expansion of macro .SWAP." } */
+
+#define SET(X, Y) \
+ (X) = (Y)
+
+#define STUFF4 \
+ if (x) \
+ {} \
+ else \
+ SET(x, y); \
+ SET(x, y)
+
+#define STUFF5 \
+ { tmp = x; x = y; }
+
+#define STUFF6 \
+ x++;;
+
+int x, y, tmp;
+
+void
+fn1 (void)
+{
+ if (x)
+ {
+ }
+ else /* { dg-message "statement not guarded" } */
+ SWAP(x, y); /* { dg-message "in expansion of macro .SWAP." } */
+}
+
+void
+fn2 (void)
+{
+ SWAP(x, y);
+}
+
+void
+fn3 (void)
+{
+ if (x)
+ {
+ }
+ else
+ {
+ SWAP(x, y);
+ }
+}
+
+void
+fn4 (void)
+{
+ if (x)
+ {
+ }
+ else
+ ({ x = 10; x++; });
+}
+
+void
+fn5 (void)
+{
+ if (x)
+ {
+ }
+ else /* { dg-message "statement not guarded" } */
+L1:
+ SWAP (x, y); /* { dg-message "in expansion of macro .SWAP." } */
+ goto L1;
+}
+
+void
+fn6 (void)
+{
+ if (x)
+ {
+ }
+ else
+ SET (x, y);
+ SET (tmp, x);
+}
+
+void
+fn7 (void)
+{
+ STUFF;
+}
+
+void
+fn8 (void)
+{
+ STUFF2;
+}
+
+void
+fn9 (void)
+{
+ STUFF3; /* { dg-message "in expansion of macro .STUFF3." } */
+}
+
+void
+fn10 (void)
+{
+ STUFF4;
+}
+
+void
+fn11 (void)
+{
+ if (x)
+ STUFF5;
+}
+
+void
+fn12 (void)
+{
+ if (x)
+ STUFF6;
+}
diff --git gcc/testsuite/c-c++-common/Wmultiline-expansion-3.c gcc/testsuite/c-c++-common/Wmultiline-expansion-3.c
index e69de29..c2e83af 100644
--- gcc/testsuite/c-c++-common/Wmultiline-expansion-3.c
+++ gcc/testsuite/c-c++-common/Wmultiline-expansion-3.c
@@ -0,0 +1,12 @@
+/* PR c/80116 */
+/* { dg-options "-Wmultiline-expansion" } */
+/* { dg-do compile } */
+
+#define CHECK(X) if (!(X)) __builtin_abort ()
+
+void
+fn (int i)
+{
+ CHECK (i == 1);
+ CHECK (i == 2);
+}
diff --git gcc/testsuite/c-c++-common/Wmultiline-expansion-4.c gcc/testsuite/c-c++-common/Wmultiline-expansion-4.c
index e69de29..3ab76a7 100644
--- gcc/testsuite/c-c++-common/Wmultiline-expansion-4.c
+++ gcc/testsuite/c-c++-common/Wmultiline-expansion-4.c
@@ -0,0 +1,14 @@
+/* PR c/80116 */
+/* { dg-options "-Wmultiline-expansion" } */
+/* { dg-do compile } */
+
+#define FN(C) \
+ void \
+ fn (void) \
+ { \
+ C; \
+ }
+
+int i;
+
+FN (if (i) ++i)
diff --git gcc/testsuite/c-c++-common/Wmultiline-expansion-5.c gcc/testsuite/c-c++-common/Wmultiline-expansion-5.c
index e69de29..95017ba 100644
--- gcc/testsuite/c-c++-common/Wmultiline-expansion-5.c
+++ gcc/testsuite/c-c++-common/Wmultiline-expansion-5.c
@@ -0,0 +1,18 @@
+/* PR c/80116 */
+/* { dg-options "-Wmultiline-expansion" } */
+/* { dg-do compile } */
+
+#define M(N) \
+L ## N: \
+ x++; x++ /* { dg-warning "multiline macro expansion" } */
+
+int x, y, tmp;
+
+void
+fn1 (void)
+{
+ if (x) /* { dg-message "statement not guarded" } */
+ M (0); /* { dg-message "in expansion of macro .M." } */
+ if (x) /* { dg-message "statement not guarded" } */
+ M (1); /* { dg-message "in expansion of macro .M." } */
+}
diff --git gcc/testsuite/c-c++-common/Wmultiline-expansion-6.c gcc/testsuite/c-c++-common/Wmultiline-expansion-6.c
index e69de29..9f799e1 100644
--- gcc/testsuite/c-c++-common/Wmultiline-expansion-6.c
+++ gcc/testsuite/c-c++-common/Wmultiline-expansion-6.c
@@ -0,0 +1,22 @@
+/* PR c/80116 */
+/* { dg-options "-Wmultiline-expansion" } */
+/* { dg-do compile } */
+
+#define M \
+ if (x) x++; x++
+
+void
+f (int x)
+{
+ M;
+ M;
+ M;
+ M;
+ M;
+ M;
+ M;
+ M;
+ M;
+ M;
+ M;
+}
Marek