diff mbox

C++ PATCH for c++/63455 (ICE with decltype of statement-expression)

Message ID 543E80AC.80203@redhat.com
State New
Headers show

Commit Message

Jason Merrill Oct. 15, 2014, 2:11 p.m. UTC
The failure in this test was caused by a 
cp_parser_commit_to_tentative_parse within the statement-expression 
affecting the tentative parse for the decltype, which we don't want; we 
need to be able to fail the id-or-member-access parse and then succeed 
with the general expression parse.

I noticed that cp_parser_commit_to_tentative_parse stops when it finds 
an already-committed level, so it occurred to me that we can stop it 
walking all the way out by creating a committed level at the outside of 
the statement-expression as a firewall.

That seemed to be working properly, but somehow when I stripped the 
firewall level the lexer was trying to give me the wrong token.  This 
turned out to be because cp_parser_statement was saving tokens and never 
committing them, so stripping the firewall was giving me tokens from one 
of the statements.  So I created an RAII class to handle saving and 
committing tokens and doing a sanity check that my pushes and pops match.

But then the sanity check failed on a couple of lambda testcases, for 
the same reason as the statement-expression: since lambdas also contain 
arbitrary code that might commit to a tentative parse, we need to use 
the firewall for them as well.

Tested x86_64-pc-linux-gnu, applying to trunk.
diff mbox

Patch

commit 96c53b1efe342912da4d3ec92c6623bd5453a307
Author: Jason Merrill <jason@redhat.com>
Date:   Tue Oct 14 10:53:33 2014 -0400

    	PR c++/63455
    c-family/
    	* c-common.h (CPP_PREPARSED_EXPR): New.
    	(N_CP_TTYPES): Adjust.
    cp/
    	* parser.c (struct saved_token_sentinel): New.
    	(cp_parser_statement): Use it.
    	(cp_parser_start_tentative_firewall): New.
    	(cp_parser_end_tentative_firewall): New.
    	(cp_parser_lambda_expression): Use them.
    	(cp_parser_statement_expr): New.
    	(cp_parser_primary_expression): Use it.

diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index fec9a06..b45ccfc 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -361,8 +361,11 @@  struct c_common_resword
 /* A token type for pre-parsed C++0x decltype.  */
 #define CPP_DECLTYPE ((enum cpp_ttype) (CPP_NESTED_NAME_SPECIFIER + 1))
 
+/* A token type for pre-parsed primary-expression (lambda- or statement-).  */
+#define CPP_PREPARSED_EXPR ((enum cpp_ttype) (CPP_DECLTYPE + 1))
+
 /* The number of token types, including C++-specific ones.  */
-#define N_CP_TTYPES ((int) (CPP_DECLTYPE + 1))
+#define N_CP_TTYPES ((int) (CPP_PREPARSED_EXPR + 1))
 
 /* Disable mask.  Keywords are disabled if (reswords[i].disable &
    mask) is _true_.  Thus for keywords which are present in all
diff --git a/gcc/cp/parser.c b/gcc/cp/parser.c
index b5a3724..c730995 100644
--- a/gcc/cp/parser.c
+++ b/gcc/cp/parser.c
@@ -1,4 +1,4 @@ 
-/* C++ Parser.
+/* -*- C++ -*- Parser.
    Copyright (C) 2000-2014 Free Software Foundation, Inc.
    Written by Mark Mitchell <mark@codesourcery.com>.
 
@@ -1155,6 +1155,34 @@  cp_lexer_rollback_tokens (cp_lexer* lexer)
   lexer->next_token = lexer->saved_tokens.pop ();
 }
 
+/* RAII wrapper around the above functions, with sanity checking.  Creating
+   a variable saves tokens, which are committed when the variable is
+   destroyed unless they are explicitly rolled back by calling the rollback
+   member function.  */
+
+struct saved_token_sentinel
+{
+  cp_lexer *lexer;
+  unsigned len;
+  bool commit;
+  saved_token_sentinel(cp_lexer *lexer): lexer(lexer), commit(true)
+  {
+    len = lexer->saved_tokens.length ();
+    cp_lexer_save_tokens (lexer);
+  }
+  void rollback ()
+  {
+    cp_lexer_rollback_tokens (lexer);
+    commit = false;
+  }
+  ~saved_token_sentinel()
+  {
+    if (commit)
+      cp_lexer_commit_tokens (lexer);
+    gcc_assert (lexer->saved_tokens.length () == len);
+  }
+};
+
 /* Print a representation of the TOKEN on the STREAM.  */
 
 static void
@@ -4107,6 +4135,65 @@  complain_flags (bool decltype_p)
   return complain;
 }
 
+/* We're about to parse a collection of statements.  If we're currently
+   parsing tentatively, set up a firewall so that any nested
+   cp_parser_commit_to_tentative_parse won't affect the current context.  */
+
+static cp_token_position
+cp_parser_start_tentative_firewall (cp_parser *parser)
+{
+  if (!cp_parser_uncommitted_to_tentative_parse_p (parser))
+    return 0;
+
+  cp_parser_parse_tentatively (parser);
+  cp_parser_commit_to_topmost_tentative_parse (parser);
+  return cp_lexer_token_position (parser->lexer, false);
+}
+
+/* We've finished parsing the collection of statements.  Wrap up the
+   firewall and replace the relevant tokens with the parsed form.  */
+
+static void
+cp_parser_end_tentative_firewall (cp_parser *parser, cp_token_position start,
+				  tree expr)
+{
+  if (!start)
+    return;
+
+  /* Finish the firewall level.  */
+  cp_parser_parse_definitely (parser);
+  /* And remember the result of the parse for when we try again.  */
+  cp_token *token = cp_lexer_token_at (parser->lexer, start);
+  token->type = CPP_PREPARSED_EXPR;
+  token->u.value = expr;
+  token->keyword = RID_MAX;
+  cp_lexer_purge_tokens_after (parser->lexer, start);
+}
+
+/* Parse a GNU statement-expression, i.e. ({ stmts }), except for the
+   enclosing parentheses.  */
+
+static tree
+cp_parser_statement_expr (cp_parser *parser)
+{
+  cp_token_position start = cp_parser_start_tentative_firewall (parser);
+
+  /* Consume the '('.  */
+  cp_lexer_consume_token (parser->lexer);
+  /* Start the statement-expression.  */
+  tree expr = begin_stmt_expr ();
+  /* Parse the compound-statement.  */
+  cp_parser_compound_statement (parser, expr, false, false);
+  /* Finish up.  */
+  expr = finish_stmt_expr (expr, false);
+  /* Consume the ')'.  */
+  if (!cp_parser_require (parser, CPP_CLOSE_PAREN, RT_CLOSE_PAREN))
+    cp_parser_skip_to_end_of_statement (parser);
+
+  cp_parser_end_tentative_firewall (parser, start, expr);
+  return expr;
+}
+
 /* Expressions [gram.expr] */
 
 /* Parse a primary-expression.
@@ -4193,6 +4280,7 @@  cp_parser_primary_expression (cp_parser *parser,
     case CPP_CHAR32:
     case CPP_WCHAR:
     case CPP_NUMBER:
+    case CPP_PREPARSED_EXPR:
       if (TREE_CODE (token->u.value) == USERDEF_LITERAL)
 	return cp_parser_userdef_numeric_literal (parser);
       token = cp_lexer_consume_token (parser->lexer);
@@ -4272,6 +4360,36 @@  cp_parser_primary_expression (cp_parser *parser,
 				       true);
 
     case CPP_OPEN_PAREN:
+      /* If we see `( { ' then we are looking at the beginning of
+	 a GNU statement-expression.  */
+      if (cp_parser_allow_gnu_extensions_p (parser)
+	  && cp_lexer_nth_token_is (parser->lexer, 2, CPP_OPEN_BRACE))
+	{
+	  /* Statement-expressions are not allowed by the standard.  */
+	  pedwarn (token->location, OPT_Wpedantic,
+		   "ISO C++ forbids braced-groups within expressions");
+
+	  /* And they're not allowed outside of a function-body; you
+	     cannot, for example, write:
+
+	     int i = ({ int j = 3; j + 1; });
+
+	     at class or namespace scope.  */
+	  if (!parser->in_function_body
+	      || parser->in_template_argument_list_p)
+	    {
+	      error_at (token->location,
+			"statement-expressions are not allowed outside "
+			"functions nor in template-argument lists");
+	      cp_parser_skip_to_end_of_block_or_statement (parser);
+	      if (cp_lexer_next_token_is (parser->lexer, CPP_CLOSE_PAREN))
+		cp_lexer_consume_token (parser->lexer);
+	      return error_mark_node;
+	    }
+	  else
+	    return cp_parser_statement_expr (parser);
+	}
+      /* Otherwise it's a normal parenthesized expression.  */
       {
 	tree expr;
 	bool saved_greater_than_is_operator_p;
@@ -4283,57 +4401,22 @@  cp_parser_primary_expression (cp_parser *parser,
 	saved_greater_than_is_operator_p
 	  = parser->greater_than_is_operator_p;
 	parser->greater_than_is_operator_p = true;
-	/* If we see `( { ' then we are looking at the beginning of
-	   a GNU statement-expression.  */
-	if (cp_parser_allow_gnu_extensions_p (parser)
-	    && cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE))
-	  {
-	    /* Statement-expressions are not allowed by the standard.  */
-	    pedwarn (token->location, OPT_Wpedantic, 
-		     "ISO C++ forbids braced-groups within expressions");
 
-	    /* And they're not allowed outside of a function-body; you
-	       cannot, for example, write:
+	/* Parse the parenthesized expression.  */
+	expr = cp_parser_expression (parser, idk, cast_p, decltype_p);
+	/* Let the front end know that this expression was
+	   enclosed in parentheses. This matters in case, for
+	   example, the expression is of the form `A::B', since
+	   `&A::B' might be a pointer-to-member, but `&(A::B)' is
+	   not.  */
+	expr = finish_parenthesized_expr (expr);
+	/* DR 705: Wrapping an unqualified name in parentheses
+	   suppresses arg-dependent lookup.  We want to pass back
+	   CP_ID_KIND_QUALIFIED for suppressing vtable lookup
+	   (c++/37862), but none of the others.  */
+	if (*idk != CP_ID_KIND_QUALIFIED)
+	  *idk = CP_ID_KIND_NONE;
 
-		 int i = ({ int j = 3; j + 1; });
-
-	       at class or namespace scope.  */
-	    if (!parser->in_function_body
-		|| parser->in_template_argument_list_p)
-	      {
-		error_at (token->location,
-			  "statement-expressions are not allowed outside "
-			  "functions nor in template-argument lists");
-		cp_parser_skip_to_end_of_block_or_statement (parser);
-		expr = error_mark_node;
-	      }
-	    else
-	      {
-		/* Start the statement-expression.  */
-		expr = begin_stmt_expr ();
-		/* Parse the compound-statement.  */
-		cp_parser_compound_statement (parser, expr, false, false);
-		/* Finish up.  */
-		expr = finish_stmt_expr (expr, false);
-	      }
-	  }
-	else
-	  {
-	    /* Parse the parenthesized expression.  */
-	    expr = cp_parser_expression (parser, idk, cast_p, decltype_p);
-	    /* Let the front end know that this expression was
-	       enclosed in parentheses. This matters in case, for
-	       example, the expression is of the form `A::B', since
-	       `&A::B' might be a pointer-to-member, but `&(A::B)' is
-	       not.  */
-	    expr = finish_parenthesized_expr (expr);
-	    /* DR 705: Wrapping an unqualified name in parentheses
-	       suppresses arg-dependent lookup.  We want to pass back
-	       CP_ID_KIND_QUALIFIED for suppressing vtable lookup
-	       (c++/37862), but none of the others.  */
-	    if (*idk != CP_ID_KIND_QUALIFIED)
-	      *idk = CP_ID_KIND_NONE;
-	  }
 	/* The `>' token might be the end of a template-id or
 	   template-parameter-list now.  */
 	parser->greater_than_is_operator_p
@@ -8869,6 +8952,7 @@  cp_parser_lambda_expression (cp_parser* parser)
   tree type;
   bool ok = true;
   cp_token *token = cp_lexer_peek_token (parser->lexer);
+  cp_token_position start = 0;
 
   LAMBDA_EXPR_LOCATION (lambda_expr) = token->location;
 
@@ -8882,6 +8966,15 @@  cp_parser_lambda_expression (cp_parser* parser)
 	}
       ok = false;
     }
+  else if (parser->in_template_argument_list_p)
+    {
+      if (!token->error_reported)
+	{
+	  error_at (token->location, "lambda-expression in template-argument");
+	  token->error_reported = true;
+	}
+      ok = false;
+    }
 
   /* We may be in the middle of deferred access check.  Disable
      it now.  */
@@ -8929,7 +9022,13 @@  cp_parser_lambda_expression (cp_parser* parser)
     ok &= cp_parser_lambda_declarator_opt (parser, lambda_expr);
 
     if (ok)
-      cp_parser_lambda_body (parser, lambda_expr);
+      {
+	if (!cp_parser_error_occurred (parser)
+	    && cp_lexer_next_token_is (parser->lexer, CPP_OPEN_BRACE)
+	    && cp_parser_start_tentative_firewall (parser))
+	  start = token;
+	cp_parser_lambda_body (parser, lambda_expr);
+      }
     else if (cp_parser_require (parser, CPP_OPEN_BRACE, RT_OPEN_BRACE))
       {
 	if (cp_parser_skip_to_closing_brace (parser))
@@ -8967,9 +9066,13 @@  cp_parser_lambda_expression (cp_parser* parser)
   insert_pending_capture_proxies ();
 
   if (ok)
-    return build_lambda_object (lambda_expr);
+    lambda_expr = build_lambda_object (lambda_expr);
   else
-    return error_mark_node;
+    lambda_expr = error_mark_node;
+
+  cp_parser_end_tentative_firewall (parser, start, lambda_expr);
+
+  return lambda_expr;
 }
 
 /* Parse the beginning of a lambda expression.
@@ -9503,7 +9606,7 @@  cp_parser_statement (cp_parser* parser, tree in_statement_expr,
   /* There is no statement yet.  */
   statement = NULL_TREE;
 
-  cp_lexer_save_tokens (parser->lexer);
+  saved_token_sentinel saved_tokens (parser->lexer);
   attrs_location = cp_lexer_peek_token (parser->lexer)->location;
   if (c_dialect_objc ())
     /* In obj-c++, seeing '[[' might be the either the beginning of
@@ -9668,7 +9771,7 @@  cp_parser_statement (cp_parser* parser, tree in_statement_expr,
 	    {
 	      /*  Attributes should be parsed as part of the the
 		  declaration, so let's un-parse them.  */
-	      cp_lexer_rollback_tokens (parser->lexer);
+	      saved_tokens.rollback();
 	      std_attrs = NULL_TREE;
 	    }
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-sfinae1.C b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-sfinae1.C
index 973f8a7..40abcb9 100644
--- a/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-sfinae1.C
+++ b/gcc/testsuite/g++.dg/cpp0x/lambda/lambda-sfinae1.C
@@ -8,9 +8,9 @@  struct AddRvalueReferenceImpl { typedef T type; };
 
 template <typename T>
 struct AddRvalueReferenceImpl<T, typename BoolSink<false &&
-      [] {
+      [] {			// { dg-error "lambda" }
          extern T &&tref;
-      }>::type> {		// { dg-error "lambda" }
+      }>::type> {
    typedef T &&type;
 };
 
@@ -27,9 +27,9 @@  struct IsConstructibleImpl { enum { value = 0 }; };
 
 template <typename T, typename ...Args>
 struct IsConstructibleImpl<T, typename BoolSink<false &&
-      [] {
+      [] {			// { dg-error "lambda" }
          T t( ::ImplHelpers::create<Args>() ...);
-      }>::type, Args ...> {	// { dg-error "lambda" }
+      }>::type, Args ...> {
    enum { value = 1 };
 };
 
@@ -53,3 +53,4 @@  static_assert(+IsConstructible<int &&, int &&>::value, "error");
 // { dg-prune-output "expected" }
 // { dg-prune-output "does not name a class" }
 // { dg-prune-output "static assertion" }
+// { dg-prune-output "template argument . is invalid" }
diff --git a/gcc/testsuite/g++.dg/ext/stmtexpr16.C b/gcc/testsuite/g++.dg/ext/stmtexpr16.C
new file mode 100644
index 0000000..ddce40c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/ext/stmtexpr16.C
@@ -0,0 +1,10 @@ 
+// PR c++/63455
+// { dg-options "-std=gnu++11" }
+
+int main()
+{
+    int x = 0;
+
+    // without '+0', gcc 4.6 gives a different error (no ICE though)
+    decltype(({ int y = x; y; })+0) v1 = 0;
+}