https://github.com/arrowten updated https://github.com/llvm/llvm-project/pull/192678
>From 014d410f76ad658139bef8ac8a0d1771652d256e 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 745f9e6556eec..c54b6e5a1757e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -614,6 +614,7 @@ Bug Fixes to C++ Support - 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 a crash in constant evaluation using placement new on an array which was later initialized. (#GH196450) +- 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
