https://github.com/arrowten updated 
https://github.com/llvm/llvm-project/pull/192678

>From a41f5636c201ff15c41d8e3d18ac4ef1c7d0277b Mon Sep 17 00:00:00 2001
From: Ajay Wakodikar <[email protected]>
Date: Fri, 8 May 2026 07:49:43 -0400
Subject: [PATCH] [Sema] Reject unqualified lookup of local nested class

---
 clang/docs/ReleaseNotes.rst                   |  1 +
 clang/lib/Sema/SemaDecl.cpp                   | 24 +++++-
 .../Sema/unqualified-lookup-local-class.cpp   | 77 +++++++++++++++++++
 3 files changed, 100 insertions(+), 2 deletions(-)
 create mode 100644 clang/test/Sema/unqualified-lookup-local-class.cpp

diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst
index b49286b35c6b0..e2ff9f95ddfb4 100644
--- a/clang/docs/ReleaseNotes.rst
+++ b/clang/docs/ReleaseNotes.rst
@@ -610,6 +610,7 @@ Bug Fixes to C++ Support
 - Fixed crashes in Itanium C++ name mangling for lambdas with trailing 
requires-clauses involving requires-expressions. (#GH100774) (#GH123854)
 - Fixed an invalid rejection and assertion failure while generating 
``operator=`` for fields with the ``__restrict`` qualifier. (#GH37979)
 - Fixed a use-after-free bug when parsing default arguments containing lambdas 
in declarations with template-id declarators. (#GH196725)
+- Fixed an issue where Clang incorrectly accepted invalid unqualified uses of 
local nested class names outside their declaring scope. (#GH184622)
 
 Bug Fixes to AST Handling
 ^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp
index bec351528760a..4b9576479e29e 100644
--- a/clang/lib/Sema/SemaDecl.cpp
+++ b/clang/lib/Sema/SemaDecl.cpp
@@ -1585,8 +1585,28 @@ void Sema::PushOnScopeChains(NamedDecl *D, Scope *S, 
bool AddToContext) {
 
   // Out-of-line definitions shouldn't be pushed into scope in C++, unless they
   // are function-local declarations.
-  if (getLangOpts().CPlusPlus && D->isOutOfLine() && !S->getFnParent())
-    return;
+  if (getLangOpts().CPlusPlus && D->isOutOfLine()) {
+    if (!S->getFnParent())
+      return;
+
+    // Even inside a function, an out-of-line definition of a type that is
+    // nested inside a local class must not be pushed into the enclosing
+    // function scope. For example:
+    //
+    //   class A { public: class B; };
+    //   class A::B {};   // out-of-line definition inside the function
+    //   B b;             // must fail - only A::B is valid
+    //   Wrapper{B{}}     // must also fail
+    //
+    // Per C++ scoping rules only the qualified form A::B is accessible.
+    // Without this guard, PushOnScopeChains would add B to the function's
+    // local scope, making it findable via unqualified lookup, which is
+    // incorrect. The condition targets TagDecls (class/struct/union/enum)
+    // whose DeclContext is a CXXRecordDecl, i.e., types that are members
+    // of a local class being defined out-of-line.
+    if (isa<TagDecl>(D) && isa<CXXRecordDecl>(D->getDeclContext()))
+      return;
+  }
 
   // Template instantiations should also not be pushed into scope.
   if (isa<FunctionDecl>(D) &&
diff --git a/clang/test/Sema/unqualified-lookup-local-class.cpp 
b/clang/test/Sema/unqualified-lookup-local-class.cpp
new file mode 100644
index 0000000000000..bdc6d0b0e38ee
--- /dev/null
+++ b/clang/test/Sema/unqualified-lookup-local-class.cpp
@@ -0,0 +1,77 @@
+// RUN: %clang_cc1 -std=c++20 -verify %s
+
+// Regression test for PR184622: out-of-line definitions of nested classes
+// inside a function must not be visible via unqualified lookup.
+
+// Unqualified member access inside a class body is fine.
+struct A1 {
+  int x;
+  void f() { x = 1; } // OK
+};
+
+struct Base {
+  int y;
+};
+
+struct Derived : Base {
+  void f() { y = 2; } // OK - inherited member
+};
+
+struct A2 { int z; };
+struct B2 : A2 {
+  using A2::z;
+  void f() { z = 3; } // OK
+};
+
+// Out-of-line definitions at namespace scope are fine.
+class OuterNS { public: class Inner; };
+class OuterNS::Inner {}; // OK - not inside a function
+
+// Out-of-line definitions of types nested in a class inside a function:
+// the qualified form must work, but the unqualified name must not leak.
+void pass1() {
+  struct A { struct B {}; };
+  A::B b; // OK - qualified
+
+  class C { public: class B; };
+  class C::B {};
+  C::B b2; // OK - qualified
+}
+
+template<class T>
+struct Wrapper { Wrapper(T) {} };
+
+// class nested in class, defined out-of-line inside function - unqualified 'B'
+// must not be found.
+void fail_class() {
+  class A { public: class B; };
+  class A::B {};
+  B b;                      // expected-error {{unknown type name 'B'}}
+  Wrapper w = Wrapper{B{}}; // expected-error {{use of undeclared identifier 
'B'}}
+  A::B ok;                  // OK - qualified lookup still works
+}
+
+// Same rule applies to structs (TagDecl covers class/struct/union/enum).
+void fail_struct() {
+  struct Outer { struct Inner; };
+  struct Outer::Inner {};
+  Inner bad;         // expected-error {{unknown type name 'Inner'}}
+  Outer::Inner good; // OK
+}
+
+// Union nested in a class inside a function.
+void fail_union() {
+  struct S { union U; };
+  union S::U { int a; float b; }; // expected-note {{'S::U' declared here}}
+  U bad;   // expected-error {{unknown type name 'U'}}
+  S::U ok; // OK
+}
+
+// Multiple independent functions: each has its own scope, so 'B' in fail2
+// is unrelated to 'B' in fail_class above.
+void fail2() {
+  class Outer { public: class Inner; };
+  class Outer::Inner {};
+  Inner bad;         // expected-error {{unknown type name 'Inner'}}
+  Outer::Inner good; // OK
+}

_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to