Author: Emery Conrad
Date: 2026-09-16T09:49:45+03:00
New Revision: 5835ef200a1fdd0aa92578ee824772eea4143279

URL: 
https://github.com/llvm/llvm-project/commit/5835ef200a1fdd0aa92578ee824772eea4143279
DIFF: 
https://github.com/llvm/llvm-project/commit/5835ef200a1fdd0aa92578ee824772eea4143279.diff

LOG: [clang] Fix Itanium mangler crash on lambdas in top-level statements 
(#217041)

`clang-repl` asserts when a lambda in a top-level statement needs a
mangled name, for example `ns::call([]{});`. Since #84150 a
`TopLevelStmtDecl` is a `DeclContext`, and `manglePrefix` casts it to
`NamedDecl`.

Treat `TopLevelStmtDecl` as a local container and give it a synthesized
internal encoding (`L9__stmt__0v`), like `Ub_` for block literals.
Entities in a top-level statement then mangle as `<local-name>`s with
discriminators, so same-named locals in two statements do not collide.
Each `TopLevelStmtDecl` gets an ordinal at creation, in parse order; it
is serialized.

Tests: `Interpreter/lambda-top-level-stmt.cpp` (crashes without the fix)
and `CodeGenCXX/top-level-stmt-local-names.cpp` (two lambdas, two
same-named local classes). The interpreter test is `UNSUPPORTED:
system-windows`: MSVC compat enables `-fdelayed-template-parsing`, and
clang-repl asserts on any late-parsed template in a later fragment,
lambda or not.

Co-authored-by: Emery Conrad <[email protected]>

Added: 
    clang/test/CodeGenCXX/top-level-stmt-local-names.cpp
    clang/test/Interpreter/lambda-top-level-stmt.cpp

Modified: 
    clang/include/clang/AST/ASTContext.h
    clang/include/clang/AST/Decl.h
    clang/lib/AST/Decl.cpp
    clang/lib/AST/ItaniumMangle.cpp
    clang/lib/Serialization/ASTReaderDecl.cpp
    clang/lib/Serialization/ASTWriterDecl.cpp

Removed: 
    


################################################################################
diff  --git a/clang/include/clang/AST/ASTContext.h 
b/clang/include/clang/AST/ASTContext.h
index 5bb132f03db71..8d48c2b7c2cba 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -3658,6 +3658,9 @@ class ASTContext : public RefCountedBase<ASTContext> {
   void setStaticLocalNumber(const VarDecl *VD, unsigned Number);
   unsigned getStaticLocalNumber(const VarDecl *VD) const;
 
+  /// Ordinal for the next TopLevelStmtDecl; counts created and loaded ones.
+  unsigned NumTopLevelStmtDecls = 0;
+
   bool hasSeenTypeAwareOperatorNewOrDelete() const {
     return !TypeAwareOperatorNewAndDeletes.empty();
   }

diff  --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h
index eafeeecac7794..c64dc8c02d40e 100644
--- a/clang/include/clang/AST/Decl.h
+++ b/clang/include/clang/AST/Decl.h
@@ -4773,6 +4773,8 @@ class TopLevelStmtDecl : public Decl, public DeclContext {
 
   Stmt *Statement = nullptr;
   bool IsSemiMissing = false;
+  /// Position among all top-level statements of the session, in parse order.
+  unsigned Ordinal = 0;
 
   TopLevelStmtDecl(DeclContext *DC, SourceLocation L, Stmt *S)
       : Decl(TopLevelStmt, DC, L), DeclContext(TopLevelStmt), Statement(S) {}
@@ -4789,6 +4791,7 @@ class TopLevelStmtDecl : public Decl, public DeclContext {
   void setStmt(Stmt *S);
   bool isSemiMissing() const { return IsSemiMissing; }
   void setSemiMissing(bool Missing = true) { IsSemiMissing = Missing; }
+  unsigned getOrdinal() const { return Ordinal; }
 
   static bool classof(const Decl *D) { return classofKind(D->getKind()); }
   static bool classofKind(Kind K) { return K == TopLevelStmt; }

diff  --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp
index 34f5d0abe5f74..8c1418625bf33 100644
--- a/clang/lib/AST/Decl.cpp
+++ b/clang/lib/AST/Decl.cpp
@@ -5938,7 +5938,9 @@ TopLevelStmtDecl *TopLevelStmtDecl::Create(ASTContext &C, 
Stmt *Statement) {
   SourceLocation Loc = Statement ? Statement->getBeginLoc() : SourceLocation();
   DeclContext *DC = C.getTranslationUnitDecl();
 
-  return new (C, DC) TopLevelStmtDecl(DC, Loc, Statement);
+  auto *D = new (C, DC) TopLevelStmtDecl(DC, Loc, Statement);
+  D->Ordinal = C.NumTopLevelStmtDecls++;
+  return D;
 }
 
 TopLevelStmtDecl *TopLevelStmtDecl::CreateDeserialized(ASTContext &C,

diff  --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp
index 3a3cde3448f44..af2ffaca4a6b2 100644
--- a/clang/lib/AST/ItaniumMangle.cpp
+++ b/clang/lib/AST/ItaniumMangle.cpp
@@ -45,7 +45,8 @@ namespace UnsupportedItaniumManglingKind =
 namespace {
 
 static bool isLocalContainerContext(const DeclContext *DC) {
-  return isa<FunctionDecl, ObjCMethodDecl, BlockDecl, 
CXXExpansionStmtDecl>(DC);
+  return isa<FunctionDecl, ObjCMethodDecl, BlockDecl, CXXExpansionStmtDecl,
+             TopLevelStmtDecl>(DC);
 }
 
 static const FunctionDecl *getStructor(const FunctionDecl *fn) {
@@ -516,6 +517,7 @@ class CXXNameMangler {
                        ArrayRef<StringRef> AdditionalAbiTags = {});
   void mangleBlockForPrefix(const BlockDecl *Block);
   void mangleUnqualifiedBlock(const BlockDecl *Block);
+  void mangleTopLevelStmtEncoding(const TopLevelStmtDecl *D);
   void mangleTemplateParamDecl(const NamedDecl *Decl);
   void mangleTemplateParameterList(const TemplateParameterList *Params);
   void mangleTypeConstraint(TemplateName Concept,
@@ -1906,6 +1908,8 @@ void CXXNameMangler::mangleLocalName(GlobalDecl GD,
       mangleObjCMethodName(MD);
     } else if (const BlockDecl *BD = dyn_cast<BlockDecl>(DC)) {
       mangleBlockForPrefix(BD);
+    } else if (const auto *TLSD = dyn_cast<TopLevelStmtDecl>(DC)) {
+      mangleTopLevelStmtEncoding(TLSD);
     } else {
       mangleFunctionEncoding(getParentOfLocalEntity(DC));
     }
@@ -2043,6 +2047,13 @@ void CXXNameMangler::mangleUnqualifiedBlock(const 
BlockDecl *Block) {
   Out << '_';
 }
 
+void CXXNameMangler::mangleTopLevelStmtEncoding(const TopLevelStmtDecl *D) {
+  // Numbered internal function, like Ub_ for blocks: locals get <local-name>s.
+  SmallString<16> Name("__stmt__");
+  Name += llvm::utostr(D->getOrdinal());
+  Out << 'L' << Name.size() << Name << 'v';
+}
+
 // <template-param-decl>
 //   ::= Ty                                  # template type parameter
 //   ::= Tk <concept name> [<template-args>] # constrained type parameter

diff  --git a/clang/lib/Serialization/ASTReaderDecl.cpp 
b/clang/lib/Serialization/ASTReaderDecl.cpp
index d4c47a81ed32f..7fc585b12153b 100644
--- a/clang/lib/Serialization/ASTReaderDecl.cpp
+++ b/clang/lib/Serialization/ASTReaderDecl.cpp
@@ -1778,6 +1778,10 @@ void 
ASTDeclReader::VisitFileScopeAsmDecl(FileScopeAsmDecl *AD) {
 
 void ASTDeclReader::VisitTopLevelStmtDecl(TopLevelStmtDecl *D) {
   VisitDecl(D);
+  D->Ordinal = Record.readInt();
+  // Keep new statements numbered after the ones loaded from an AST file.
+  ASTContext &Ctx = Reader.getContext();
+  Ctx.NumTopLevelStmtDecls = std::max(Ctx.NumTopLevelStmtDecls, D->Ordinal + 
1);
   D->Statement = Record.readStmt();
 }
 

diff  --git a/clang/lib/Serialization/ASTWriterDecl.cpp 
b/clang/lib/Serialization/ASTWriterDecl.cpp
index 6f67acf9a6e7e..73fe1ee990eb9 100644
--- a/clang/lib/Serialization/ASTWriterDecl.cpp
+++ b/clang/lib/Serialization/ASTWriterDecl.cpp
@@ -1437,6 +1437,7 @@ void 
ASTDeclWriter::VisitFileScopeAsmDecl(FileScopeAsmDecl *D) {
 
 void ASTDeclWriter::VisitTopLevelStmtDecl(TopLevelStmtDecl *D) {
   VisitDecl(D);
+  Record.push_back(D->getOrdinal());
   Record.AddStmt(D->getStmt());
   Code = serialization::DECL_TOP_LEVEL_STMT_DECL;
 }

diff  --git a/clang/test/CodeGenCXX/top-level-stmt-local-names.cpp 
b/clang/test/CodeGenCXX/top-level-stmt-local-names.cpp
new file mode 100644
index 0000000000000..c94967c25ffa6
--- /dev/null
+++ b/clang/test/CodeGenCXX/top-level-stmt-local-names.cpp
@@ -0,0 +1,18 @@
+// RUN: %clang_cc1 -std=c++20 -fincremental-extensions -triple 
x86_64-linux-gnu -emit-llvm -o - %s | FileCheck %s
+// Entities in a top-level statement mangle as <local-name>s of that statement,
+// so same-named locals in 
diff erent top-level statements do not collide.
+
+extern "C" int printf(const char *, ...);
+namespace ns { template <typename F> void call(F f) { f(); } }
+
+ns::call([] { printf("ONE\n"); });
+ns::call([] { printf("TWO\n"); });
+// CHECK-DAG: define internal void @"_ZN2ns4callIZL9__stmt__0vE3$_0EEvT_"
+// CHECK-DAG: define internal void @"_ZN2ns4callIZL9__stmt__1vE3$_1EEvT_"
+// CHECK-DAG: define internal void @"_ZZL9__stmt__0vENK3$_0clEv"
+// CHECK-DAG: define internal void @"_ZZL9__stmt__1vENK3$_1clEv"
+
+{ struct S { int f() { return 1; } }; printf("%d\n", S().f()); }
+{ struct S { int f() { return 2; } }; printf("%d\n", S().f()); }
+// CHECK-DAG: define internal noundef i32 @_ZZL9__stmt__2vEN1S1fEv
+// CHECK-DAG: define internal noundef i32 @_ZZL9__stmt__3vEN1S1fEv

diff  --git a/clang/test/Interpreter/lambda-top-level-stmt.cpp 
b/clang/test/Interpreter/lambda-top-level-stmt.cpp
new file mode 100644
index 0000000000000..9161b3b8732e5
--- /dev/null
+++ b/clang/test/Interpreter/lambda-top-level-stmt.cpp
@@ -0,0 +1,20 @@
+// Lambdas in top-level statements used to crash the Itanium mangler, which
+// cast their TopLevelStmtDecl context to NamedDecl. Two lambdas verify that
+// the closure types still mangle to distinct names.
+// REQUIRES: host-supports-jit
+// MSVC compat enables -fdelayed-template-parsing, which hits a pre-existing
+// Sema::PushDeclContext assert on any late-parsed template instantiation in
+// incremental mode, independent of this fix.
+// UNSUPPORTED: system-windows
+// RUN: cat %s | clang-repl | FileCheck %s
+
+extern "C" int printf(const char *, ...);
+
+namespace ns { template <typename F> void call(F f) { f(); } }
+
+ns::call([] { printf("ONE\n"); });
+// CHECK: ONE
+ns::call([] { printf("TWO\n"); });
+// CHECK-NEXT: TWO
+
+%quit


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

Reply via email to