[clang] [clang][AST] Add ASTContext::hasLayout() (PR #216541)

Timm Baeder via cfe-commits cfe-commits at lists.llvm.org
Sun Aug 16 00:05:14 PDT 2026


https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/216541

Whenever we add a new call to e.g. `ASTContext::getASTRecordLayout()`, we inevitably cause a problem because that function has quite a few prerequisites:

```c++
  D = D->getDefinition();
  assert(D && "Cannot get layout of forward declarations!");
  assert(!D->isInvalidDecl() && "Cannot get layout of invalid decl!");
  assert(D->isCompleteDefinition() && "Cannot layout type before complete!");
```

Add a function to check whether a record decl can be pased to `getASTRecordLayout()` and update a few callers.

>From d2ca8f8a2c119d406f2034d44b58ce54fba44619 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sun, 16 Aug 2026 08:57:58 +0200
Subject: [PATCH] [clang][AST] Add ASTContext::hasLayout()

---
 clang/include/clang/AST/ASTContext.h                 |  7 +++++++
 clang/lib/AST/ByteCode/Pointer.cpp                   | 10 ++++++++--
 clang/lib/AST/ExprConstant.cpp                       |  4 ++++
 clang/lib/AST/RecordLayoutBuilder.cpp                |  3 ---
 clang/lib/Sema/SemaChecking.cpp                      |  2 +-
 clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp |  3 +++
 clang/lib/StaticAnalyzer/Core/MemRegion.cpp          |  4 ++--
 7 files changed, 25 insertions(+), 8 deletions(-)

diff --git a/clang/include/clang/AST/ASTContext.h b/clang/include/clang/AST/ASTContext.h
index 56b51566f58f5..5ecfdf0567055 100644
--- a/clang/include/clang/AST/ASTContext.h
+++ b/clang/include/clang/AST/ASTContext.h
@@ -2930,9 +2930,16 @@ class ASTContext : public RefCountedBase<ASTContext> {
   /// [[gnu::ms_struct]].
   bool defaultsToMsStruct() const;
 
+  /// Whether layout (offset and size) information can be queried for \p D.
+  static bool hasLayout(const RecordDecl *D) {
+    D = D->getDefinition();
+    return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+  }
+
   /// Get or compute information about the layout of the specified
   /// record (struct/union/class) \p D, which indicates its size and field
   /// position information.
+  /// \pre hasLayout(D)
   const ASTRecordLayout &getASTRecordLayout(const RecordDecl *D) const;
 
   /// Get or compute information about the layout of the specified
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index 4f36d20b352cb..6df64da62e0e1 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -423,6 +423,8 @@ Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const {
     const Record *R = P.getBase().getRecord();
     assert(R);
 
+    if (!ASTContext::hasLayout(R->getDecl()))
+      return std::nullopt;
     const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(R->getDecl());
     Result += ASTCtx
                   .toCharUnitsFromBits(
@@ -481,8 +483,10 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
   PtrView P = view();
   while (true) {
     if (P.isBaseClass()) {
-      const ASTRecordLayout &Layout =
-          ASTCtx.getASTRecordLayout(getRecordDecl(P.getBase()));
+      const CXXRecordDecl *BaseRD = getRecordDecl(P.getBase());
+      if (!ASTContext::hasLayout(BaseRD))
+        return std::nullopt;
+      const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(BaseRD);
       const CXXRecordDecl *RD = getRecordDecl(P);
       if (P.isVirtualBaseClass())
         Result += Layout.getVBaseClassOffset(RD).getQuantity();
@@ -522,6 +526,8 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
 
     assert(P.getField());
     const FieldDecl *F = P.getField();
+    if (!ASTContext::hasLayout(F->getParent()))
+      return std::nullopt;
     const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(F->getParent());
     Result +=
         ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(F->getFieldIndex()))
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 480d5119a5363..a727b42f52890 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -7613,6 +7613,8 @@ static bool HandleDestructionImpl(EvalInfo &Info, SourceRange CallRange,
   if (RD->isUnion())
     return true;
 
+  if (!ASTContext::hasLayout(RD))
+    return false;
   const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD);
 
   // We don't have a good way to iterate fields in reverse, so collect all the
@@ -8007,6 +8009,8 @@ class APValueToBufferConverter {
 
   bool visitRecord(const APValue &Val, QualType Ty, CharUnits Offset) {
     const RecordDecl *RD = Ty->getAsRecordDecl();
+    if (!ASTContext::hasLayout(RD))
+      return false;
     const ASTRecordLayout &Layout = Info.Ctx.getASTRecordLayout(RD);
 
     // Visit the base classes.
diff --git a/clang/lib/AST/RecordLayoutBuilder.cpp b/clang/lib/AST/RecordLayoutBuilder.cpp
index c27572bc7f50d..e6da6c78238c1 100644
--- a/clang/lib/AST/RecordLayoutBuilder.cpp
+++ b/clang/lib/AST/RecordLayoutBuilder.cpp
@@ -3423,9 +3423,6 @@ bool ASTContext::defaultsToMsStruct() const {
          getTargetInfo().getTriple().isWindowsGNUEnvironment();
 }
 
-/// getASTRecordLayout - Get or compute information about the layout of the
-/// specified record (struct/union/class), which indicates its size and field
-/// position information.
 const ASTRecordLayout &
 ASTContext::getASTRecordLayout(const RecordDecl *D) const {
   if (D->hasExternalLexicalStorage() && !D->getDefinition())
diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp
index 3e6266b8ac542..d44f4b054856a 100644
--- a/clang/lib/Sema/SemaChecking.cpp
+++ b/clang/lib/Sema/SemaChecking.cpp
@@ -15744,7 +15744,7 @@ std::optional<std::pair<
     auto *ME = cast<MemberExpr>(E);
     auto *FD = dyn_cast<FieldDecl>(ME->getMemberDecl());
     if (!FD || FD->getType()->isReferenceType() ||
-        FD->getParent()->isInvalidDecl())
+        !ASTContext::hasLayout(FD->getParent()))
       break;
     std::optional<std::pair<CharUnits, CharUnits>> P;
     if (ME->isArrow())
diff --git a/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
index 1554604b374ca..c8d7b6555f3f4 100644
--- a/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/PaddingChecker.cpp
@@ -76,6 +76,9 @@ class PaddingChecker : public Checker<check::ASTDecl<TranslationUnitDecl>> {
     if (!(RD = RD->getDefinition()))
       return;
 
+    if (RD->isInvalidDecl())
+      return;
+
     // This is the simplest correct case: a class with no fields and one base
     // class. Other cases are more complicated because of how the base classes
     // & fields might interact, so we don't bother dealing with them.
diff --git a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
index 9f27358381738..36a71d510b902 100644
--- a/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
+++ b/clang/lib/StaticAnalyzer/Core/MemRegion.cpp
@@ -1638,7 +1638,7 @@ static RegionOffset calculateOffset(const MemRegion *R) {
       }
 
       const CXXRecordDecl *Child = Ty->getAsCXXRecordDecl();
-      if (!Child) {
+      if (!Child || !ASTContext::hasLayout(Child)) {
         // We cannot compute the offset of the base class.
         SymbolicOffsetBase = R;
       } else {
@@ -1712,7 +1712,7 @@ static RegionOffset calculateOffset(const MemRegion *R) {
       assert(R);
 
       const RecordDecl *RD = FR->getDecl()->getParent();
-      if (RD->isUnion() || !RD->isCompleteDefinition()) {
+      if (RD->isUnion() || !ASTContext::hasLayout(RD)) {
         // We cannot compute offset for incomplete type.
         // For unions, we could treat everything as offset 0, but we'd rather
         // treat each field as a symbolic offset so they aren't stored on top



More information about the cfe-commits mailing list