Better error recovery for merge-conflict markers
authorDavid Malcolm <dmalcolm@redhat.com>
Wed, 16 Dec 2015 18:15:01 +0000 (18:15 +0000)
committerDavid Malcolm <dmalcolm@gcc.gnu.org>
Wed, 16 Dec 2015 18:15:01 +0000 (18:15 +0000)
gcc/c-family/ChangeLog:
* c-common.h (conflict_marker_get_final_tok_kind): New prototype.
* c-lex.c (conflict_marker_get_final_tok_kind): New function.

gcc/c/ChangeLog:
* c-parser.c (struct c_parser): Expand array "tokens_buf" from 2
to 4.
(c_parser_peek_nth_token): New function.
(c_parser_peek_conflict_marker): New function.
(c_parser_error): Detect conflict markers and report them as such.

gcc/cp/ChangeLog:
* parser.c (cp_lexer_peek_conflict_marker): New function.
(cp_parser_error): Detect conflict markers and report them as
such.

gcc/testsuite/ChangeLog:
* c-c++-common/conflict-markers-1.c: New testcase.
* c-c++-common/conflict-markers-2.c: Likewise.
* c-c++-common/conflict-markers-3.c: Likewise.
* c-c++-common/conflict-markers-4.c: Likewise.
* c-c++-common/conflict-markers-5.c: Likewise.
* c-c++-common/conflict-markers-6.c: Likewise.
* c-c++-common/conflict-markers-7.c: Likewise.
* c-c++-common/conflict-markers-8.c: Likewise.
* c-c++-common/conflict-markers-9.c: Likewise.
* c-c++-common/conflict-markers-10.c: Likewise.
* c-c++-common/conflict-markers-11.c: Likewise.
* g++.dg/conflict-markers-1.C: Likewise.

From-SVN: r231712

20 files changed:
gcc/c-family/ChangeLog
gcc/c-family/c-common.h
gcc/c-family/c-lex.c
gcc/c/ChangeLog
gcc/c/c-parser.c
gcc/cp/ChangeLog
gcc/cp/parser.c
gcc/testsuite/ChangeLog
gcc/testsuite/c-c++-common/conflict-markers-1.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-10.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-11.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-2.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-3.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-4.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-5.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-6.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-7.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-8.c [new file with mode: 0644]
gcc/testsuite/c-c++-common/conflict-markers-9.c [new file with mode: 0644]
gcc/testsuite/g++.dg/conflict-markers-1.C [new file with mode: 0644]

index 525cc16f17bd2d0307940d27d84e9a8ef66749b9..3208ce59ddadb8eda3a627dc5182ed6c4d2e4a0f 100644 (file)
@@ -1,3 +1,8 @@
+2015-12-16  David Malcolm  <dmalcolm@redhat.com>
+
+       * c-common.h (conflict_marker_get_final_tok_kind): New prototype.
+       * c-lex.c (conflict_marker_get_final_tok_kind): New function.
+
 2015-12-15  Ilya Verbin  <ilya.verbin@intel.com>
 
        * c-common.c (c_common_attribute_table): Handle "omp declare target
index ef64e6b1b4280d988e2810610c6a6b612dae8a55..2183565c0ddd09227043c8af44af06bd64eb7fd2 100644 (file)
@@ -1089,6 +1089,10 @@ extern void c_genericize (tree);
 extern int c_gimplify_expr (tree *, gimple_seq *, gimple_seq *);
 extern tree c_build_bind_expr (location_t, tree, tree);
 
+/* In c-lex.c.  */
+extern enum cpp_ttype
+conflict_marker_get_final_tok_kind (enum cpp_ttype tok1_kind);
+
 /* In c-pch.c  */
 extern void pch_init (void);
 extern void pch_cpp_save_state (void);
index 9c86ba729edccbb3ae31223dd1f6aae720c43d39..125407b820236a8d263404645713e32a6be386a7 100644 (file)
@@ -1263,3 +1263,29 @@ lex_charconst (const cpp_token *token)
 
   return value;
 }
+
+/* Helper function for c_parser_peek_conflict_marker
+   and cp_lexer_peek_conflict_marker.
+   Given a possible conflict marker token of kind TOK1_KIND
+   consisting of a pair of characters, get the token kind for the
+   standalone final character.  */
+
+enum cpp_ttype
+conflict_marker_get_final_tok_kind (enum cpp_ttype tok1_kind)
+{
+  switch (tok1_kind)
+    {
+    default: gcc_unreachable ();
+    case CPP_LSHIFT:
+      /* "<<" and '<' */
+      return CPP_LESS;
+
+    case CPP_EQ_EQ:
+      /* "==" and '=' */
+      return CPP_EQ;
+
+    case CPP_RSHIFT:
+      /* ">>" and '>' */
+      return CPP_GREATER;
+    }
+}
index 49c3cbb73dcf26c4339f56e5acdab804822b645c..22692d42a642e30ffbbee4ebfff45475b33b2420 100644 (file)
@@ -1,3 +1,11 @@
+2015-12-16  David Malcolm  <dmalcolm@redhat.com>
+
+       * c-parser.c (struct c_parser): Expand array "tokens_buf" from 2
+       to 4.
+       (c_parser_peek_nth_token): New function.
+       (c_parser_peek_conflict_marker): New function.
+       (c_parser_error): Detect conflict markers and report them as such.
+
 2015-12-16  David Malcolm  <dmalcolm@redhat.com>
 
        * c-parser.c (c_parser_postfix_expression): Use EXPR_LOC_OR_LOC
index e149e19bc2c63b6f07a9f12d76514deb1544ee19..43c26ae73847ebcb7817ec8a44eb894ae8af6260 100644 (file)
@@ -202,8 +202,8 @@ struct GTY(()) c_parser {
   /* The look-ahead tokens.  */
   c_token * GTY((skip)) tokens;
   /* Buffer for look-ahead tokens.  */
-  c_token tokens_buf[2];
-  /* How many look-ahead tokens are available (0, 1 or 2, or
+  c_token tokens_buf[4];
+  /* How many look-ahead tokens are available (0 - 4, or
      more if parsing from pre-lexed tokens).  */
   unsigned int tokens_avail;
   /* True if a syntax error is being recovered from; false otherwise.
@@ -492,6 +492,23 @@ c_parser_peek_2nd_token (c_parser *parser)
   return &parser->tokens[1];
 }
 
+/* Return a pointer to the Nth token from PARSER, reading it
+   in if necessary.  The N-1th token is already read in.  */
+
+static c_token *
+c_parser_peek_nth_token (c_parser *parser, unsigned int n)
+{
+  /* N is 1-based, not zero-based.  */
+  gcc_assert (n > 0);
+
+  if (parser->tokens_avail >= n)
+    return &parser->tokens[n - 1];
+  gcc_assert (parser->tokens_avail == n - 1);
+  c_lex_one_token (parser, &parser->tokens[n - 1]);
+  parser->tokens_avail = n;
+  return &parser->tokens[n - 1];
+}
+
 /* Return true if TOKEN can start a type name,
    false otherwise.  */
 static bool
@@ -829,6 +846,46 @@ c_parser_set_source_position_from_token (c_token *token)
     }
 }
 
+/* Helper function for c_parser_error.
+   Having peeked a token of kind TOK1_KIND that might signify
+   a conflict marker, peek successor tokens to determine
+   if we actually do have a conflict marker.
+   Specifically, we consider a run of 7 '<', '=' or '>' characters
+   at the start of a line as a conflict marker.
+   These come through the lexer as three pairs and a single,
+   e.g. three CPP_LSHIFT ("<<") and a CPP_LESS ('<').
+   If it returns true, *OUT_LOC is written to with the location/range
+   of the marker.  */
+
+static bool
+c_parser_peek_conflict_marker (c_parser *parser, enum cpp_ttype tok1_kind,
+                              location_t *out_loc)
+{
+  c_token *token2 = c_parser_peek_2nd_token (parser);
+  if (token2->type != tok1_kind)
+    return false;
+  c_token *token3 = c_parser_peek_nth_token (parser, 3);
+  if (token3->type != tok1_kind)
+    return false;
+  c_token *token4 = c_parser_peek_nth_token (parser, 4);
+  if (token4->type != conflict_marker_get_final_tok_kind (tok1_kind))
+    return false;
+
+  /* It must be at the start of the line.  */
+  location_t start_loc = c_parser_peek_token (parser)->location;
+  if (LOCATION_COLUMN (start_loc) != 1)
+    return false;
+
+  /* We have a conflict marker.  Construct a location of the form:
+       <<<<<<<
+       ^~~~~~~
+     with start == caret, finishing at the end of the marker.  */
+  location_t finish_loc = get_finish (token4->location);
+  *out_loc = make_location (start_loc, start_loc, finish_loc);
+
+  return true;
+}
+
 /* Issue a diagnostic of the form
       FILE:LINE: MESSAGE before TOKEN
    where TOKEN is the next token in the input stream of PARSER.
@@ -850,6 +907,20 @@ c_parser_error (c_parser *parser, const char *gmsgid)
   parser->error = true;
   if (!gmsgid)
     return;
+
+  /* If this is actually a conflict marker, report it as such.  */
+  if (token->type == CPP_LSHIFT
+      || token->type == CPP_RSHIFT
+      || token->type == CPP_EQ_EQ)
+    {
+      location_t loc;
+      if (c_parser_peek_conflict_marker (parser, token->type, &loc))
+       {
+         error_at (loc, "version control conflict marker in file");
+         return;
+       }
+    }
+
   /* This diagnostic makes more sense if it is tagged to the line of
      the token we just peeked at.  */
   c_parser_set_source_position_from_token (token);
index a23d05f7bd214d27a621023513aa6c82b984623f..a3a73a39cbe16a72c5f5233de3a6ad74e7a0fe63 100644 (file)
@@ -1,3 +1,9 @@
+2015-12-16  David Malcolm  <dmalcolm@redhat.com>
+
+       * parser.c (cp_lexer_peek_conflict_marker): New function.
+       (cp_parser_error): Detect conflict markers and report them as
+       such.
+
 2015-12-15  Martin Sebor  <msebor@redhat.com>
 
        c++/42121
index a420cf1eff5780ff13a234f4226b90d176bc055e..c1948c463ebe1d118fa0f8daa0d796dd9b4a8b6f 100644 (file)
@@ -2689,6 +2689,46 @@ cp_parser_is_keyword (cp_token* token, enum rid keyword)
   return token->keyword == keyword;
 }
 
+/* Helper function for cp_parser_error.
+   Having peeked a token of kind TOK1_KIND that might signify
+   a conflict marker, peek successor tokens to determine
+   if we actually do have a conflict marker.
+   Specifically, we consider a run of 7 '<', '=' or '>' characters
+   at the start of a line as a conflict marker.
+   These come through the lexer as three pairs and a single,
+   e.g. three CPP_LSHIFT tokens ("<<") and a CPP_LESS token ('<').
+   If it returns true, *OUT_LOC is written to with the location/range
+   of the marker.  */
+
+static bool
+cp_lexer_peek_conflict_marker (cp_lexer *lexer, enum cpp_ttype tok1_kind,
+                              location_t *out_loc)
+{
+  cp_token *token2 = cp_lexer_peek_nth_token (lexer, 2);
+  if (token2->type != tok1_kind)
+    return false;
+  cp_token *token3 = cp_lexer_peek_nth_token (lexer, 3);
+  if (token3->type != tok1_kind)
+    return false;
+  cp_token *token4 = cp_lexer_peek_nth_token (lexer, 4);
+  if (token4->type != conflict_marker_get_final_tok_kind (tok1_kind))
+    return false;
+
+  /* It must be at the start of the line.  */
+  location_t start_loc = cp_lexer_peek_token (lexer)->location;
+  if (LOCATION_COLUMN (start_loc) != 1)
+    return false;
+
+  /* We have a conflict marker.  Construct a location of the form:
+       <<<<<<<
+       ^~~~~~~
+     with start == caret, finishing at the end of the marker.  */
+  location_t finish_loc = get_finish (token4->location);
+  *out_loc = make_location (start_loc, start_loc, finish_loc);
+
+  return true;
+}
+
 /* If not parsing tentatively, issue a diagnostic of the form
       FILE:LINE: MESSAGE before TOKEN
    where TOKEN is the next token in the input stream.  MESSAGE
@@ -2713,6 +2753,19 @@ cp_parser_error (cp_parser* parser, const char* gmsgid)
          return;
        }
 
+      /* If this is actually a conflict marker, report it as such.  */
+      if (token->type == CPP_LSHIFT
+         || token->type == CPP_RSHIFT
+         || token->type == CPP_EQ_EQ)
+       {
+         location_t loc;
+         if (cp_lexer_peek_conflict_marker (parser->lexer, token->type, &loc))
+           {
+             error_at (loc, "version control conflict marker in file");
+             return;
+           }
+       }
+
       c_parse_error (gmsgid,
                     /* Because c_parser_error does not understand
                        CPP_KEYWORD, keywords are treated like
index 91ae32a58720358ed9aea5ca289e6cd40ef069d5..83cf7acbd5044057633a74910d5c140bbb86a700 100644 (file)
@@ -1,3 +1,18 @@
+2015-12-16  David Malcolm  <dmalcolm@redhat.com>
+
+       * c-c++-common/conflict-markers-1.c: New testcase.
+       * c-c++-common/conflict-markers-2.c: Likewise.
+       * c-c++-common/conflict-markers-3.c: Likewise.
+       * c-c++-common/conflict-markers-4.c: Likewise.
+       * c-c++-common/conflict-markers-5.c: Likewise.
+       * c-c++-common/conflict-markers-6.c: Likewise.
+       * c-c++-common/conflict-markers-7.c: Likewise.
+       * c-c++-common/conflict-markers-8.c: Likewise.
+       * c-c++-common/conflict-markers-9.c: Likewise.
+       * c-c++-common/conflict-markers-10.c: Likewise.
+       * c-c++-common/conflict-markers-11.c: Likewise.
+       * g++.dg/conflict-markers-1.C: Likewise.
+
 2015-12-16  David Malcolm  <dmalcolm@redhat.com>
 
        * gcc.dg/cast-function-1.c (bar): Update column numbers.
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-1.c b/gcc/testsuite/c-c++-common/conflict-markers-1.c
new file mode 100644 (file)
index 0000000..b9b3bab
--- /dev/null
@@ -0,0 +1,11 @@
+int p;
+
+<<<<<<< HEAD /* { dg-error "conflict marker" } */
+extern int some_var;
+=======      /* { dg-error "conflict marker" } */
+extern short some_var; /* This line would lead to a warning due to the
+                         duplicate name, but it is skipped when handling
+                         the conflict marker.  */
+>>>>>>> Some commit message  /* { dg-error "conflict marker" } */
+
+int q;
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-10.c b/gcc/testsuite/c-c++-common/conflict-markers-10.c
new file mode 100644 (file)
index 0000000..59e5a49
--- /dev/null
@@ -0,0 +1,25 @@
+/* { dg-options "-fdiagnostics-show-caret" } */
+
+<<<<<<< HEAD /* { dg-error "conflict marker" } */
+/* { dg-begin-multiline-output "" }
+ <<<<<<< HEAD
+ ^~~~~~~
+   { dg-end-multiline-output "" } */
+
+extern int some_var;
+
+=======      /* { dg-error "conflict marker" } */
+/* { dg-begin-multiline-output "" }
+ =======
+ ^~~~~~~
+   { dg-end-multiline-output "" } */
+
+extern short some_var; /* This line would lead to a warning due to the
+                         duplicate name, but it is skipped when handling
+                         the conflict marker.  */
+
+>>>>>>> Some commit message  /* { dg-error "conflict marker" } */
+/* { dg-begin-multiline-output "" }
+ >>>>>>>
+ ^~~~~~~
+   { dg-end-multiline-output "" } */
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-11.c b/gcc/testsuite/c-c++-common/conflict-markers-11.c
new file mode 100644 (file)
index 0000000..8771453
--- /dev/null
@@ -0,0 +1,14 @@
+/* Verify that we only report conflict markers at the start of lines.  */
+int p;
+
+ <<<<<<< HEAD /* { dg-error "expected identifier|expected unqualified-id" } */
+
+int q;
+
+ =======      /* { dg-error "expected identifier|expected unqualified-id" } */
+
+int r;
+
+ >>>>>>> Some commit message  /* { dg-error "expected identifier|expected unqualified-id" } */
+
+int s;
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-2.c b/gcc/testsuite/c-c++-common/conflict-markers-2.c
new file mode 100644 (file)
index 0000000..f06d043
--- /dev/null
@@ -0,0 +1,2 @@
+/* This should not be flagged as a conflict marker.  */
+const char *msg = "<<<<<<< ";
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-3.c b/gcc/testsuite/c-c++-common/conflict-markers-3.c
new file mode 100644 (file)
index 0000000..f149ecc
--- /dev/null
@@ -0,0 +1,11 @@
+/* Ensure we can handle unterminated conflict markers.  */
+
+int p;
+
+<<<<<<< HEAD  /* { dg-error "conflict marker" } */
+
+int q;
+
+<<<<<<< HEAD  /* { dg-error "conflict marker" } */
+
+int r;
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-4.c b/gcc/testsuite/c-c++-common/conflict-markers-4.c
new file mode 100644 (file)
index 0000000..a3c53ea
--- /dev/null
@@ -0,0 +1,11 @@
+/* Ensure we can handle mismatched conflict markers.  */
+
+int p;
+
+>>>>>>> Some commit message  /* { dg-error "conflict marker" } */
+
+int q;
+
+>>>>>>> Some other commit message  /* { dg-error "conflict marker" } */
+
+int r;
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-5.c b/gcc/testsuite/c-c++-common/conflict-markers-5.c
new file mode 100644 (file)
index 0000000..b55c9c3
--- /dev/null
@@ -0,0 +1,11 @@
+/* Ensure we can handle mismatched conflict markers.  */
+
+int p;
+
+=======  /* { dg-error "conflict marker" } */
+
+int q;
+
+=======  /* { dg-error "conflict marker" } */
+
+int r;
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-6.c b/gcc/testsuite/c-c++-common/conflict-markers-6.c
new file mode 100644 (file)
index 0000000..081e289
--- /dev/null
@@ -0,0 +1,38 @@
+/* Branch coverage of conflict marker detection:
+   none of these should be reported as conflict markers.  */
+
+int a0;
+
+<< HEAD  /* { dg-error "expected" } */
+
+int a1;
+
+<<<< HEAD  /* { dg-error "expected" } */
+
+int a2;
+
+<<<<<< HEAD  /* { dg-error "expected" } */
+
+int b0;
+
+== HEAD  /* { dg-error "expected" } */
+
+int b1;
+
+==== HEAD  /* { dg-error "expected" } */
+
+int b2;
+
+====== HEAD  /* { dg-error "expected" } */
+
+int c0;
+
+>> HEAD  /* { dg-error "expected" } */
+
+int c1;
+
+>>>> HEAD  /* { dg-error "expected" } */
+
+int c2;
+
+>>>>>> HEAD  /* { dg-error "expected" } */
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-7.c b/gcc/testsuite/c-c++-common/conflict-markers-7.c
new file mode 100644 (file)
index 0000000..e68f84d
--- /dev/null
@@ -0,0 +1,6 @@
+/* It's valid to stringize the "<<<<<<<"; don't
+   report it as a conflict marker.  */
+#define str(s) #s
+const char *s = str(
+<<<<<<<
+);
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-8.c b/gcc/testsuite/c-c++-common/conflict-markers-8.c
new file mode 100644 (file)
index 0000000..be2e121
--- /dev/null
@@ -0,0 +1,4 @@
+/* A macro that's never expanded shouldn't be reported as a
+   conflict marker.  */
+#define foo \
+<<<<<<<
diff --git a/gcc/testsuite/c-c++-common/conflict-markers-9.c b/gcc/testsuite/c-c++-common/conflict-markers-9.c
new file mode 100644 (file)
index 0000000..5c1e663
--- /dev/null
@@ -0,0 +1,8 @@
+/* It's valid to have
+<<<<<<<
+   inside both
+   comments (as above), and within string literals.  */
+const char *s = "\
+<<<<<<<";
+
+/* The above shouldn't be reported as errors.  */
diff --git a/gcc/testsuite/g++.dg/conflict-markers-1.C b/gcc/testsuite/g++.dg/conflict-markers-1.C
new file mode 100644 (file)
index 0000000..be3cb88
--- /dev/null
@@ -0,0 +1,13 @@
+/* Ensure that we don't complain about conflict markers on
+   valid template argument lists, valid in C++11 onwards.  */
+// { dg-options "-std=c++11" }
+
+template <typename T>
+struct foo
+{
+  T t;
+};
+
+foo <foo <foo <foo <foo <foo <foo <int
+>>>>>>> f;
+// The above line is valid C++11, and isn't a conflict marker