c++: fix ICE with static local var in NSDMI of local class [PR123354]

Message ID 20260106121248.161723-2-peter0x44@disroot.org
State New
Headers
Series c++: fix ICE with static local var in NSDMI of local class [PR123354] |

Commit Message

Peter Damianov Jan. 6, 2026, 12:12 p.m. UTC
  When a local class has an NSDMI that references a static local variable
from the enclosing template function, we ICE during synthesized method
walking.

The issue is that r14-8981 added maybe_push_to_top_level in
synthesized_method_walk, which calls push_function_context for
function-local classes (non-lambdas). This clears current_function_decl.
When processing an NSDMI that references a static local variable from
the enclosing function, tsubst_decl calls enclosing_instantiation_of to
find the function instantiation.
That function walks up current_function_decl looking for a function with
matching source location, but since current_function_decl is NULL, the
loop exits immediately and hits gcc_unreachable().

This patch makes enclosing_instantiation_of return NULL_TREE when it
cannot find the enclosing instantiation, and have the caller fall back
to using tsubst_decl on DECL_CONTEXT to obtain the instantiated function.

gcc/cp/ChangeLog:

	PR c++/123354
	* pt.cc (enclosing_instantiation_of): Return NULL_TREE instead
	of crashing when current_function_decl is NULL.
	(tsubst_decl): Handle NULL return from enclosing_instantiation_of
	by using tsubst to find the enclosing function instantiation.

gcc/testsuite/ChangeLog:

	PR c++/123354
	* g++.dg/template/pr123354.C: New test.
---
 gcc/cp/pt.cc                             | 10 +++++++++-
 gcc/testsuite/g++.dg/template/pr123354.C | 18 ++++++++++++++++++
 2 files changed, 27 insertions(+), 1 deletion(-)
 create mode 100644 gcc/testsuite/g++.dg/template/pr123354.C
  

Comments

Patrick Palka Jan. 10, 2026, 10:24 p.m. UTC | #1
On Tue, 6 Jan 2026, Peter Damianov wrote:

> When a local class has an NSDMI that references a static local variable
> from the enclosing template function, we ICE during synthesized method
> walking.
> 
> The issue is that r14-8981 added maybe_push_to_top_level in
> synthesized_method_walk, which calls push_function_context for
> function-local classes (non-lambdas). This clears current_function_decl.

But push_function_context clears cfun, not current_function_decl?
Ah, and then pop_function_context clobbers current_function_decl with
the saved cfun, which is NULL here.  So the problem really seems to be
with pop_function_context assuming current_function_decl must agree with
cfun which isn't true in the C++ front end.  (cfun is generally set when
parsing/processing a function, and current_function_decl is for
temporarily entering the scope of a function.)

Perhaps maybe_push/pop_top_level needs to save/restore
current_function_decl separately alongside calling
push/pop_function_context?  Or maybe replace the push/pop_function_context
calls with our own saving/restoring of cfun?  Wonder what Jason thinks.

> When processing an NSDMI that references a static local variable from
> the enclosing function, tsubst_decl calls enclosing_instantiation_of to
> find the function instantiation.
> That function walks up current_function_decl looking for a function with
> matching source location, but since current_function_decl is NULL, the
> loop exits immediately and hits gcc_unreachable().
> 
> This patch makes enclosing_instantiation_of return NULL_TREE when it
> cannot find the enclosing instantiation, and have the caller fall back
> to using tsubst_decl on DECL_CONTEXT to obtain the instantiated function.
> 
> gcc/cp/ChangeLog:
> 
> 	PR c++/123354
> 	* pt.cc (enclosing_instantiation_of): Return NULL_TREE instead
> 	of crashing when current_function_decl is NULL.
> 	(tsubst_decl): Handle NULL return from enclosing_instantiation_of
> 	by using tsubst to find the enclosing function instantiation.
> 
> gcc/testsuite/ChangeLog:
> 
> 	PR c++/123354
> 	* g++.dg/template/pr123354.C: New test.
> ---
>  gcc/cp/pt.cc                             | 10 +++++++++-
>  gcc/testsuite/g++.dg/template/pr123354.C | 18 ++++++++++++++++++
>  2 files changed, 27 insertions(+), 1 deletion(-)
>  create mode 100644 gcc/testsuite/g++.dg/template/pr123354.C
> 
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 759418c344e..4ec9cd71b20 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -15520,7 +15520,9 @@ enclosing_instantiation_of (tree tctx)
>    for (; fn; fn = decl_function_context (fn))
>      if (DECL_SOURCE_LOCATION (fn) == DECL_SOURCE_LOCATION (tctx))
>        return fn;
> -  gcc_unreachable ();
> +
> +  /* If we can't find the enclosing instantiation, return NULL.  */
> +  return NULL_TREE;
>  }
>  
>  /* Substitute the ARGS into the T, which is a _DECL.  Return the
> @@ -15938,6 +15940,12 @@ tsubst_decl (tree t, tree args, tsubst_flags_t complain,
>  	    if (TREE_STATIC (t))
>  	      {
>  		tree fn = enclosing_instantiation_of (DECL_CONTEXT (t));
> +		if (fn == NULL_TREE)
> +		  /* We're in a context where current_function_decl is cleared
> +		     (e.g., synthesized_method_walk); use tsubst_decl to find
> +		     the enclosing function instantiation.  */
> +		  fn = tsubst_decl (DECL_CONTEXT (t), args, complain,
> +				    use_spec_table);
>  		if (fn != current_function_decl)
>  		  ctx = fn;
>  	      }
> diff --git a/gcc/testsuite/g++.dg/template/pr123354.C b/gcc/testsuite/g++.dg/template/pr123354.C
> new file mode 100644
> index 00000000000..f87956a4c39
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/template/pr123354.C
> @@ -0,0 +1,18 @@
> +// PR c++/123354
> +// { dg-do compile { target c++11 } }
> +// ICE with static local var referenced in NSDMI of local class
> +
> +template<typename T>
> +void foo() {
> +    static constexpr int value = 42;
> +    struct s1_t {
> +        struct s2_t {
> +            int dummy { 0 };
> +            char* ptr { static_cast<char*>(::operator new(sizeof(value)))};
> +        } s2;
> +    } object;
> +}
> +
> +int main() {
> +    foo<void>();
> +}
> -- 
> 2.52.0
> 
>
  
Jason Merrill Jan. 28, 2026, 7:01 a.m. UTC | #2
On 1/11/26 6:24 AM, Patrick Palka wrote:
> On Tue, 6 Jan 2026, Peter Damianov wrote:
> 
>> When a local class has an NSDMI that references a static local variable
>> from the enclosing template function, we ICE during synthesized method
>> walking.
>>
>> The issue is that r14-8981 added maybe_push_to_top_level in
>> synthesized_method_walk, which calls push_function_context for
>> function-local classes (non-lambdas). This clears current_function_decl.
> 
> But push_function_context clears cfun, not current_function_decl?
> Ah, and then pop_function_context clobbers current_function_decl with
> the saved cfun, which is NULL here.  So the problem really seems to be
> with pop_function_context assuming current_function_decl must agree with
> cfun which isn't true in the C++ front end.  (cfun is generally set when
> parsing/processing a function, and current_function_decl is for
> temporarily entering the scope of a function.)
> 
> Perhaps maybe_push/pop_top_level needs to save/restore
> current_function_decl separately alongside calling
> push/pop_function_context?  Or maybe replace the push/pop_function_context
> calls with our own saving/restoring of cfun?  Wonder what Jason thinks.

This was fixed in a different way by r16-7034-g4652ca53fcbf01 so I think 
this patch is no longer needed.  Thanks for the analysis.

Jason
  

Patch

diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 759418c344e..4ec9cd71b20 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -15520,7 +15520,9 @@  enclosing_instantiation_of (tree tctx)
   for (; fn; fn = decl_function_context (fn))
     if (DECL_SOURCE_LOCATION (fn) == DECL_SOURCE_LOCATION (tctx))
       return fn;
-  gcc_unreachable ();
+
+  /* If we can't find the enclosing instantiation, return NULL.  */
+  return NULL_TREE;
 }
 
 /* Substitute the ARGS into the T, which is a _DECL.  Return the
@@ -15938,6 +15940,12 @@  tsubst_decl (tree t, tree args, tsubst_flags_t complain,
 	    if (TREE_STATIC (t))
 	      {
 		tree fn = enclosing_instantiation_of (DECL_CONTEXT (t));
+		if (fn == NULL_TREE)
+		  /* We're in a context where current_function_decl is cleared
+		     (e.g., synthesized_method_walk); use tsubst_decl to find
+		     the enclosing function instantiation.  */
+		  fn = tsubst_decl (DECL_CONTEXT (t), args, complain,
+				    use_spec_table);
 		if (fn != current_function_decl)
 		  ctx = fn;
 	      }
diff --git a/gcc/testsuite/g++.dg/template/pr123354.C b/gcc/testsuite/g++.dg/template/pr123354.C
new file mode 100644
index 00000000000..f87956a4c39
--- /dev/null
+++ b/gcc/testsuite/g++.dg/template/pr123354.C
@@ -0,0 +1,18 @@ 
+// PR c++/123354
+// { dg-do compile { target c++11 } }
+// ICE with static local var referenced in NSDMI of local class
+
+template<typename T>
+void foo() {
+    static constexpr int value = 42;
+    struct s1_t {
+        struct s2_t {
+            int dummy { 0 };
+            char* ptr { static_cast<char*>(::operator new(sizeof(value)))};
+        } s2;
+    } object;
+}
+
+int main() {
+    foo<void>();
+}