Skip to content

Conversation

@ilovepi
Copy link
Contributor

@ilovepi ilovepi commented Nov 7, 2025

There is a possible nullptr deref in BuildCXXNestedNameSpecifier when calling ExtendNestedNameSpecifier or using isa<>. This initially showed up as a crash in clangd, that didn't manifest in when compiling w/ clang. The reduced test case added in this patch, however does expose the issue in clang. Testing locally shows that both this test case and the original clangd issue are fixed by checking the validity of the pointer before trying to dispatch. Since all code paths require the pointer to be valid (usually by virtue of a dyn_cast or isa<> check), there should be no functional difference.

Fixes #166843

There is a possible nullptr deref in BuildCXXNestedNameSpecifier when
calling ExtendNestedNameSpecifier or using isa<>. This initially showed
up as a crash in clangd, that didn't manifest in when compiling w/
clang. The reduced test case added in this patch, however does expose
the issue in clang. Testing locally shows that both this test case and
the original clangd issue are fixed by checking the validity of the
pointer before trying to dispatch. Since all code paths require the
pointer to be valid (usually by virtue of a dyn_cast or isa<> check),
there should be no functional difference.

Fixes llvm#166843
@ilovepi ilovepi marked this pull request as ready for review November 7, 2025 18:59
@llvmbot llvmbot added clang Clang issues not falling into any other category clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Nov 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Nov 7, 2025

@llvm/pr-subscribers-clang

Author: Paul Kirth (ilovepi)

Changes

There is a possible nullptr deref in BuildCXXNestedNameSpecifier when calling ExtendNestedNameSpecifier or using isa<>. This initially showed up as a crash in clangd, that didn't manifest in when compiling w/ clang. The reduced test case added in this patch, however does expose the issue in clang. Testing locally shows that both this test case and the original clangd issue are fixed by checking the validity of the pointer before trying to dispatch. Since all code paths require the pointer to be valid (usually by virtue of a dyn_cast or isa<> check), there should be no functional difference.

Fixes #166843


Full diff: https://github.com/llvm/llvm-project/pull/166995.diff

1 Files Affected:

  • (modified) clang/lib/Sema/SemaCXXScopeSpec.cpp (+20-19)
diff --git a/clang/lib/Sema/SemaCXXScopeSpec.cpp b/clang/lib/Sema/SemaCXXScopeSpec.cpp
index c52fc5bf815af..29e697d9eb029 100644
--- a/clang/lib/Sema/SemaCXXScopeSpec.cpp
+++ b/clang/lib/Sema/SemaCXXScopeSpec.cpp
@@ -779,25 +779,26 @@ bool Sema::BuildCXXNestedNameSpecifier(Scope *S, NestedNameSpecInfo &IdInfo,
   }
 
   if (!Found.empty()) {
-    const auto *ND = Found.getAsSingle<NamedDecl>();
-    if (::ExtendNestedNameSpecifier(*this, SS, ND, IdInfo.IdentifierLoc,
-                                    IdInfo.CCLoc)) {
-      const Type *T = SS.getScopeRep().getAsType();
-      Diag(IdInfo.IdentifierLoc, diag::err_expected_class_or_namespace)
-          << QualType(T, 0) << getLangOpts().CPlusPlus;
-      // Recover with this type if it would be a valid nested name specifier.
-      return !T->getAsCanonical<TagType>();
-    }
-    if (isa<TemplateDecl>(ND)) {
-      ParsedType SuggestedType;
-      DiagnoseUnknownTypeName(IdInfo.Identifier, IdInfo.IdentifierLoc, S, &SS,
-                              SuggestedType);
-    } else {
-      Diag(IdInfo.IdentifierLoc, diag::err_expected_class_or_namespace)
-          << IdInfo.Identifier << getLangOpts().CPlusPlus;
-      if (NamedDecl *ND = Found.getAsSingle<NamedDecl>())
-        Diag(ND->getLocation(), diag::note_entity_declared_at)
-            << IdInfo.Identifier;
+    if (const auto *ND = Found.getAsSingle<NamedDecl>()) {
+      if (::ExtendNestedNameSpecifier(*this, SS, ND, IdInfo.IdentifierLoc,
+                                      IdInfo.CCLoc)) {
+        const Type *T = SS.getScopeRep().getAsType();
+        Diag(IdInfo.IdentifierLoc, diag::err_expected_class_or_namespace)
+            << QualType(T, 0) << getLangOpts().CPlusPlus;
+        // Recover with this type if it would be a valid nested name specifier.
+        return !T->getAsCanonical<TagType>();
+      }
+      if (isa<TemplateDecl>(ND)) {
+        ParsedType SuggestedType;
+        DiagnoseUnknownTypeName(IdInfo.Identifier, IdInfo.IdentifierLoc, S, &SS,
+                                SuggestedType);
+      } else {
+        Diag(IdInfo.IdentifierLoc, diag::err_expected_class_or_namespace)
+            << IdInfo.Identifier << getLangOpts().CPlusPlus;
+        if (NamedDecl *ND = Found.getAsSingle<NamedDecl>())
+          Diag(ND->getLocation(), diag::note_entity_declared_at)
+              << IdInfo.Identifier;
+      }
     }
   } else if (SS.isSet())
     Diag(IdInfo.IdentifierLoc, diag::err_no_member) << IdInfo.Identifier

Copy link
Contributor

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this.

Please include test case.

if (NamedDecl *ND = Found.getAsSingle<NamedDecl>())
Diag(ND->getLocation(), diag::note_entity_declared_at)
<< IdInfo.Identifier;
if (const auto *ND = Found.getAsSingle<NamedDecl>()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please bail out early to reduce the diff.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

@ilovepi
Copy link
Contributor Author

ilovepi commented Nov 7, 2025

Thanks for working on this.

Please include test case.

Thanks for pointing that out. I forgot to add it to git 🤦

Copy link
Contributor

@mizvekov mizvekov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, Thanks.

@github-actions
Copy link

github-actions bot commented Nov 7, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

namespace a {
template <class b>
void c() {
((::c::)); // expected-error {{expected unqualified-id}}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like we aren't actually diagnosing the error? I'd expect something like error: 'c' is not a class, namespace, or enumeration, which is what we generate in the non-template case.

What happens if you instead write the following?

  ((::c::x));

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like we aren't actually diagnosing the error? I'd expect something like error: 'c' is not a class, namespace, or enumeration, which is what we generate in the non-template case.

Yeah, that was something I wasn't sure about.

What happens if you instead write the following?

  ((::c::x));

Looks like we report nothing, the way I've structured it. That version still causes the crash w/o the nullptr check, so let me update the check to make sure it works in the expected way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i've updated the early exit by duplicating the diagnostic. I didn't see an obvious way to avoid doing that. Happy to amend if this still isn't quite right.

@ilovepi ilovepi disabled auto-merge November 7, 2025 21:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[clangd] Assertion failure in clangd: Assertion `detail::isPresent(Val) && "dyn_cast on a non-existent value"' failed.

4 participants