Patchwork C++0x PATCH to add -fconstexpr-depth

login
register
mail settings
Submitter Jason Merrill
Date March 15, 2011, 6:22 p.m.
Message ID <4D7FAE65.3000505@redhat.com>
Download mbox | patch
Permalink /patch/87024/
State New
Headers show

Comments

Jason Merrill - March 15, 2011, 6:22 p.m.
The draft standard specifies a minimum limit of 512 recursive constexpr 
calls in order to avoid, say, recursing through the entire range of int. 
  This patch adds a check for this recursion limit and a flag to change 
the limit.

Tested x86_64-pc-linux-gnu, applying to trunk.
commit caa7a7a82f9f99902a7116a8494c15d5c6dabd0f
Author: Jason Merrill <jason@redhat.com>
Date:   Wed Jan 26 17:41:51 2011 -0500

    c-family/
    	* c-common.c (max_constexpr_depth): New.
    	* c-common.h: Declare it.
    	* c-opts.c (c_common_handle_option): Set it.
    	* c.opt (fconstexpr-depth): New option.
    cp/
    	* semantics.c (push_cx_call_context): Return bool.
    	(cxx_eval_call_expression): Handle excess depth.
Joseph S. Myers - March 18, 2011, 6:07 p.m.
On Tue, 15 Mar 2011, Jason Merrill wrote:

> +/* Likewise, for constexpr function call evaluations.  N3225 specifies a
> +   minimum of 512.  */
>  
> +int max_constexpr_depth = 512;

Since all you're doing in the option handler is setting a global variable, 
it's better to declare the option in the .opt file with 
Var(max_constexpr_depth) Init(512).  Then you don't need to define the 
variable manually in c-common.c or declare it in c-common.h or have any 
handler code for it in c-opts.c.

> +fconstexpr-depth=
> +C++ ObjC++ Joined RejectNegative UInteger
> +-constexpr-depth=<number>	Specify maximum constexpr recursion depth

Typo in the help text: it's -fconstexpr-depth not -constexpr-depth.
Jason Merrill - March 18, 2011, 9:19 p.m.
Thanks for the feedback; fixed.

Jason

Patch

diff --git a/gcc/c-family/c-common.c b/gcc/c-family/c-common.c
index 4da9a2d..6674c58 100644
--- a/gcc/c-family/c-common.c
+++ b/gcc/c-family/c-common.c
@@ -255,12 +255,15 @@  int flag_use_repository;
 enum cxx_dialect cxx_dialect = cxx98;
 
 /* Maximum template instantiation depth.  This limit exists to limit the
-   time it takes to notice infinite template instantiations; the default
-   value of 1024 is likely to be in the next C++ standard.  */
+   time it takes to notice excessively recursive template instantiations;
+   the default value of 1024 is likely to be in the next C++ standard.  */
 
 int max_tinst_depth = 1024;
 
+/* Likewise, for constexpr function call evaluations.  N3225 specifies a
+   minimum of 512.  */
 
+int max_constexpr_depth = 512;
 
 /* The elements of `ridpointers' are identifier nodes for the reserved
    type names and storage classes.  It is indexed by a RID_... value.  */
diff --git a/gcc/c-family/c-common.h b/gcc/c-family/c-common.h
index 406def9..b6fcee9 100644
--- a/gcc/c-family/c-common.h
+++ b/gcc/c-family/c-common.h
@@ -619,10 +619,14 @@  extern enum cxx_dialect cxx_dialect;
 
 /* Maximum template instantiation depth.  This limit is rather
    arbitrary, but it exists to limit the time it takes to notice
-   infinite template instantiations.  */
+   excessively recursive template instantiations.  */
 
 extern int max_tinst_depth;
 
+/* Likewise, for constexpr function call evaluations.  */
+
+extern int max_constexpr_depth;
+
 /* Nonzero means that we should not issue warnings about problems that
    occur when the code is executed, because the code being processed
    is not expected to be executed.  This is set during parsing.  This
diff --git a/gcc/c-family/c-opts.c b/gcc/c-family/c-opts.c
index 8d6e6e7..8d2a08b 100644
--- a/gcc/c-family/c-opts.c
+++ b/gcc/c-family/c-opts.c
@@ -568,6 +568,10 @@  c_common_handle_option (size_t scode, const char *arg, int value,
 	disable_builtin_function (arg);
       break;
 
+    case OPT_fconstexpr_depth_:
+      max_constexpr_depth = value;
+      break;
+
     case OPT_fdirectives_only:
       cpp_opts->directives_only = value;
       break;
diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt
index bb928fa..f791190 100644
--- a/gcc/c-family/c.opt
+++ b/gcc/c-family/c.opt
@@ -719,6 +719,10 @@  fconstant-string-class=
 ObjC ObjC++ Joined MissingArgError(no class name specified with %qs)
 -fconst-string-class=<name>	Use class <name> for constant strings
 
+fconstexpr-depth=
+C++ ObjC++ Joined RejectNegative UInteger
+-constexpr-depth=<number>	Specify maximum constexpr recursion depth
+
 fdeduce-init-list
 C++ ObjC++ Var(flag_deduce_init_list) Init(1)
 -fno-deduce-init-list	disable deduction of std::initializer_list for a template type parameter from a brace-enclosed initializer-list
diff --git a/gcc/cp/semantics.c b/gcc/cp/semantics.c
index a0f48c0e..53497f3 100644
--- a/gcc/cp/semantics.c
+++ b/gcc/cp/semantics.c
@@ -5922,17 +5922,21 @@  cxx_bind_parameters_in_call (const constexpr_call *old_call, tree t,
 /* Variables and functions to manage constexpr call expansion context.
    These do not need to be marked for PCH or GC.  */
 
+/* FIXME remember and print actual constant arguments.  */
 static VEC(tree,heap) *call_stack = NULL;
 static int call_stack_tick;
 static int last_cx_error_tick;
 
-static void
+static bool
 push_cx_call_context (tree call)
 {
   ++call_stack_tick;
   if (!EXPR_HAS_LOCATION (call))
     SET_EXPR_LOCATION (call, input_location);
   VEC_safe_push (tree, heap, call_stack, call);
+  if (VEC_length (tree, call_stack) > (unsigned) max_constexpr_depth)
+    return false;
+  return true;
 }
 
 static void
@@ -5967,6 +5971,9 @@  cxx_eval_call_expression (const constexpr_call *old_call, tree t,
   tree result;
   constexpr_call new_call = { NULL, NULL, NULL, 0 };
   constexpr_call **slot;
+  constexpr_call *entry;
+  bool depth_ok;
+
   if (TREE_CODE (fun) != FUNCTION_DECL)
     {
       /* Might be a constexpr function pointer.  */
@@ -6029,7 +6036,7 @@  cxx_eval_call_expression (const constexpr_call *old_call, tree t,
   if (*non_constant_p)
     return t;
 
-  push_cx_call_context (t);
+  depth_ok = push_cx_call_context (t);
 
   new_call.hash
     = iterative_hash_template_arg (new_call.bindings,
@@ -6039,37 +6046,43 @@  cxx_eval_call_expression (const constexpr_call *old_call, tree t,
   maybe_initialize_constexpr_call_table ();
   slot = (constexpr_call **)
     htab_find_slot (constexpr_call_table, &new_call, INSERT);
-  if (*slot != NULL)
-    {
-      /* Calls which are in progress have their result set to NULL
-         so that we can detect circular dependencies.  */
-      if ((*slot)->result == NULL)
-        {
-	  if (!allow_non_constant)
-	    error ("call has circular dependency");
-	  (*slot)->result = result = error_mark_node;
-        }
-      else
-	{
-	  result = (*slot)->result;
-	  if (result == error_mark_node && !allow_non_constant)
-	    /* Re-evaluate to get the error.  */
-	    cxx_eval_constant_expression (&new_call, new_call.fundef->body,
-					  allow_non_constant, addr,
-					  non_constant_p);
-	}
-    }
-  else
+  entry = *slot;
+  if (entry == NULL)
     {
       /* We need to keep a pointer to the entry, not just the slot, as the
 	 slot can move in the call to cxx_eval_builtin_function_call.  */
-      constexpr_call *entry = ggc_alloc_constexpr_call ();
+      *slot = entry = ggc_alloc_constexpr_call ();
       *entry = new_call;
-      *slot = entry;
-      result
-	= cxx_eval_constant_expression (&new_call, new_call.fundef->body,
-					allow_non_constant, addr,
-					non_constant_p);
+    }
+  /* Calls which are in progress have their result set to NULL
+     so that we can detect circular dependencies.  */
+  else if (entry->result == NULL)
+    {
+      if (!allow_non_constant)
+	error ("call has circular dependency");
+      *non_constant_p = true;
+      entry->result = result = error_mark_node;
+    }
+
+  if (!depth_ok)
+    {
+      if (!allow_non_constant)
+	error ("constexpr evaluation depth exceeds maximum of %d (use "
+	       "-fconstexpr-depth= to increase the maximum)",
+	       max_constexpr_depth);
+      *non_constant_p = true;
+      entry->result = result = error_mark_node;
+    }
+  else
+    {
+      result = entry->result;
+      if (!result || (result == error_mark_node && !allow_non_constant))
+	result = (cxx_eval_constant_expression
+		  (&new_call, new_call.fundef->body,
+		   allow_non_constant, addr,
+		   non_constant_p));
+      if (result == error_mark_node)
+	*non_constant_p = true;
       if (*non_constant_p)
 	entry->result = result = error_mark_node;
       else
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index aff5a05..b498eac 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -181,7 +181,7 @@  in the following sections.
 @item C++ Language Options
 @xref{C++ Dialect Options,,Options Controlling C++ Dialect}.
 @gccoptlist{-fabi-version=@var{n}  -fno-access-control  -fcheck-new @gol
--fconserve-space  -ffriend-injection @gol
+-fconserve-space  -fconstexpr-depth=@var{n}  -ffriend-injection @gol
 -fno-elide-constructors @gol
 -fno-enforce-eh-specs @gol
 -ffor-scope  -fno-for-scope  -fno-gnu-keywords @gol
@@ -1881,6 +1881,13 @@  two definitions were merged.
 This option is no longer useful on most targets, now that support has
 been added for putting variables into BSS without making them common.
 
+@item -fconstexpr-depth=@var{n}
+@opindex fconstexpr-depth
+Set the maximum nested evaluation depth for C++0x constexpr functions
+to @var{n}.  A limit is needed to detect endless recursion during
+constant expression evaluation.  The minimum specified by the standard
+is 512; G++ defaults to 1024.
+
 @item -fno-deduce-init-list
 @opindex fno-deduce-init-list
 Disable deduction of a template type parameter as
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-recursion.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-recursion.C
new file mode 100644
index 0000000..2f9b488
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-recursion.C
@@ -0,0 +1,5 @@ 
+// Test that we catch excessive recursion.
+// { dg-options "-std=c++0x -fconstexpr-depth=5" }
+// { dg-prune-output "in constexpr expansion" }
+constexpr int f (int i) { return f (i-1); }
+constexpr int i = f(42);	// { dg-error "constexpr evaluation depth" }