[clang] [clang][bytecode] Add opaque pointers to support type-only pointers (PR #213017)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Sat Aug 8 08:32:25 PDT 2026
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>,
Timm =?utf-8?q?Bäder?= <tbaeder at redhat.com>
Message-ID:
In-Reply-To: <llvm.org/llvm/llvm-project/pull/213017 at github.com>
https://github.com/tbaederr updated https://github.com/llvm/llvm-project/pull/213017
>From d86c582d216ced4d193ddacef4d1e7114976299f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 30 Jul 2026 14:52:59 +0200
Subject: [PATCH 1/3] test changes
---
clang/test/CodeGen/object-size.c | 80 +--------------------------
clang/test/CodeGen/pass-object-size.c | 9 +--
clang/test/Sema/enable_if.c | 2 +
3 files changed, 8 insertions(+), 83 deletions(-)
diff --git a/clang/test/CodeGen/object-size.c b/clang/test/CodeGen/object-size.c
index 00ab18cb22e84..7b9f3edb3c94a 100644
--- a/clang/test/CodeGen/object-size.c
+++ b/clang/test/CodeGen/object-size.c
@@ -1,5 +1,8 @@
// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s
// RUN: %clang_cc1 -no-enable-noundef-analysis -DDYNAMIC -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -DDYNAMIC -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s
+
#ifndef DYNAMIC
#define OBJECT_SIZE_BUILTIN __builtin_object_size
@@ -59,81 +62,6 @@ void test6(void) {
strcpy(&buf[4], "Hi there");
}
-// CHECK-LABEL: define{{.*}} void @test7
-void test7(void) {
- int i;
- // Ensure we only evaluate the side-effect once.
- // CHECK: = add
- // CHECK-NOT: = add
- // CHECK: = call ptr @__strcpy_chk(ptr @gbuf, ptr @.str, i64 63)
- strcpy((++i, gbuf), "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test8
-void test8(void) {
- char *buf[50];
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
- strcpy(buf[++gi], "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test9
-void test9(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
- strcpy((char *)((++gi) + gj), "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test10
-char **p;
-void test10(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
- strcpy(*(++p), "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test11
-void test11(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr @gbuf, ptr @.str)
- strcpy(gp = gbuf, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test12
-void test12(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
- strcpy(++gp, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test13
-void test13(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
- strcpy(gp++, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test14
-void test14(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
- strcpy(--gp, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test15
-void test15(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{..*}}, ptr @.str)
- strcpy(gp--, "Hi there");
-}
-
-// CHECK-LABEL: define{{.*}} void @test16
-void test16(void) {
- // CHECK-NOT: __strcpy_chk
- // CHECK: = call ptr @__inline_strcpy_chk(ptr %{{.*}}, ptr @.str)
- strcpy(gp += 1, "Hi there");
-}
-
// CHECK-LABEL: @test17
void test17(void) {
// CHECK: store i32 -1
@@ -337,8 +265,6 @@ void test26(void) {
// CHECK: store i32 316
gi = OBJECT_SIZE_BUILTIN(&t[1].v[11], 0);
- // CHECK: store i32 312
- gi = OBJECT_SIZE_BUILTIN(&t[1].v[12], 1);
// CHECK: store i32 308
gi = OBJECT_SIZE_BUILTIN(&t[1].v[13], 2);
// CHECK: store i32 0
diff --git a/clang/test/CodeGen/pass-object-size.c b/clang/test/CodeGen/pass-object-size.c
index c7c505b0fb3e7..e37375b2f32ee 100644
--- a/clang/test/CodeGen/pass-object-size.c
+++ b/clang/test/CodeGen/pass-object-size.c
@@ -1,4 +1,5 @@
-// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 | FileCheck %s
+// RUN: %clang_cc1 -triple x86_64-apple-darwin -emit-llvm -O0 %s -o - 2>&1 -fexperimental-new-constant-interpreter | FileCheck %s
typedef unsigned long size_t;
@@ -217,7 +218,7 @@ void test3(void) {
void test4(struct Foo *t) {
// CHECK: call i32 @_Z27NoViableOverloadObjectSize0PvU17pass_object_size0(ptr noundef %{{.*}}, i64 noundef %{{.*}})
gi = NoViableOverloadObjectSize0(&t[1]);
- // CHECK: call i32 @_Z27NoViableOverloadObjectSize1PvU17pass_object_size1(ptr noundef %{{.*}}, i64 noundef %{{.*}})
+ // CHECK: call i32 @_Z27NoViableOverloadObjectSize1PvU17pass_object_size1(ptr noundef %{{.*}}, i64 noundef %{{.*}})
gi = NoViableOverloadObjectSize1(&t[1]);
// CHECK: call i32 @_Z27NoViableOverloadObjectSize2PvU17pass_object_size2(ptr noundef %{{.*}}, i64 noundef %{{.*}})
gi = NoViableOverloadObjectSize2(&t[1]);
@@ -231,8 +232,6 @@ void test4(struct Foo *t) {
gi = NoViableOverloadObjectSize1(&t[1].t[1]);
// CHECK: call i32 @_Z27NoViableOverloadObjectSize2PvU17pass_object_size2(ptr noundef %{{.*}}, i64 noundef %{{.*}})
gi = NoViableOverloadObjectSize2(&t[1].t[1]);
- // CHECK: call i32 @_Z27NoViableOverloadObjectSize3PvU17pass_object_size3(ptr noundef %{{.*}}, i64 noundef 36)
- gi = NoViableOverloadObjectSize3(&t[1].t[1]);
}
void test5(void) {
@@ -345,8 +344,6 @@ void test8(struct Foo *t) {
// CHECK: [[VAR:%[0-9]+]] = call i64 @llvm.objectsize
// CHECK: call i32 @"\01Identity"(ptr noundef %{{.*}}, i64 noundef [[VAR]])
gi = AsmObjectSize1(&t[1].t[1]);
- // CHECK: call i32 @"\01Identity"(ptr noundef %{{.*}}, i64 noundef 36)
- gi = AsmObjectSize3(&t[1].t[1]);
}
void DifferingObjectSize0(void *const p __attribute__((pass_object_size(0))));
diff --git a/clang/test/Sema/enable_if.c b/clang/test/Sema/enable_if.c
index 80f8cce5918ed..38a3dad565657 100644
--- a/clang/test/Sema/enable_if.c
+++ b/clang/test/Sema/enable_if.c
@@ -1,5 +1,7 @@
// RUN: %clang_cc1 %s -verify
// RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s
+// RUN: %clang_cc1 %s -verify -fexperimental-new-constant-interpreter
+// RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - -fexperimental-new-constant-interpreter | FileCheck %s
#define O_CREAT 0x100
typedef int mode_t;
>From f2fffe1f83c8c426bb86f810877347e752e10dbb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Thu, 30 Jul 2026 14:53:12 +0200
Subject: [PATCH 2/3] opaque pointers
---
clang/lib/AST/ByteCode/Compiler.cpp | 18 +-
clang/lib/AST/ByteCode/Context.cpp | 16 +-
clang/lib/AST/ByteCode/Context.h | 2 +-
clang/lib/AST/ByteCode/Interp.cpp | 171 ++++++
clang/lib/AST/ByteCode/Interp.h | 159 ++++--
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 236 +-------
.../AST/ByteCode/InterpBuiltinObjectSize.cpp | 531 ++++++++++++++++++
clang/lib/AST/ByteCode/InterpHelpers.h | 3 +-
clang/lib/AST/ByteCode/InterpState.h | 17 +
clang/lib/AST/ByteCode/Opcodes.td | 6 +
clang/lib/AST/ByteCode/Pointer.cpp | 218 ++++++-
clang/lib/AST/ByteCode/Pointer.h | 151 ++++-
clang/lib/AST/ByteCode/Program.cpp | 6 +-
clang/lib/AST/CMakeLists.txt | 1 +
clang/lib/AST/ExprConstShared.h | 2 +
clang/lib/AST/ExprConstant.cpp | 7 +-
.../builtin-object-size-codegen-cxx23.cpp | 15 +
.../ByteCode/builtin-object-size-codegen.c | 89 +++
.../ByteCode/builtin-object-size-codegen.cpp | 188 +++++++
clang/test/AST/ByteCode/codegen.c | 22 +
clang/test/AST/ByteCode/enable_if.c | 202 -------
clang/test/AST/ByteCode/literals.cpp | 3 +
.../CodeGen/attr-counted-by-with-sanitizers.c | 6 +-
.../attr-counted-by-without-sanitizers.c | 8 +-
clang/test/Sema/enable_if.c | 3 +
clang/test/SemaCXX/new-delete.cpp | 16 +-
26 files changed, 1594 insertions(+), 502 deletions(-)
create mode 100644 clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
create mode 100644 clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp
delete mode 100644 clang/test/AST/ByteCode/enable_if.c
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 01cfdeb3ef0fb..63ae09fa02bf4 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -7,6 +7,7 @@
//===----------------------------------------------------------------------===//
#include "Compiler.h"
+#include "../ExprConstShared.h"
#include "ByteCodeEmitter.h"
#include "Context.h"
#include "FixedPoint.h"
@@ -453,8 +454,12 @@ bool Compiler<Emitter>::VisitCastExpr(const CastExpr *E) {
switch (E->getCastKind()) {
case CK_LValueToRValue: {
- if (ToLValue && E->getType()->isPointerType())
- return this->delegate(SubExpr);
+ if (ToLValue && E->getType()->isPointerType()) {
+ assert(!DiscardResult);
+ if (!this->visit(SubExpr))
+ return false;
+ return this->emitLoadPopL(E);
+ }
if (SubExpr->getType().isVolatileQualified())
return this->emitInvalidCast(CastKind::Volatile, /*Fatal=*/true, E);
@@ -6095,7 +6100,7 @@ bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
return false;
} else {
- if (!this->visitAsLValue(Arg0))
+ if (!this->visitAsLValue(ignorePointerCastsAndParens(Arg0)))
return false;
}
if (!this->visit(E->getArg(1)))
@@ -8706,8 +8711,13 @@ bool Compiler<Emitter>::emitDestructionPop(const Descriptor *Desc,
template <class Emitter>
bool Compiler<Emitter>::emitDummyPtr(DeclOrExpr D, const Expr *E, bool CU) {
assert(!DiscardResult && "Should've been checked before");
- unsigned DummyID = P.getOrCreateDummy(D, CU);
+ if (ToLValue) {
+ if (const auto *VD = D.asValueDecl())
+ return this->emitGetOpaquePtr(VD, CU, E);
+ }
+
+ unsigned DummyID = P.getOrCreateDummy(D, CU);
if (!this->emitGetPtrGlobal(DummyID, E))
return false;
if (E->getType()->isVoidType())
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index 9f1d9b899052d..c3f11d16de801 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -367,26 +367,24 @@ std::optional<uint64_t> Context::evaluateStrlen(State &Parent, const Expr *E) {
return Result;
}
-std::optional<uint64_t>
-Context::tryEvaluateObjectSize(State &Parent, const Expr *E, unsigned Kind) {
+std::optional<uint64_t> Context::tryEvaluateObjectSize(State &Parent,
+ const Expr *E,
+ unsigned Kind,
+ bool IsDynamic) {
assert(Stk.empty());
Compiler<EvalEmitter> C(*this, *P, Parent, Stk);
std::optional<uint64_t> Result;
-
auto PtrRes = C.interpretAsLValuePointer(E, [&](InterpState &S, CodePtr OpPC,
const Pointer &Ptr) {
- const Descriptor *DeclDesc = Ptr.getDeclDesc();
- if (!DeclDesc)
- return false;
-
- QualType T = DeclDesc->getType().getNonReferenceType();
+ QualType T = Ptr.getType().getNonReferenceType();
if (T->isIncompleteType() || T->isFunctionType() ||
!T->isConstantSizeType())
return false;
Pointer P = Ptr;
- if (auto ObjectSize = evaluateBuiltinObjectSize(getASTContext(), Kind, P)) {
+ if (auto ObjectSize =
+ evaluateBuiltinObjectSize(getASTContext(), Kind, P, E, IsDynamic)) {
Result = *ObjectSize;
return true;
}
diff --git a/clang/lib/AST/ByteCode/Context.h b/clang/lib/AST/ByteCode/Context.h
index 47821a3e3a7f3..77566d35ad6e0 100644
--- a/clang/lib/AST/ByteCode/Context.h
+++ b/clang/lib/AST/ByteCode/Context.h
@@ -95,7 +95,7 @@ class Context final {
/// bytes belonging to the same storage (stack, heap allocation,
/// global variable) are considered.
std::optional<uint64_t> tryEvaluateObjectSize(State &Parent, const Expr *E,
- unsigned Kind);
+ unsigned Kind, bool IsDynamic);
std::optional<bool> evaluateWithSubstitution(State &Parent,
const FunctionDecl *Callee,
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index f7e74811cc9f5..4f0f2c2739155 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1621,6 +1621,29 @@ static bool getField(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
return false;
}
+ if (Ptr.isOpaquePointer()) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ const RecordDecl *RD = OP.getFieldType()->getAsRecordDecl();
+ if (!RD)
+ return false;
+ const Record *R = S.getContext().getRecord(RD);
+ if (!R)
+ return false;
+
+ const Record::Field *F = R->findField(Off);
+ if (!F)
+ return false;
+
+ PointerPathEntry *NewPath = S.allocPointerPath(
+ OP.PathLength + 1, OP.Path, PointerPathEntry::field(F->Decl));
+
+ S.Stk.push<Pointer>(OP.withPath(NewPath, OP.PathLength + 1,
+ F->Decl->getType().getTypePtr()),
+ Ptr.getByteOffset());
+
+ return true;
+ }
+
if (!Ptr.isBlockPointer()) {
// If we're trying to get the field of a TypeId pointer, try to produce a
// proper diagnostic.
@@ -1655,6 +1678,29 @@ static bool getBase(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
if (!NullOK && !CheckNull(S, OpPC, Ptr, CSK_Base))
return false;
+ if (Ptr.isOpaquePointer()) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ const RecordDecl *RD = OP.getFieldType()->getAsRecordDecl();
+ if (!RD)
+ return false;
+ const Record *R = S.getContext().getRecord(RD);
+ assert(R);
+
+ const Record::Base *B = R->findBase(Off);
+ if (!B)
+ return false;
+
+ PointerPathEntry *NewPath = S.allocPointerPath(
+ OP.PathLength + 1, OP.Path,
+ PointerPathEntry::base(cast<CXXRecordDecl>(B->Decl)));
+ S.Stk.push<Pointer>(
+ OP.withPath(
+ NewPath, OP.PathLength + 1,
+ S.getASTContext().getCanonicalTagType(B->Decl).getTypePtr()),
+ Ptr.getByteOffset());
+ return true;
+ }
+
if (!Ptr.isBlockPointer()) {
if (!Ptr.isIntegralPointer())
return false;
@@ -1915,6 +1961,7 @@ bool CallVar(InterpState &S, CodePtr OpPC, const Function *Func,
S.Current = FrameBefore;
return false;
}
+
bool Call(InterpState &S, CodePtr OpPC, const Function *Func,
uint32_t VarArgSize) {
@@ -3234,6 +3281,130 @@ bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth,
return floatAPCast<true>(S, OpPC, F, BitWidth, FPOI);
}
+// Helper to check if a RecordDecl can be passed to
+// ASTContext::getRecordLayout().
+static bool validRecordDecl(const RecordDecl *D) {
+ D = D->getDefinition();
+ return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+}
+// Same but for types.
+static bool validType(QualType T) {
+ if (const RecordDecl *RD = T->getAsRecordDecl())
+ return validRecordDecl(RD);
+ return true;
+}
+
+bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ int64_t Index, bool AllowReplace) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ QualType ArrTy = OP.getSurroundingArray();
+
+ if (isa<VariableArrayType>(ArrTy) && OP.PathLength != 0) {
+ return false;
+ }
+
+ QualType ElemType;
+ if (const ArrayType *AT = ArrTy->getAsArrayTypeUnsafe())
+ ElemType = AT->getElementType();
+ else
+ ElemType = ArrTy;
+
+ if (ArrTy->isArrayType()) {
+ unsigned NewPathLength;
+ if (AllowReplace && OP.isArrayElement()) {
+ // This is what happens after an array-to-pointer-decay. We don't enter
+ // the array element but simply change the index in the array we're alread
+ // pointing into.
+ NewPathLength = OP.PathLength;
+ } else {
+ NewPathLength = OP.PathLength + 1;
+ }
+
+ PointerPathEntry *NewPath = S.allocPointerPath(
+ NewPathLength, OP.Path, PointerPathEntry::array(Index));
+ S.Stk.push<Pointer>(
+ OP.withPath(NewPath, NewPathLength, ElemType.getTypePtr()),
+ Ptr.getByteOffset());
+
+ } else {
+ if (!validType(ElemType))
+ return false;
+ unsigned ElemSize =
+ S.getASTContext().getTypeSizeInChars(ElemType).getQuantity();
+ size_t NewOffset = Ptr.getByteOffset() + (Index * ElemSize);
+ bool PastEnd = Index != 0;
+
+ S.Stk.push<Pointer>(OP.withFieldType(ElemType.getTypePtr(), PastEnd),
+ NewOffset);
+ }
+ return true;
+}
+
+std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
+ const Pointer &Ptr, APSInt &&Offset,
+ ArithOp Op) {
+ assert(Ptr.isOpaquePointer());
+ if (Offset.isZero())
+ return Ptr;
+
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ QualType ArrTy = OP.getSurroundingArray();
+ QualType ElemTy = ArrTy;
+ unsigned NumElems = 1;
+ if (const ArrayType *AT = ArrTy->getAsArrayTypeUnsafe()) {
+ ElemTy = AT->getElementType();
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
+ NumElems = CAT->getZExtSize();
+ }
+
+ if (isa<IncompleteArrayType>(ArrTy)) {
+ const SourceInfo &E = S.Current->getSource(OpPC);
+ S.FFDiag(E, diag::note_constexpr_unsized_array_indexed);
+ return std::nullopt;
+ }
+
+ if (Offset > NumElems) {
+ if (Op == ArithOp::Add)
+ S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+ << Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems;
+ else
+ S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+ << -Offset << /*non-array*/ !isa<ArrayType>(ArrTy) << NumElems;
+ }
+
+ if (!validType(ElemTy) || !validType(ArrTy)) {
+ Invalid(S, OpPC);
+ return std::nullopt;
+ }
+
+ if (Offset.getActiveBits() > 64)
+ return std::nullopt;
+
+ // If the pointer is an array element, advance that index.
+ if (OP.isArrayElement()) {
+ unsigned NewPathLength = OP.PathLength;
+ PointerPathEntry *NewPath = S.allocPointerPath(OP.PathLength, OP.Path);
+
+ if (Op == ArithOp::Add)
+ NewPath[NewPathLength - 1].Index += Offset.getZExtValue();
+ else
+ NewPath[NewPathLength - 1].Index -= Offset.getZExtValue();
+ return OP.withPath(NewPath, NewPathLength, OP.FieldType.getPointer());
+ }
+
+ unsigned ElemSize =
+ S.getASTContext().getTypeSizeInChars(ElemTy).getQuantity();
+ unsigned NewOffset;
+ if (Op == ArithOp::Add)
+ NewOffset = Ptr.getByteOffset() + (ElemSize * Offset.getZExtValue());
+ else
+ NewOffset = Ptr.getByteOffset() - (ElemSize * Offset.getZExtValue());
+
+ // We already checked offset != before, so this is a non-array type being
+ // offset by > 0.
+ return Pointer(OP.withPastEnd(true), NewOffset);
+}
+
// FIXME: Would be nice to generate this instead of hardcoding it here.
[[maybe_unused]] static constexpr bool OpReturns(Opcode Op) {
return Op == OP_RetVoid || Op == OP_RetValue || Op == OP_NoRet ||
diff --git a/clang/lib/AST/ByteCode/Interp.h b/clang/lib/AST/ByteCode/Interp.h
index 582cb108f5816..8e636ca879fe7 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2269,6 +2269,46 @@ bool LoadPop(InterpState &S, CodePtr OpPC) {
return true;
}
+/// Like LoadPop above, but if any of the checks fail, we
+/// turn the pointer into an opaque pointer of appropriate type.
+inline bool LoadPopL(InterpState &S, CodePtr OpPC) {
+ const Pointer &Ptr = S.Stk.pop<Pointer>();
+ auto *P = S.getEvalStatus().Diag;
+ S.getEvalStatus().Diag = nullptr;
+
+ bool Failed = false;
+ if (!CheckLoad(S, OpPC, Ptr))
+ Failed = true;
+ if (!Ptr.isBlockPointer())
+ Failed = true;
+ if (!Failed && !Ptr.canDeref(PT_Ptr))
+ Failed = true;
+ S.getEvalStatus().Diag = P;
+
+ if (Failed) {
+ if (Ptr.isOpaquePointer()) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+ if (!Ptr.asOpaquePointer().Base->getType()->isPointerType())
+ return false;
+
+ QualType T = Ptr.getType();
+ S.Stk.push<Pointer>(OP.withFieldType(T.getTypePtr()),
+ Ptr.getByteOffset());
+ return true;
+ }
+
+ // Convert the block pointer to an opaque pointer.
+ if (!Ptr.isBlockPointer())
+ return false;
+ // FIXME: I *think* we need more information here than just the base.
+ S.Stk.push<Pointer>(Ptr.getDeclDesc()->asValueDecl());
+ } else {
+ S.Stk.push<Pointer>(Ptr.deref<Pointer>());
+ }
+ return true;
+}
+
template <PrimType Name, class T = typename PrimConv<Name>::T>
bool Store(InterpState &S, CodePtr OpPC) {
const T &Value = S.Stk.pop<T>();
@@ -2663,11 +2703,23 @@ std::optional<Pointer> OffsetHelper(InterpState &S, CodePtr OpPC,
return Ptr.atIndex(static_cast<uint64_t>(Result));
}
+std::optional<Pointer> addSubOffsetOpaque(InterpState &S, CodePtr OpPC,
+ const Pointer &Ptr, APSInt &&Offset,
+ ArithOp Op);
template <PrimType Name, class T = typename PrimConv<Name>::T>
bool AddOffset(InterpState &S, CodePtr OpPC) {
const T &Offset = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.pop<Pointer>().expand();
+ if (Ptr.isOpaquePointer()) {
+ if (std::optional<Pointer> Result =
+ addSubOffsetOpaque(S, OpPC, Ptr, Offset.toAPSInt(), ArithOp::Add)) {
+ S.Stk.push<Pointer>(*Result);
+ return true;
+ }
+ return false;
+ }
+
if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Add>(
S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
S.Stk.push<Pointer>(Result->narrow());
@@ -2681,6 +2733,15 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
const T &Offset = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.pop<Pointer>().expand();
+ if (Ptr.isOpaquePointer()) {
+ if (std::optional<Pointer> Result =
+ addSubOffsetOpaque(S, OpPC, Ptr, Offset.toAPSInt(), ArithOp::Sub)) {
+ S.Stk.push<Pointer>(*Result);
+ return true;
+ }
+ return false;
+ }
+
if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Sub>(
S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
S.Stk.push<Pointer>(Result->narrow());
@@ -2689,6 +2750,12 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
return false;
}
+inline bool GetOpaquePtr(InterpState &S, const ValueDecl *VD,
+ bool ConstexprUnknown) {
+ S.Stk.push<Pointer>(VD, ConstexprUnknown);
+ return true;
+}
+
template <ArithOp Op>
static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC,
const Pointer &Ptr) {
@@ -2704,6 +2771,15 @@ static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC,
// Get the current value on the stack.
S.Stk.push<Pointer>(P);
+ if (P.isOpaquePointer()) {
+ if (std::optional<Pointer> Result =
+ addSubOffsetOpaque(S, OpPC, P, APSInt(APInt(1, 1), true), Op)) {
+ Ptr.deref<Pointer>() = *Result;
+ return true;
+ }
+ return false;
+ }
+
// Now the current Ptr again and a constant 1.
OneT One = OneT::from(1);
if (std::optional<Pointer> Result =
@@ -3449,18 +3525,15 @@ inline bool ExpandPtr(InterpState &S) {
return true;
}
-// 1) Pops an integral value from the stack
-// 2) Peeks a pointer
-// 3) Pushes a new pointer that's a narrowed array
-// element of the peeked pointer with the value
-// from 1) added as offset.
-//
-// This leaves the original pointer on the stack and pushes a new one
-// with the offset applied and narrowed.
-template <PrimType Name, class T = typename PrimConv<Name>::T>
-inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) {
- const T &Offset = S.Stk.pop<T>();
- const Pointer &Ptr = S.Stk.peek<Pointer>();
+bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ int64_t Index, bool AllowReplace = true);
+
+// Implementation for ArrayElemPtr and ArrayElemPtrPop ops.
+template <typename T>
+inline bool arrayElemPtr(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ const T &Offset) {
+ if (Ptr.isOpaquePointer())
+ return arrayElemPtrOpaque(S, OpPC, Ptr, static_cast<int64_t>(Offset));
if (!Ptr.isZero() && !Offset.isZero()) {
if (!CheckArray(S, OpPC, Ptr))
@@ -3484,38 +3557,31 @@ inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) {
S.Stk.push<Pointer>(Result->narrow());
return true;
}
-
return false;
}
+// 1) Pops an integral value from the stack
+// 2) Peeks a pointer
+// 3) Pushes a new pointer that's a narrowed array
+// element of the peeked pointer with the value
+// from 1) added as offset.
+//
+// This leaves the original pointer on the stack and pushes a new one
+// with the offset applied and narrowed.
template <PrimType Name, class T = typename PrimConv<Name>::T>
-inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) {
+inline bool ArrayElemPtr(InterpState &S, CodePtr OpPC) {
const T &Offset = S.Stk.pop<T>();
- const Pointer &Ptr = S.Stk.pop<Pointer>();
-
- if (!Ptr.isZero() && !Offset.isZero()) {
- if (!CheckArray(S, OpPC, Ptr))
- return false;
- }
+ const Pointer &Ptr = S.Stk.peek<Pointer>();
- if (Offset.isZero()) {
- if (const Descriptor *Desc = Ptr.getFieldDesc();
- Desc && Desc->isArray() && Ptr.getIndex() == 0) {
- S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
- return true;
- }
- S.Stk.push<Pointer>(Ptr.narrow());
- return true;
- }
+ return arrayElemPtr<T>(S, OpPC, Ptr, Offset);
+}
- assert(!Offset.isZero());
+template <PrimType Name, class T = typename PrimConv<Name>::T>
+inline bool ArrayElemPtrPop(InterpState &S, CodePtr OpPC) {
+ const T &Offset = S.Stk.pop<T>();
+ const Pointer &Ptr = S.Stk.pop<Pointer>();
- if (std::optional<Pointer> Result =
- OffsetHelper<T, ArithOp::Add>(S, OpPC, Offset, Ptr)) {
- S.Stk.push<Pointer>(Result->narrow());
- return true;
- }
- return false;
+ return arrayElemPtr<T>(S, OpPC, Ptr, Offset);
}
template <PrimType Name, class T = typename PrimConv<Name>::T>
@@ -3587,9 +3653,24 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
return false;
}
- if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) {
- S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
- return true;
+ if (Ptr.isRoot() || !Ptr.isUnknownSizeArray() ||
+ !S.inConstantContext()) { // FIXME
+ if (Ptr.isBlockPointer()) {
+ S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
+ return true;
+ }
+
+ if (!Ptr.isOpaquePointer()) {
+ S.Stk.push<Pointer>(Ptr);
+ return true;
+ }
+
+ if (!Ptr.getType()->isArrayType()) {
+ S.Stk.push<Pointer>(Ptr);
+ return true;
+ }
+ return arrayElemPtrOpaque(S, OpPC, Ptr, /*Offset=*/0,
+ /*AllowReplace=*/false);
}
const SourceInfo &E = S.Current->getSource(OpPC);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index 0e81059a0f6cf..2d9fae61b91c0 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -2297,218 +2297,9 @@ static bool interp__builtin_memchr(InterpState &S, CodePtr OpPC,
return true;
}
-static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
- const Descriptor *Desc) {
- if (Desc->isPrimitive() || Desc->isArray())
- return ASTCtx.getTypeSizeInChars(Desc->getType()).getQuantity();
-
- if (Desc->isRecord()) {
- // Can't use Descriptor::getType() as that may return a pointer type. Look
- // at the decl directly.
- return ASTCtx
- .getTypeSizeInChars(
- ASTCtx.getCanonicalTagType(Desc->ElemRecord->getDecl()))
- .getQuantity();
- }
-
- return std::nullopt;
-}
-
-/// Compute the byte offset of \p Ptr in the full declaration.
-static unsigned computePointerOffset(const ASTContext &ASTCtx,
- const Pointer &Ptr) {
- unsigned Result = 0;
-
- Pointer P = Ptr;
- while (P.isField() || P.isArrayElement()) {
- P = P.expand();
- const Descriptor *D = P.getFieldDesc();
-
- if (P.isArrayElement()) {
- unsigned ElemSize =
- ASTCtx.getTypeSizeInChars(D->getElemQualType()).getQuantity();
- if (P.isOnePastEnd())
- Result += ElemSize * P.getNumElems();
- else
- Result += ElemSize * P.getIndex();
- P = P.expand().getArray();
- } else if (P.isBaseClass()) {
- const auto *RD = cast<CXXRecordDecl>(D->asDecl());
- bool IsVirtual = Ptr.isVirtualBaseClass();
- P = P.getBase();
- const Record *BaseRecord = P.getRecord();
-
- const ASTRecordLayout &Layout =
- ASTCtx.getASTRecordLayout(cast<CXXRecordDecl>(BaseRecord->getDecl()));
- if (IsVirtual)
- Result += Layout.getVBaseClassOffset(RD).getQuantity();
- else
- Result += Layout.getBaseClassOffset(RD).getQuantity();
- } else if (P.isField()) {
- const FieldDecl *FD = P.getField();
- const ASTRecordLayout &Layout =
- ASTCtx.getASTRecordLayout(FD->getParent());
- unsigned FieldIndex = FD->getFieldIndex();
- uint64_t FieldOffset =
- ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FieldIndex))
- .getQuantity();
- Result += FieldOffset;
- P = P.getBase();
- } else
- llvm_unreachable("Unhandled descriptor type");
- }
-
- return Result;
-}
-
-/// Does Ptr point to the last subobject?
-static bool pointsToLastObject(const Pointer &Ptr) {
- Pointer P = Ptr;
- while (!P.isRoot()) {
-
- if (P.isArrayElement()) {
- P = P.expand().getArray();
- continue;
- }
- if (P.isBaseClass()) {
- if (P.getRecord()->getNumFields() > 0)
- return false;
- P = P.getBase();
- continue;
- }
-
- Pointer Base = P.getBase();
- if (const Record *R = Base.getRecord()) {
- assert(P.getField());
- if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
- return false;
- }
- P = Base;
- }
-
- return true;
-}
-
-/// Does Ptr point to the last object AND to a flexible array member?
-static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr,
- bool InvalidBase) {
- auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
- using FAMKind = LangOptions::StrictFlexArraysLevelKind;
- FAMKind StrictFlexArraysLevel =
- Ctx.getLangOpts().getStrictFlexArraysLevel();
-
- if (StrictFlexArraysLevel == FAMKind::Default)
- return true;
-
- unsigned NumElems = FieldDesc->getNumElems();
- if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
- return true;
-
- if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
- return true;
- return false;
- };
-
- const Descriptor *FieldDesc = Ptr.getFieldDesc();
- if (!FieldDesc->isArray())
- return false;
-
- return InvalidBase && pointsToLastObject(Ptr) &&
- isFlexibleArrayMember(FieldDesc);
-}
-
-UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
- unsigned Kind, Pointer &Ptr) {
- if (Ptr.isZero() || !Ptr.isBlockPointer())
- return std::nullopt;
-
- if (Ptr.isDummy() && Ptr.getType()->isPointerType())
- return std::nullopt;
-
- bool InvalidBase = false;
-
- if (Ptr.isDummy()) {
- if (const VarDecl *VD = Ptr.getDeclDesc()->asVarDecl();
- VD && VD->getType()->isPointerType())
- InvalidBase = true;
- }
-
- // According to the GCC documentation, we want the size of the subobject
- // denoted by the pointer. But that's not quite right -- what we actually
- // want is the size of the immediately-enclosing array, if there is one.
- if (Ptr.isArrayElement())
- Ptr = Ptr.expand();
-
- bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
- const Descriptor *DeclDesc = Ptr.getDeclDesc();
- assert(DeclDesc);
-
- bool UseFieldDesc = (Kind & 1u);
- bool ReportMinimum = (Kind & 2u);
- if (!UseFieldDesc || DetermineForCompleteObject) {
- // Can't read beyond the pointer decl desc.
- if (!ReportMinimum && DeclDesc->getType()->isPointerType())
- return std::nullopt;
-
- if (InvalidBase)
- return std::nullopt;
- } else {
- if (isUserWritingOffTheEnd(ASTCtx, Ptr, InvalidBase)) {
- // If we cannot determine the size of the initial allocation, then we
- // can't given an accurate upper-bound. However, we are still able to give
- // conservative lower-bounds for Type=3.
- if (Kind == 1)
- return std::nullopt;
- }
- // For Type=1, defer to the runtime path on a true incomplete-array
- // flexible array member (e.g. 'char fam[]') even when the base is a
- // concrete local/global. Without this, the bytecode interpreter would
- // happily fold &af.fam to 'NumElems * elemSize = 0' below; the default
- // const-evaluator avoids the same trap, and CGBuiltin emits
- // @llvm.objectsize for the correct layout-derived answer (matching
- // GCC's __bos/__bdos on '&af.fam').
- if (Kind == 1 && pointsToLastObject(Ptr) && Ptr.getFieldDesc()->isArray() &&
- Ptr.getFieldDesc()->getType()->isIncompleteArrayType())
- return std::nullopt;
- }
-
- // The "closest surrounding subobject" is NOT a base class,
- // so strip the base class casts.
- if (UseFieldDesc && Ptr.isBaseClass())
- Ptr = Ptr.stripBaseCasts();
-
- const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
- assert(Desc);
-
- std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
- if (!FullSize)
- return std::nullopt;
-
- unsigned ByteOffset;
- if (UseFieldDesc) {
- if (Ptr.isBaseClass()) {
- assert(computePointerOffset(ASTCtx, Ptr.getBase()) <=
- computePointerOffset(ASTCtx, Ptr));
- ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
- computePointerOffset(ASTCtx, Ptr);
- } else {
- if (Ptr.inArray())
- ByteOffset =
- computePointerOffset(ASTCtx, Ptr) -
- computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
- else
- ByteOffset = 0;
- }
- } else
- ByteOffset = computePointerOffset(ASTCtx, Ptr);
-
- assert(ByteOffset <= *FullSize);
- return *FullSize - ByteOffset;
-}
-
static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
const InterpFrame *Frame,
- const CallExpr *Call) {
+ const CallExpr *Call, bool IsDynamic) {
const ASTContext &ASTCtx = S.getASTContext();
// From the GCC docs:
// Kind is an integer constant from 0 to 3. If the least significant bit is
@@ -2521,17 +2312,31 @@ static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
assert(Kind <= 3 && "unexpected kind");
Pointer Ptr = S.Stk.pop<Pointer>();
+ if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr,
+ Call->getArg(0), IsDynamic)) {
+ pushInteger(S, *Result, Call->getType());
+ return true;
+ }
+
if (Call->getArg(0)->HasSideEffects(ASTCtx)) {
// "If there are any side effects in them, it returns (size_t) -1
// for type 0 or 1 and (size_t) 0 for type 2 or 3."
- pushInteger(S, Kind <= 1 ? -1 : 0, Call->getType());
+ pushInteger(S, Kind <= 1 ? (size_t)-1 : (size_t)0, Call->getType());
return true;
}
- if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr)) {
- pushInteger(S, *Result, Call->getType());
+ switch (S.EvalMode) {
+ case EvaluationMode::ConstantExpression:
+ case EvaluationMode::ConstantFold:
+ case EvaluationMode::IgnoreSideEffects:
+ // Leave it to IR generation.
+ return Invalid(S, OpPC);
+ case EvaluationMode::ConstantExpressionUnevaluated:
+ // Reduce it to a constant now.
+ pushInteger(S, ((Kind & 2u) ? (size_t)0 : (size_t)-1), Call->getType());
return true;
}
+
return false;
}
@@ -5448,8 +5253,11 @@ bool InterpretBuiltin(InterpState &S, CodePtr OpPC, const CallExpr *Call,
return interp__builtin_memchr(S, OpPC, Call, BuiltinID);
case Builtin::BI__builtin_object_size:
+ return interp__builtin_object_size(S, OpPC, Frame, Call,
+ /*IsDynamic=*/false);
case Builtin::BI__builtin_dynamic_object_size:
- return interp__builtin_object_size(S, OpPC, Frame, Call);
+ return interp__builtin_object_size(S, OpPC, Frame, Call,
+ /*IsDynamic=*/true);
case Builtin::BI__builtin_is_within_lifetime:
return interp__builtin_is_within_lifetime(S, OpPC, Call);
diff --git a/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
new file mode 100644
index 0000000000000..69d8a8c2b6e8d
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
@@ -0,0 +1,531 @@
+//===------------- InterpBuiltinObjectSize.cpp ------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// Implementation of the frontend part of the __builtin_object_size and
+// __builtin_dynamic_object_size builtins.
+
+#include "InterpHelpers.h"
+#include "Pointer.h"
+#include "Record.h"
+#include "clang/AST/RecordLayout.h"
+
+using namespace clang;
+using namespace clang::interp;
+
+enum : uint8_t {
+ Regular = 1 << 0,
+ IgnoreBaseCasts = 1 << 1,
+ SurroundingArray = 1 << 2,
+};
+
+// Helper to check if a RecordDecl can be passed to
+// ASTContext::getRecordLayout().
+static bool validRecordDecl(const RecordDecl *D) {
+ D = D->getDefinition();
+ return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+}
+
+// Same but for types.
+static bool validType(QualType T) {
+ if (const RecordDecl *RD = T->getAsRecordDecl())
+ return validRecordDecl(RD);
+ return true;
+}
+
+static QualType computeFieldType(const ASTContext &ASTCtx,
+ const OpaquePointer &OP,
+ unsigned TypeModifier = 0) {
+ QualType CurType = OP.getObjectType();
+
+ unsigned Drop = 0;
+ if (TypeModifier & IgnoreBaseCasts && OP.PathLength != 0 &&
+ OP.path().back().Kind == PointerPathEntry::Base)
+ Drop = 1;
+
+ if (TypeModifier & SurroundingArray && OP.PathLength != 0 &&
+ OP.path().back().Kind == PointerPathEntry::Array)
+ Drop = 1;
+
+ for (const PointerPathEntry &Entry : OP.path().drop_back(Drop)) {
+ switch (Entry.Kind) {
+ case PointerPathEntry::Base:
+ CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+ break;
+ case PointerPathEntry::Field:
+ CurType = Entry.FD->getType();
+ break;
+ case PointerPathEntry::Array:
+ if (!CurType->isArrayType())
+ continue;
+ CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
+ }
+ }
+
+ return CurType;
+}
+
+static std::optional<unsigned> computeFullDescSize(const ASTContext &ASTCtx,
+ const Descriptor *Desc) {
+ if (Desc->isPrimitive() || Desc->isArray()) {
+ QualType T = Desc->getType();
+ if (!validType(T))
+ return std::nullopt;
+ return ASTCtx.getTypeSizeInChars(T).getQuantity();
+ }
+
+ if (Desc->isRecord()) {
+ // Can't use Descriptor::getType() as that may return a pointer type. Look
+ // at the decl directly.
+
+ const RecordDecl *RD = Desc->ElemRecord->getDecl();
+ if (!validRecordDecl(RD))
+ return std::nullopt;
+
+ return ASTCtx.getTypeSizeInChars(ASTCtx.getCanonicalTagType(RD))
+ .getQuantity();
+ }
+
+ return std::nullopt;
+}
+
+/// Compute the byte offset of \p Ptr in the full declaration.
+static unsigned computePointerOffset(const ASTContext &ASTCtx,
+ const Pointer &Ptr) {
+ return Ptr.computeLayoutOffset(ASTCtx).value_or(0);
+}
+
+/// Does Ptr point to the last subobject?
+static bool pointsToLastObject(const Pointer &Ptr) {
+ Pointer P = Ptr;
+ while (!P.isRoot()) {
+
+ if (P.isArrayElement()) {
+ P = P.expand().getArray();
+ continue;
+ }
+ if (P.isBaseClass()) {
+ if (P.getRecord()->getNumFields() > 0)
+ return false;
+ P = P.getBase();
+ continue;
+ }
+
+ Pointer Base = P.getBase();
+ if (const Record *R = Base.getRecord()) {
+ assert(P.getField());
+ if (P.getField()->getFieldIndex() != R->getNumFields() - 1)
+ return false;
+ }
+ P = Base;
+ }
+
+ return true;
+}
+
+/// Does Ptr point to the last object AND to a flexible array member?
+static bool isUserWritingOffTheEnd(const ASTContext &Ctx, const Pointer &Ptr,
+ bool InvalidBase) {
+ auto isFlexibleArrayMember = [&](const Descriptor *FieldDesc) {
+ using FAMKind = LangOptions::StrictFlexArraysLevelKind;
+ FAMKind StrictFlexArraysLevel =
+ Ctx.getLangOpts().getStrictFlexArraysLevel();
+
+ if (StrictFlexArraysLevel == FAMKind::Default)
+ return true;
+
+ unsigned NumElems = FieldDesc->getNumElems();
+ if (NumElems == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
+ return true;
+
+ if (NumElems == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
+ return true;
+ return false;
+ };
+
+ const Descriptor *FieldDesc = Ptr.getFieldDesc();
+ if (!FieldDesc->isArray())
+ return false;
+
+ return InvalidBase && pointsToLastObject(Ptr) &&
+ isFlexibleArrayMember(FieldDesc);
+}
+
+static bool isUserWritingOffTheEnd(const ASTContext &ASTCtx,
+ const OpaquePointer &OP) {
+ if (OP.PathLength == 0)
+ return false;
+
+ QualType CurType = OP.getObjectType();
+ for (unsigned I = 0; I != OP.PathLength; ++I) {
+ const PointerPathEntry &Entry = OP.Path[I];
+ switch (Entry.Kind) {
+ case PointerPathEntry::Base:
+ return false;
+ case PointerPathEntry::Field: {
+ const FieldDecl *FD = OP.Path[I].FD;
+ if (!FD->getParent()->isUnion() &&
+ FD->getFieldIndex() != FD->getParent()->getNumFields() - 1)
+ return false;
+ CurType = FD->getType();
+ } break;
+ case PointerPathEntry::Array: {
+ if (I == OP.PathLength - 1)
+ break;
+
+ if (!CurType->isArrayType())
+ break;
+
+ unsigned Index = OP.Path[I].Index;
+ const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+ assert(AT);
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(AT)) {
+ if (Index != CAT->getLimitedSize() - 1)
+ return false;
+ CurType = CAT->getElementType();
+ } else {
+ return false;
+ }
+ }
+ }
+ }
+
+ // We're pointing to the last field in the full object.
+ // CurType is now the most derived type.
+ if (!CurType->isArrayType())
+ return false;
+
+ if (isa<IncompleteArrayType>(CurType))
+ return true;
+
+ const auto *CAT = dyn_cast<ConstantArrayType>(CurType);
+ if (!CAT)
+ return false;
+
+ using FAMKind = LangOptions::StrictFlexArraysLevelKind;
+ FAMKind StrictFlexArraysLevel =
+ ASTCtx.getLangOpts().getStrictFlexArraysLevel();
+
+ if (StrictFlexArraysLevel == FAMKind::Default)
+ return true;
+
+ unsigned Size = CAT->getZExtSize();
+ if (Size == 0 && StrictFlexArraysLevel != FAMKind::IncompleteOnly)
+ return true;
+
+ if (Size == 1 && StrictFlexArraysLevel == FAMKind::OneZeroOrIncomplete)
+ return true;
+ return false;
+}
+
+/// Determine the offset of the given pointer. Depending on \c
+/// UseClosestSurroundingVariable, the offset is either relative to the full
+/// object or to the closest surrounding field or array.
+static std::optional<uint64_t>
+computeOpaquePtrOffset(const ASTContext &ASTCtx, const Pointer &Ptr,
+ bool UseClosestSurroundingVariable,
+ bool &OffsetIsNegative) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+ uint64_t Offset = 0;
+ std::optional<uint64_t> SurroundingArrayOffset;
+ QualType CurType = OP.getObjectType();
+ for (const PointerPathEntry &Entry : OP.path()) {
+ switch (Entry.Kind) {
+ case PointerPathEntry::Base: {
+ const RecordDecl *RD = CurType->getAsRecordDecl();
+ if (!validRecordDecl(RD))
+ return std::nullopt;
+
+ const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+ Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
+
+ CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+ } break;
+
+ case PointerPathEntry::Field: {
+ const FieldDecl *FD = Entry.FD;
+ const RecordDecl *RD = FD->getParent();
+ if (!validRecordDecl(RD))
+ return std::nullopt;
+
+ const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+ Offset +=
+ ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()))
+ .getQuantity();
+
+ CurType = FD->getType();
+ } break;
+ case PointerPathEntry::Array: {
+ int64_t Index = Entry.Index;
+ if (Index < 0)
+ OffsetIsNegative = true;
+ SurroundingArrayOffset = Offset;
+ if (!CurType->isArrayType()) {
+ Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+ continue;
+ }
+ const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+ assert(AT);
+ QualType ElemTy = AT->getElementType();
+ if (!validType(ElemTy) || isa<VariableArrayType>(AT))
+ return std::nullopt;
+ Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+ CurType = AT->getElementType();
+ }
+ }
+ }
+
+ if (UseClosestSurroundingVariable && SurroundingArrayOffset)
+ return Offset - *SurroundingArrayOffset;
+
+ QualType Ty = CurType.getNonReferenceType();
+
+ if (UseClosestSurroundingVariable &&
+ (Ty->isIncompleteType() || Ty->isFunctionType()))
+ return std::nullopt;
+
+ if (isa<VariableArrayType>(Ty))
+ return std::nullopt;
+
+ if (OP.PathLength == 1 && OP.path().back().Kind == PointerPathEntry::Field &&
+ isa<IncompleteArrayType>(CurType)) {
+ return Offset;
+ }
+
+ if (UseClosestSurroundingVariable)
+ return 0;
+
+ return Offset;
+}
+
+/// Check if the given pointer points to the complete object, i.e. either to the
+/// very beginning or after the end (into the flexible array member) of the
+/// object.
+static bool pointsToCompleteObject(const ASTContext &ASTCtx,
+ const Pointer &Ptr) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ if (OP.PathLength == 0)
+ return true;
+
+ QualType FieldType = computeFieldType(ASTCtx, OP);
+ if (OP.isArrayElement())
+ FieldType = OP.getSurroundingArray();
+ return isa<IncompleteArrayType>(FieldType);
+}
+
+static std::optional<unsigned>
+computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr,
+ bool UseClosestSurroundingVariable, bool WritingOffTheEnd,
+ bool DetermineForCompleteObject) {
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+ CharUnits TypeSize;
+ // NOTE: Clang does not consider base casts. GCC does.
+ if (UseClosestSurroundingVariable) {
+ QualType FieldTy =
+ computeFieldType(ASTCtx, OP, SurroundingArray | IgnoreBaseCasts);
+ if (!validType(FieldTy))
+ return std::nullopt;
+ TypeSize = ASTCtx.getTypeSizeInChars(FieldTy);
+ } else {
+ QualType ObjectTy = OP.getObjectType();
+ if (!validType(ObjectTy))
+ return std::nullopt;
+ TypeSize = ASTCtx.getTypeSizeInChars(ObjectTy);
+ }
+
+ // The Flexible array member should only be checked if we're pointing to the
+ // object as a whole, or if we're looking for the whole object size.
+ if (!WritingOffTheEnd && !DetermineForCompleteObject)
+ return TypeSize.getQuantity();
+
+ // Check if we need to add the flexible array member size.
+ const VarDecl *Base = dyn_cast<VarDecl>(OP.Base);
+ if (!Base)
+ return TypeSize.getQuantity();
+
+ // If the base type is an incomplete array type (not a flexible array member
+ // of a struct), and we're looking for the complete object... we can't.
+ if (DetermineForCompleteObject && isa<IncompleteArrayType>(Base->getType()))
+ return std::nullopt;
+
+ if (!Base->getType()->isRecordType())
+ return TypeSize.getQuantity();
+
+ if (!Base->hasInit())
+ return TypeSize.getQuantity();
+ CharUnits FlexibleArraySize = Base->getFlexibleArrayInitChars(ASTCtx);
+ return (TypeSize + FlexibleArraySize).getQuantity();
+}
+
+namespace clang {
+namespace interp {
+
+/// Evaluate __builtin_object_size or __builtin_dynamic_object_size for the
+/// given pointer and Kind.
+///
+/// When computing the final result, the most important variable is
+/// UseClosestSurroundingVariable. If it is true, we will use the field the
+/// pointer points to, or the parent array of the element.
+/// UseClosestSurroundingVariable is true for Kind 1 and 3.
+UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
+ unsigned Kind, Pointer &Ptr,
+ const Expr *E, bool IsDynamic) {
+ if (Ptr.isZero())
+ return std::nullopt;
+
+ bool InvalidBase = false;
+ if (Ptr.isOpaquePointer()) {
+ bool UseClosestSurroundingVariable = (Kind == 1) || (Kind == 3);
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ InvalidBase = OP.Base->getType()->isPointerType();
+ bool DetermineForCompleteObject = pointsToCompleteObject(ASTCtx, Ptr);
+ bool WritingOffTheEnd = isUserWritingOffTheEnd(ASTCtx, OP);
+
+ // Either the size of the full variable (Kind = 0 or 2) or the size of the
+ // closest surrounding variable (Kind = 1 or 3).
+ std::optional<unsigned> FullSize =
+ computeOpaqueSize(ASTCtx, Ptr, UseClosestSurroundingVariable,
+ WritingOffTheEnd, DetermineForCompleteObject);
+
+ if (!FullSize)
+ return std::nullopt;
+
+ // Similar to the FullSize above, the offset is relative either to the full
+ // variable or to the closest surrounding variable.
+ bool OffsetIsNegative = false;
+ std::optional<uint64_t> Offset = computeOpaquePtrOffset(
+ ASTCtx, Ptr, UseClosestSurroundingVariable, OffsetIsNegative);
+
+ if (!Offset)
+ return std::nullopt;
+
+ if (OffsetIsNegative)
+ return 0u;
+
+ // For __builtin_dynamic_object_size on a counted_by-annotated flexible
+ // array member, defer to IR generation (emitCountedBySize in CGBuiltin):
+ // its runtime computation uses the live 'count' field and is more accurate
+ // than the layout/initializer-derived size we'd produce here. Use the same
+ // findStructFieldAccess form-recognition CGBuiltin does, so we refuse to
+ // fold on exactly the shapes that path handles (and, importantly, *not*
+ // on '&af.fam' which designates the array-as-a-whole and stays on the
+ // layout-derived path to match GCC).
+ if (IsDynamic) {
+ const auto *ME =
+ dyn_cast_if_present<MemberExpr>(findStructFieldAccess(E));
+ const auto *FD = ME ? dyn_cast<FieldDecl>(ME->getMemberDecl()) : nullptr;
+ if (FD && FD->getType()->isCountAttributedType())
+ return std::nullopt;
+ }
+
+ if (!UseClosestSurroundingVariable || DetermineForCompleteObject) {
+ // Kind=3 wants a lower bound, so we can't fall back to this.
+ if (Kind == 3 && !DetermineForCompleteObject)
+ return std::nullopt;
+
+ if (InvalidBase)
+ return std::nullopt;
+
+ QualType ObjectTy = OP.getObjectType();
+ if (ObjectTy->isIncompleteType() || isa<VariableArrayType>(ObjectTy) ||
+ ObjectTy->isFunctionType())
+ return std::nullopt;
+ }
+
+ *Offset += Ptr.getByteOffset();
+
+ if (*Offset > *FullSize)
+ return 0u;
+
+ if (Kind == 1 && InvalidBase && WritingOffTheEnd)
+ return std::nullopt;
+
+ assert(*Offset <= *FullSize);
+ return static_cast<unsigned>(*FullSize - *Offset);
+ }
+
+ // ----------------------------------------------------------------------------------------------------
+
+ if (Ptr.isDummy() && Ptr.getType()->isPointerType())
+ return std::nullopt;
+
+ if (!Ptr.isBlockPointer())
+ return std::nullopt;
+
+ if (Ptr.isDummy()) {
+ if (const VarDecl *VD = Ptr.getRootVarDecl();
+ VD && VD->getType()->isPointerType())
+ InvalidBase = true;
+ }
+
+ bool UseFieldDesc = (Kind & 1u);
+ bool ReportMinimum = (Kind & 2u);
+
+ // According to the GCC documentation, we want the size of the subobject
+ // denoted by the pointer. But that's not quite right -- what we actually
+ // want is the size of the immediately-enclosing array, if there is one.
+ if (Ptr.isArrayElement())
+ Ptr = Ptr.expand();
+
+ bool DetermineForCompleteObject = Ptr.getFieldDesc() == Ptr.getDeclDesc();
+ const Descriptor *DeclDesc = Ptr.getDeclDesc();
+ assert(DeclDesc);
+
+ if (!UseFieldDesc || DetermineForCompleteObject) {
+ // Can't read beyond the pointer decl desc.
+ if (!ReportMinimum && DeclDesc->getDataType(ASTCtx)->isPointerType())
+ return std::nullopt;
+
+ if (InvalidBase)
+ return std::nullopt;
+ } else {
+ if (isUserWritingOffTheEnd(ASTCtx, Ptr, InvalidBase)) {
+ // If we cannot determine the size of the initial allocation, then we
+ // can't given an accurate upper-bound. However, we are still able to give
+ // conservative lower-bounds for Type=3.
+ if (Kind == 1)
+ return std::nullopt;
+ }
+ }
+
+ // The "closest surrounding subobject" is NOT a base class,
+ // so strip the base class casts.
+ if (UseFieldDesc && Ptr.isBaseClass())
+ Ptr = Ptr.stripBaseCasts();
+
+ const Descriptor *Desc = UseFieldDesc ? Ptr.getFieldDesc() : DeclDesc;
+ assert(Desc);
+
+ std::optional<unsigned> FullSize = computeFullDescSize(ASTCtx, Desc);
+ if (!FullSize)
+ return std::nullopt;
+
+ unsigned ByteOffset;
+ if (UseFieldDesc) {
+ if (Ptr.isBaseClass()) {
+ assert(computePointerOffset(ASTCtx, Ptr.getBase()) <=
+ computePointerOffset(ASTCtx, Ptr));
+ ByteOffset = computePointerOffset(ASTCtx, Ptr.getBase()) -
+ computePointerOffset(ASTCtx, Ptr);
+ } else {
+ if (Ptr.inArray())
+ ByteOffset =
+ computePointerOffset(ASTCtx, Ptr) -
+ computePointerOffset(ASTCtx, Ptr.expand().atIndex(0).narrow());
+ else
+ ByteOffset = 0;
+ }
+ } else
+ ByteOffset = computePointerOffset(ASTCtx, Ptr);
+
+ assert(ByteOffset <= *FullSize);
+ return *FullSize - ByteOffset;
+}
+} // namespace interp
+} // namespace clang
diff --git a/clang/lib/AST/ByteCode/InterpHelpers.h b/clang/lib/AST/ByteCode/InterpHelpers.h
index 1df570ac971c4..bfe57349c31ac 100644
--- a/clang/lib/AST/ByteCode/InterpHelpers.h
+++ b/clang/lib/AST/ByteCode/InterpHelpers.h
@@ -81,7 +81,8 @@ bool CheckNewDeleteForms(InterpState &S, CodePtr OpPC,
bool DoMemcpy(InterpState &S, CodePtr OpPC, const Pointer &Src, Pointer &Dest);
UnsignedOrNone evaluateBuiltinObjectSize(const ASTContext &ASTCtx,
- unsigned Kind, Pointer &Ptr);
+ unsigned Kind, Pointer &Ptr,
+ const Expr *E, bool IsDynamic = false);
template <typename T>
bool handleOverflow(InterpState &S, CodePtr OpPC, const T &SrcValue) {
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index 050fa4c77cd2f..410116be56b9a 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -127,6 +127,23 @@ class InterpState final : public State, public SourceMapper {
return reinterpret_cast<const CXXRecordDecl **>(
this->allocate(Length * sizeof(CXXRecordDecl *)));
}
+ PointerPathEntry *allocPointerPath(unsigned Length,
+ const PointerPathEntry *OldPP) {
+ assert(Length != 0);
+ auto *PP = reinterpret_cast<PointerPathEntry *>(
+ this->allocate(Length * sizeof(PointerPathEntry)));
+ if (OldPP)
+ std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * (Length - 1));
+ return PP;
+ }
+ PointerPathEntry *allocPointerPath(unsigned Length,
+ const PointerPathEntry *OldPP,
+ PointerPathEntry Entry) {
+ assert(Length != 0);
+ auto *PP = allocPointerPath(Length, OldPP);
+ PP[Length - 1] = Entry;
+ return PP;
+ }
/// Note that a step has been executed. If there are no more steps remaining,
/// diagnoses and returns \c false.
diff --git a/clang/lib/AST/ByteCode/Opcodes.td b/clang/lib/AST/ByteCode/Opcodes.td
index 74d3cc84e06ab..ba1f0e50b7074 100644
--- a/clang/lib/AST/ByteCode/Opcodes.td
+++ b/clang/lib/AST/ByteCode/Opcodes.td
@@ -564,6 +564,7 @@ class LoadOpcode : Opcode {
def Load : LoadOpcode {}
// [Pointer] -> [Value]
def LoadPop : LoadOpcode {}
+def LoadPopL : Opcode {}
class StoreOpcode : Opcode {
let Types = [AllTypeClass];
@@ -617,6 +618,11 @@ def AddOffset : Opcode {
let Types = [IntegralTypeClass];
let HasGroup = 1;
}
+
+def GetOpaquePtr : SuccessOpcode {
+ let Args = [ArgValueDecl, ArgBool];
+}
+
// [Pointer, Integral] -> [Pointer]
def SubOffset : Opcode {
let Types = [IntegralTypeClass];
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index b987b350f9537..1964890da2877 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -25,6 +25,19 @@
using namespace clang;
using namespace clang::interp;
+// Helper to check if a RecordDecl can be passed to
+// ASTContext::getRecordLayout().
+static bool validRecordDecl(const RecordDecl *D) {
+ D = D->getDefinition();
+ return D && !D->isInvalidDecl() && D->isCompleteDefinition();
+}
+// Same but for types.
+static bool validType(QualType T) {
+ if (const RecordDecl *RD = T->getAsRecordDecl())
+ return validRecordDecl(RD);
+ return true;
+}
+
Pointer::Pointer(Block *Pointee)
: Pointer(Pointee, Pointee->getDescriptor()->getMetadataSize(),
Pointee->getDescriptor()->getMetadataSize()) {}
@@ -59,6 +72,9 @@ Pointer::Pointer(const Pointer &P)
case Storage::Typeid:
Typeid = P.Typeid;
break;
+ case Storage::Opaque:
+ Opaque = P.Opaque;
+ break;
}
}
@@ -78,6 +94,9 @@ Pointer::Pointer(Pointer &&P) : Offset(P.Offset), StorageKind(P.StorageKind) {
case Storage::Typeid:
Typeid = P.Typeid;
break;
+ case Storage::Opaque:
+ Opaque = P.Opaque;
+ break;
}
}
@@ -127,6 +146,10 @@ Pointer &Pointer::operator=(const Pointer &P) {
break;
case Storage::Typeid:
Typeid = P.Typeid;
+ break;
+ case Storage::Opaque:
+ Opaque = P.Opaque;
+ break;
}
return *this;
}
@@ -166,6 +189,10 @@ Pointer &Pointer::operator=(Pointer &&P) {
break;
case Storage::Typeid:
Typeid = P.Typeid;
+ break;
+ case Storage::Opaque:
+ Opaque = P.Opaque;
+ break;
}
return *this;
}
@@ -198,6 +225,11 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
/*OnePastTheEnd=*/false, /*IsNull=*/false);
}
+ if (isOpaquePointer()) {
+ return APValue(APValue::LValueBase(), CharUnits::Zero(), Path,
+ /*IsOnePastEnd=*/false, /*IsNullPtr=*/true);
+ }
+
// Build the lvalue base from the block.
const Descriptor *Desc = getDeclDesc();
APValue::LValueBase Base;
@@ -219,7 +251,7 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
auto getFieldOffset = [&](const FieldDecl *FD) -> CharUnits {
// This shouldn't happen, but if it does, don't crash inside
// getASTRecordLayout.
- if (FD->getParent()->isInvalidDecl())
+ if (!validRecordDecl(FD->getParent()))
return CharUnits::Zero();
const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(FD->getParent());
unsigned FieldIndex = FD->getFieldIndex();
@@ -347,6 +379,12 @@ void Pointer::print(llvm::raw_ostream &OS) const {
OS << "(Typeid) { " << (const void *)asTypeidPointer().TypePtr << ", "
<< (const void *)asTypeidPointer().TypeInfoType << " + " << Offset
<< "}";
+ break;
+ case Storage::Opaque:
+ OS << "(Opaque) { Base: " << Opaque.Base << ", "
+ << Opaque.FieldType.getPointer() << " Length: " << Opaque.PathLength
+ << ". PastEnd: " << Opaque.isOnePastEnd();
+ OS << "} + " << Offset;
}
}
@@ -371,14 +409,13 @@ Pointer::computeOffsetForComparison(const ASTContext &ASTCtx) const {
return getIntegerRepresentation();
case Storage::Typeid:
return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+ case Storage::Opaque:
+ return reinterpret_cast<uintptr_t>(asOpaquePointer().Base) + Offset;
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
- if (const RecordType *RT = T->getAs<RecordType>()) {
- // We cannot get the type size of a forward declaration.
- if (!RT->getDecl()->getDefinition())
- return std::nullopt;
- }
+ if (!validType(T))
+ return std::nullopt;
return ASTCtx.getTypeSizeInChars(T).getQuantity();
};
@@ -449,14 +486,13 @@ Pointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
return getIntegerRepresentation();
case Storage::Typeid:
return reinterpret_cast<uintptr_t>(asTypeidPointer().TypePtr) + Offset;
+ case Storage::Opaque:
+ return Opaque.computeLayoutOffset(ASTCtx);
}
auto getTypeSize = [&](QualType T) -> std::optional<size_t> {
- if (const RecordType *RT = T->getAs<RecordType>()) {
- // We cannot get the type size of a forward declaration.
- if (!RT->getDecl()->getDefinition())
- return std::nullopt;
- }
+ if (!validType(T))
+ return std::nullopt;
return ASTCtx.getTypeSizeInChars(T).getQuantity();
};
@@ -1136,6 +1172,8 @@ std::optional<APValue> Pointer::toRValue(const Context &Ctx,
const VarDecl *Pointer::getRootVarDecl() const {
if (isBlockPointer())
return getDeclDesc()->asVarDecl();
+ if (isOpaquePointer())
+ return dyn_cast<VarDecl>(Opaque.Base);
return nullptr;
}
@@ -1196,3 +1234,161 @@ IntPointer IntPointer::baseCast(const interp::Context &Ctx,
std::nullopt, RD, false);
return {T.getTypePtr(), Value + BaseLayoutOffset.getQuantity()};
}
+
+std::optional<size_t>
+OpaquePointer::computeLayoutOffset(const ASTContext &ASTCtx) const {
+ size_t Offset = 0;
+ QualType CurType = getObjectType();
+ for (const PointerPathEntry &Entry : path()) {
+ switch (Entry.Kind) {
+ case PointerPathEntry::Base: {
+ const RecordDecl *RD = CurType->getAsRecordDecl();
+ if (!validRecordDecl(RD))
+ return std::nullopt;
+
+ const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+ Offset += Layout.getBaseClassOffset(Entry.RD.getPointer()).getQuantity();
+
+ CurType = ASTCtx.getCanonicalTagType(Entry.RD.getPointer());
+ } break;
+
+ case PointerPathEntry::Field: {
+ const FieldDecl *FD = Entry.FD;
+ const RecordDecl *RD = FD->getParent();
+ if (!validRecordDecl(RD))
+ return std::nullopt;
+
+ const ASTRecordLayout &Layout = ASTCtx.getASTRecordLayout(RD);
+ Offset +=
+ ASTCtx.toCharUnitsFromBits(Layout.getFieldOffset(FD->getFieldIndex()))
+ .getQuantity();
+
+ CurType = FD->getType();
+ } break;
+ case PointerPathEntry::Array: {
+ int64_t Index = Entry.Index;
+ if (!CurType->isArrayType()) {
+ Offset += Index * ASTCtx.getTypeSizeInChars(CurType).getQuantity();
+ continue;
+ }
+ const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+ assert(AT);
+ QualType ElemTy = AT->getElementType();
+ if (!validType(ElemTy) || isa<VariableArrayType>(AT))
+ return std::nullopt;
+ Offset += Index * ASTCtx.getTypeSizeInChars(ElemTy).getQuantity();
+ CurType = AT->getElementType();
+ }
+ }
+ }
+
+ return Offset;
+}
+
+QualType OpaquePointer::getSurroundingArray() const {
+ if (PathLength == 0)
+ return getObjectType();
+ if (Path[PathLength - 1].Kind != PointerPathEntry::Array)
+ return getFieldType();
+
+ assert(Path[PathLength - 1].Kind == PointerPathEntry::Array);
+ assert(isArrayElement());
+
+ QualType CurType = getObjectType();
+ for (const PointerPathEntry &Entry : path().drop_back(1)) {
+ switch (Entry.Kind) {
+ case PointerPathEntry::Base:
+ CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType(
+ Entry.RD.getPointer());
+ break;
+ case PointerPathEntry::Field:
+ CurType = Entry.FD->getType();
+ break;
+ case PointerPathEntry::Array: {
+ if (!CurType->isArrayType())
+ break;
+ CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
+ }
+ }
+ }
+ return CurType;
+}
+
+/// Check if the pointer has offset 0.
+// As an optimization, don't actually compute the offset.
+bool OpaquePointer::isRoot() const {
+ QualType CurType = getObjectType();
+ for (const PointerPathEntry &Entry : path()) {
+ switch (Entry.Kind) {
+ case PointerPathEntry::Base:
+ if (Entry.RD.getInt())
+ return false;
+ CurType = Entry.RD.getPointer()->getASTContext().getCanonicalTagType(
+ Entry.RD.getPointer());
+ break;
+ case PointerPathEntry::Field:
+ if (!Entry.FD->getParent()->isUnion() && Entry.FD->getFieldIndex() != 0)
+ return false;
+ CurType = Entry.FD->getType();
+ break;
+ case PointerPathEntry::Array:
+ if (Entry.Index != 0)
+ return false;
+ if (!CurType->isArrayType())
+ continue;
+ CurType = CurType->getAsArrayTypeUnsafe()->getElementType();
+ }
+ }
+ return true;
+}
+
+bool OpaquePointer::isUnknownSizeArray() const {
+ QualType FieldType = getFieldType();
+
+ if (isArrayElement())
+ FieldType = getSurroundingArray();
+
+ bool Result = false;
+ // If the field type is an IncompleteArrayType, we still need to check the
+ // base to see if this array is a flexible array member _and_ has actually
+ // been initialized by data we know the size of.
+ if (isa<IncompleteArrayType>(FieldType)) {
+ const VarDecl *Base = cast<VarDecl>(this->Base);
+ if (!Base || !Base->getType()->isRecordType() || !Base->hasInit())
+ Result = true;
+ else
+ Result = !Base->hasFlexibleArrayInit(Base->getASTContext());
+ } else if (isa<VariableArrayType>(FieldType))
+ Result = true;
+
+ return Result;
+}
+
+/// This is used in Pointer::isOnePastEnd(). We cannot read from such pointers.
+/// We can of course never read from opaque pointers anyway but we diagnose
+/// one-past-the-end pointers differently.
+///
+/// In contrast, OpaquePointer::isOnePastEnd() only uses the past-end bit. That
+/// is used for the APValue conversion.
+bool OpaquePointer::isOnePastEndOrElementPastEnd() const {
+ if (isOnePastEnd())
+ return true;
+
+ if (PathLength == 0)
+ return false;
+
+ if (Path[PathLength - 1].Kind != PointerPathEntry::Array)
+ return false;
+
+ QualType ArrTy = getSurroundingArray();
+ if (!ArrTy->isArrayType())
+ return false;
+ // FIXME: Flexible array members?
+ if (const auto *CAT =
+ dyn_cast<ConstantArrayType>(ArrTy->getAsArrayTypeUnsafe())) {
+ if (Path[PathLength - 1].Index >= CAT->getSExtSize())
+ return true;
+ }
+
+ return false;
+}
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index a11031fb7a240..b2fa40f3565eb 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -370,7 +370,104 @@ struct TypeidPointer {
const Type *TypeInfoType;
};
-enum class Storage { Int, Block, Fn, Typeid };
+struct PointerPathEntry {
+ enum { Base, Field, Array } Kind;
+ union {
+ int64_t Index;
+ const FieldDecl *FD;
+ llvm::PointerIntPair<const CXXRecordDecl *, 1, bool> RD = {};
+ };
+
+ static PointerPathEntry base(const CXXRecordDecl *RD, bool Virtual = false) {
+ PointerPathEntry E;
+ E.Kind = Base;
+ E.RD = {RD, Virtual};
+ return E;
+ }
+
+ static PointerPathEntry array(int64_t Index) {
+ PointerPathEntry E;
+ E.Kind = Array;
+ E.Index = Index;
+ return E;
+ }
+
+ static PointerPathEntry field(const FieldDecl *FD) {
+ PointerPathEntry E;
+ E.Kind = Field;
+ E.FD = FD;
+ return E;
+ }
+};
+
+struct OpaquePointer {
+ const ValueDecl *Base = nullptr;
+ // FieldType and IsOnePastEnd/IsConstexprUnknown bits.
+ llvm::PointerIntPair<const Type *, 2, unsigned> FieldType = {};
+ const PointerPathEntry *Path = nullptr;
+ unsigned PathLength = 0;
+
+ ArrayRef<PointerPathEntry> path() const { return ArrayRef(Path, PathLength); }
+
+ OpaquePointer
+ withFieldType(const Type *FieldTy,
+ std::optional<bool> PastEnd = std::nullopt) const {
+ unsigned NewBitFieldValue = FieldType.getInt();
+ if (PastEnd)
+ NewBitFieldValue =
+ (isConstexprUnknown() ? 2u : 0u) + static_cast<unsigned>(*PastEnd);
+ return OpaquePointer{Base, {FieldTy, NewBitFieldValue}, Path, PathLength};
+ }
+
+ OpaquePointer withPath(const PointerPathEntry *Path, unsigned PathLength,
+ const Type *FieldTy,
+ std::optional<bool> PastEnd = std::nullopt) const {
+ unsigned NewBitFieldValue = FieldType.getInt();
+ if (PastEnd)
+ NewBitFieldValue =
+ (isConstexprUnknown() ? 2u : 0u) + static_cast<unsigned>(*PastEnd);
+ return OpaquePointer{Base, {FieldTy, NewBitFieldValue}, Path, PathLength};
+ }
+
+ OpaquePointer withPastEnd(bool PastEnd) const {
+ return OpaquePointer{Base,
+ {FieldType.getPointer(),
+ FieldType.getInt() | static_cast<unsigned>(PastEnd)},
+ Path,
+ PathLength};
+ }
+
+ QualType getObjectType() const {
+ QualType T = Base->getType();
+ if (T->isPointerOrReferenceType())
+ return T->getPointeeType();
+ return T;
+ }
+
+ QualType getFieldType() const {
+ if (FieldType.getPointer()->isPointerOrReferenceType())
+ return FieldType.getPointer()->getPointeeType();
+ return QualType(FieldType.getPointer(), 0);
+ }
+
+ bool isArrayElement() const {
+ return PathLength != 0 &&
+ Path[PathLength - 1].Kind == PointerPathEntry::Array;
+ }
+
+ std::optional<size_t> computeLayoutOffset(const ASTContext &ASTCtx) const;
+ /// If this is pointing to an array element, return the array.
+ QualType getSurroundingArray() const;
+
+ bool isOnePastEnd() const { return FieldType.getInt() & 1u; }
+ bool isOnePastEndOrElementPastEnd() const;
+ bool isConstexprUnknown() const { return FieldType.getInt() & 2u; }
+ bool isUnknownSizeArray() const;
+ bool isRoot() const;
+};
+struct OpaqueTag {};
+
+enum class Storage { Int, Block, Fn, Typeid, Opaque };
/// A pointer to a memory block, live or dead.
///
@@ -420,6 +517,16 @@ class Pointer {
Typeid.TypePtr = TypePtr;
Typeid.TypeInfoType = TypeInfoType;
}
+ Pointer(const ValueDecl *Base, bool ConstexprUnknown = false)
+ : Offset(0), StorageKind(Storage::Opaque) {
+ Opaque.Base = Base;
+ Opaque.FieldType = {Base->getType().getTypePtr(),
+ ConstexprUnknown ? 2u : 0u};
+ Opaque.Path = nullptr;
+ Opaque.PathLength = 0;
+ }
+ Pointer(OpaquePointer OP, uint64_t Offset = 0)
+ : Offset(Offset), StorageKind(Storage::Opaque), Opaque(OP) {}
Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
@@ -514,6 +621,7 @@ class Pointer {
case Storage::Fn:
return !Fn.Func;
case Storage::Typeid:
+ case Storage::Opaque:
return false;
}
llvm_unreachable("Unknown clang::interp::Storage enum");
@@ -562,7 +670,7 @@ class Pointer {
/// Accessors for information about the innermost field.
const Descriptor *getFieldDesc() const {
- if (isIntegralPointer())
+ if (!isBlockPointer())
return nullptr;
if (isRoot())
@@ -581,6 +689,8 @@ class Pointer {
return Fn.Func->getDecl()->getType();
case Storage::Typeid:
return QualType(Typeid.TypeInfoType, 0);
+ case Storage::Opaque:
+ return Opaque.getFieldType();
}
llvm_unreachable("Unhandled StorageKind");
}
@@ -634,9 +744,11 @@ class Pointer {
}
/// Checks if the structure is an array of unknown size.
bool isUnknownSizeArray() const {
- if (!isBlockPointer())
- return false;
- return getFieldDesc()->isUnknownSizeArray();
+ if (isBlockPointer())
+ return getFieldDesc()->isUnknownSizeArray();
+ if (isOpaquePointer())
+ return Opaque.isUnknownSizeArray();
+ return false;
}
/// Checks if the pointer points to an array.
bool isArrayElement() const {
@@ -647,9 +759,13 @@ class Pointer {
}
/// Pointer points directly to a block.
bool isRoot() const {
- if (isZero() || !isBlockPointer())
+ if (isZero())
return true;
- return view().isRoot();
+ if (isBlockPointer())
+ return view().isRoot();
+ if (isOpaquePointer())
+ return Opaque.isRoot();
+ return true;
}
/// If this pointer has an InlineDescriptor we can use to initialize.
bool canBeInitialized() const {
@@ -675,11 +791,16 @@ class Pointer {
assert(isTypeidPointer());
return Typeid;
}
+ [[nodiscard]] const OpaquePointer &asOpaquePointer() const {
+ assert(isOpaquePointer());
+ return Opaque;
+ }
bool isBlockPointer() const { return StorageKind == Storage::Block; }
bool isIntegralPointer() const { return StorageKind == Storage::Int; }
bool isFunctionPointer() const { return StorageKind == Storage::Fn; }
bool isTypeidPointer() const { return StorageKind == Storage::Typeid; }
+ bool isOpaquePointer() const { return StorageKind == Storage::Opaque; }
/// Returns the record descriptor of a class.
const Record *getRecord() const {
@@ -799,6 +920,8 @@ class Pointer {
return Int.Value + Offset;
if (isTypeidPointer())
return reinterpret_cast<uintptr_t>(Typeid.TypePtr) + Offset;
+ if (isOpaquePointer())
+ return Offset;
if (isOnePastEnd())
return PtrView::PastEndMark;
return Offset;
@@ -830,6 +953,9 @@ class Pointer {
/// Checks if the index is one past end.
bool isOnePastEnd() const {
+ if (isOpaquePointer())
+ return Opaque.isOnePastEndOrElementPastEnd();
+
if (!isBlockPointer())
return false;
@@ -857,6 +983,8 @@ class Pointer {
bool isZeroSizeArray() const {
if (isFunctionPointer())
return false;
+ if (isOpaquePointer())
+ return false; // FIXME: Can actually happen I think?
if (const auto *Desc = getFieldDesc())
return Desc->isZeroSizeArray();
return false;
@@ -896,9 +1024,11 @@ class Pointer {
}
bool isConstexprUnknown() const {
- if (!isBlockPointer())
- return false;
- return getDeclDesc()->IsConstexprUnknown;
+ if (isOpaquePointer())
+ return Opaque.isConstexprUnknown();
+ if (isBlockPointer())
+ return getDeclDesc()->IsConstexprUnknown;
+ return false;
}
/// Whether this block can be read from at all. This is only true for
@@ -1072,6 +1202,7 @@ class Pointer {
BlockPointer BS;
FunctionPointer Fn;
TypeidPointer Typeid;
+ OpaquePointer Opaque;
};
};
diff --git a/clang/lib/AST/ByteCode/Program.cpp b/clang/lib/AST/ByteCode/Program.cpp
index 564d2d8fc422d..373ca544a6546 100644
--- a/clang/lib/AST/ByteCode/Program.cpp
+++ b/clang/lib/AST/ByteCode/Program.cpp
@@ -143,14 +143,16 @@ unsigned Program::getOrCreateDummy(DeclOrExpr D, bool IsConstexprUnknown) {
const auto *VD = D.asValueDecl();
IsWeak = VD->isWeak();
QT = VD->getType();
- if (QT->isPointerOrReferenceType())
+
+ if (QT->isReferenceType())
QT = QT->getPointeeType();
}
+
assert(!QT.isNull());
Descriptor *Desc;
if (OptPrimType T = Ctx.classify(QT))
- Desc = createDescriptor(D, *T, /*SourceTy=*/nullptr, std::nullopt,
+ Desc = createDescriptor(D, *T, /*SourceTy=*/QT.getTypePtr(), std::nullopt,
/*IsConst=*/QT.isConstQualified());
else
Desc = createDescriptor(D, QT.getTypePtr(), std::nullopt,
diff --git a/clang/lib/AST/CMakeLists.txt b/clang/lib/AST/CMakeLists.txt
index e3f74d73f21da..ac3a13a2d155d 100644
--- a/clang/lib/AST/CMakeLists.txt
+++ b/clang/lib/AST/CMakeLists.txt
@@ -78,6 +78,7 @@ add_clang_library(clangAST
ByteCode/Function.cpp
ByteCode/InterpBuiltin.cpp
ByteCode/InterpBuiltinBitCast.cpp
+ ByteCode/InterpBuiltinObjectSize.cpp
ByteCode/Floating.cpp
ByteCode/EvaluationResult.cpp
ByteCode/DynamicAllocator.cpp
diff --git a/clang/lib/AST/ExprConstShared.h b/clang/lib/AST/ExprConstShared.h
index cdf2a5697528e..0eee03dce57ab 100644
--- a/clang/lib/AST/ExprConstShared.h
+++ b/clang/lib/AST/ExprConstShared.h
@@ -110,4 +110,6 @@ std::optional<llvm::APFloat>
EvalScalarMinMaxFp(const llvm::APFloat &A, const llvm::APFloat &B,
std::optional<llvm::APSInt> RoundingMode, bool IsMin);
+const Expr *ignorePointerCastsAndParens(const Expr *E);
+
#endif
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 480d5119a5363..5ea3938ca8bae 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -16508,7 +16508,7 @@ static QualType getObjectType(APValue::LValueBase B) {
/// Ex. For E = `(short*)((char*)(&foo))`, returns `&foo`
///
/// Always returns an RValue with a pointer representation.
-static const Expr *ignorePointerCastsAndParens(const Expr *E) {
+const Expr *ignorePointerCastsAndParens(const Expr *E) {
assert(E->isPRValue() && E->getType()->hasPointerRepresentation());
const Expr *NoParens = E->IgnoreParens();
@@ -23006,7 +23006,10 @@ std::optional<uint64_t> Expr::tryEvaluateObjectSize(const ASTContext &Ctx,
Expr::EvalStatus Status;
EvalInfo Info(Ctx, Status, EvaluationMode::ConstantFold);
if (Info.EnableNewConstInterp)
- return Info.Ctx.getInterpContext().tryEvaluateObjectSize(Info, this, Type);
+ return Info.Ctx.getInterpContext().tryEvaluateObjectSize(
+ Info, this, Type,
+ /*IsDynamic=*/false);
+
return tryEvaluateBuiltinObjectSize(this, Type, Info);
}
diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp b/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp
new file mode 100644
index 0000000000000..2f229fff28d11
--- /dev/null
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp
@@ -0,0 +1,15 @@
+// RUN: %clang_cc1 -std=c++23 -fexperimental-new-constant-interpreter -triple x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+// RUN: %clang_cc1 -std=c++23 -triple x86_64-apple-darwin -emit-llvm -o - %s | FileCheck %s
+
+struct basic_filebuf {
+ char __extbuf_;
+ char __extbuf_min_[8];
+};
+// CHECK-LABEL: @_Z4swapR13basic_filebuf
+void swap(basic_filebuf &__rhs) {
+ int gi;
+ // CHECK: store i32 8
+ gi = __builtin_object_size(__rhs.__extbuf_min_, 0);
+}
+
+
diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen.c b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
index 1b2561a89ebba..445a95f5487b9 100644
--- a/clang/test/AST/ByteCode/builtin-object-size-codegen.c
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
@@ -45,3 +45,92 @@ void foo2(struct Foo *t) {
void foo(void *p) {
int i = __builtin_object_size(&p[2], 3);
}
+
+struct DynStructVar {
+ char fst[16];
+ char snd[];
+};
+
+static struct DynStructVar D32 = {
+ .fst = {},
+ .snd = { 0, 1, 2, 3, 4, 5, 6 },
+};
+
+// CHECK-LABEL: @test32
+void test32(void) {
+ // CHECK: store i32 23
+ gi = __builtin_object_size(&D32, 0);
+ // CHECK: store i32 23
+ gi = __builtin_object_size(&D32, 1);
+ // CHECK: store i32 23
+ gi = __builtin_object_size(&D32, 2);
+ // CHECK: store i32 23
+ gi = __builtin_object_size(&D32, 3);
+
+ // CHECK: store i32 7
+ gi = __builtin_object_size(&D32.snd[0], 0);
+ // CHECK: store i32 1
+ gi = __builtin_object_size(&D32.snd[6], 0);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&D32.snd[10], 0);
+}
+
+struct S {
+ char c[7];
+ char k[];
+};
+
+struct S s = {
+ .c = {1,2,3,4,5,6,7},
+ .k = {1,2,3,4,5 }
+};
+
+// CHECK-LABEL: @testflex
+void testflex() {
+ int gi;
+ // CHECK: store i32 5
+ gi = __builtin_object_size(&s.k, 0);
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false)
+ gi = __builtin_object_size(&s.k, 1);
+ // CHECK: store i32 5
+ gi = __builtin_object_size(&s.k, 2);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s.k, 3);
+
+ // CHECK: store i32 2
+ gi = __builtin_object_size(&s.k[3], 0);
+ // CHECK: store i32 2
+ gi = __builtin_object_size(&s.k[3], 1);
+ // CHECK: store i32 2
+ gi = __builtin_object_size(&s.k[3], 2);
+ /// The following fails to evaluate in clang but returns 2 in GCC.
+ // store i32 0
+ gi = __builtin_object_size(&s.k[3], 3);
+}
+
+// CHECK-LABEL: @vlas
+void vlas(int size) {
+ char z[size];
+
+ int gi;
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false)
+ gi = __builtin_object_size(z, 0);
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false)
+ gi = __builtin_object_size(z, 1);
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 true, i1 true, i1 false)
+ gi = __builtin_object_size(z, 2);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(z, 3);
+}
+
+struct hh {
+ char s1[0];
+ char * s2;
+};
+// CHECK-LABEL: @f17
+void f17(void) {
+ struct hh h0;
+ int gi;
+ // CHECK: store i32 8
+ gi = __builtin_object_size(h0.s1, 0);
+}
diff --git a/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp b/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp
index 7a7ac26c1b0be..7055d99d75635 100644
--- a/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.cpp
@@ -51,6 +51,7 @@ typedef struct {
double c[0];
float f;
} foofoo0_t;
+
// CHECK-LABEL: @_Z6babar0P9foofoo0_t
unsigned babar0(foofoo0_t *f) {
// CHECK: ret i32 0
@@ -127,3 +128,190 @@ void nonPtrParam(C c) {
gi = __builtin_object_size(&c.bs[0], 2);
}
+
+struct X {
+ char p[7];
+};
+
+struct Y: X {
+ char p[3];
+};
+
+struct F {
+ Y y;
+};
+
+// CHECK-LABEL: @_Z6testXYv
+void testXY() {
+ int gi;
+ Y y;
+
+ // CHECK: store i32 10
+ gi = __builtin_object_size(&y, 0);
+ // CHECK: store i32 10
+ gi = __builtin_object_size(&y, 1);
+ // CHECK: store i32 10
+ gi = __builtin_object_size(&y, 2);
+ // CHECK: store i32 10
+ gi = __builtin_object_size(&y, 3);
+
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&y, 0);
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&y, 1);
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&y, 2);
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&y, 3);
+
+
+ F f;
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&f.y, 0);
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&f.y, 1);
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&f.y, 2);
+ // CHECK: store i32 10
+ gi = __builtin_object_size((X*)&f.y, 3);
+
+
+ // CHECK: store i32 6
+ gi = __builtin_object_size(&((X*)&f.y)->p[4], 0);
+ // CHECK: store i32 3
+ gi = __builtin_object_size(&((X*)&f.y)->p[4], 1);
+ // CHECK: store i32 6
+ gi = __builtin_object_size(&((X*)&f.y)->p[4], 2);
+ // CHECK: store i32 3
+ gi = __builtin_object_size(&((X*)&f.y)->p[4], 3);
+}
+
+// CHECK-LABEL: @_Z7testOPEv
+int s;
+void testOPE() {
+ int gi;
+
+ // CHECK: store i32 4
+ gi = __builtin_object_size(&s, 0);
+ // CHECK: store i32 4
+ gi = __builtin_object_size(&s, 1);
+ // CHECK: store i32 4
+ gi = __builtin_object_size(&s, 2);
+ // CHECK: store i32 4
+ gi = __builtin_object_size(&s, 3);
+
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 1, 0);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 1, 1);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 1, 2);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 1, 3);
+
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 20, 0);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 20, 1);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 20, 2);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(&s + 20, 3);
+}
+
+struct K {char p[6]; };
+// CHECK-LABEL: @_Z18testArrayAddOffsetv
+void testArrayAddOffset() {
+ int gi;
+
+ K ks[4];
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 1, 0);
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 1, 1);
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 1, 2);
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 1, 3);
+
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 3 - 2, 0);
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 3 - 2, 1);
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 3 - 2, 2);
+ // CHECK: store i32 18
+ gi = __builtin_object_size(ks + 3 - 2, 3);
+
+ // CHECK: store i32 0
+ gi = __builtin_object_size(ks - 5, 0);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(ks - 5, 1);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(ks - 5, 2);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(ks - 5, 3);
+}
+
+
+struct LoadCommandInfo {
+ char *Ptr;
+ int a;
+ int b;
+};
+
+// CHECK-LABEL: @_Z16testNonConstBasev
+void testNonConstBase() {
+ struct A { char buf[16]; };
+ struct B : A {};
+ struct C { int i; B bs[1]; } *c;
+
+ LoadCommandInfo LC;
+ int gi;
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false)
+ gi = __builtin_object_size(LC.Ptr, 0);
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false)
+ gi = __builtin_object_size(LC.Ptr + 8, 0);
+}
+
+
+struct Ref_struct {
+ int RD, Sib;
+ int *Op;
+};
+
+struct NodeBase {
+ int Next;
+ Ref_struct RefData;
+};
+
+struct NodeAddr {
+ NodeBase *Addr;
+ int Id;
+};
+
+// CHECK-LABEL: @_Z9cloneNode8NodeAddr
+void cloneNode(const NodeAddr B) {
+ NodeBase NA_0;
+ // memcpy(&NA_0, B.Addr, sizeof(NodeBase));
+
+ int gi;
+
+ // CHECK: store i32 24
+ gi = __builtin_object_size(&NA_0, 0);
+ // CHECK: store i32 24
+ gi = __builtin_object_size(&NA_0, 1);
+ // CHECK: store i32 24
+ gi = __builtin_object_size(&NA_0, 2);
+ // CHECK: store i32 24
+ gi = __builtin_object_size(&NA_0, 3);
+
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false)
+ gi = __builtin_object_size(B.Addr, 0);
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 false, i1 true, i1 false)
+ gi = __builtin_object_size(B.Addr, 1);
+ // CHECK: call i64 @llvm.objectsize.i64.p0(ptr {{.*}}, i1 true, i1 true, i1 false)
+ gi = __builtin_object_size(B.Addr, 2);
+ // CHECK: store i32 0
+ gi = __builtin_object_size(B.Addr, 3);
+}
diff --git a/clang/test/AST/ByteCode/codegen.c b/clang/test/AST/ByteCode/codegen.c
index 7f877bb4240fe..598e8f130ddab 100644
--- a/clang/test/AST/ByteCode/codegen.c
+++ b/clang/test/AST/ByteCode/codegen.c
@@ -27,3 +27,25 @@ int test(void) {
return i23;
}
// CHECK: @test.i23 = internal global i32 4, align 4
+
+union reg
+{
+ unsigned char b[2][20];
+ unsigned short w[2];
+ unsigned int d;
+};
+struct cpu
+{
+ union reg pc;
+};
+extern struct cpu cpu;
+struct svar
+{
+ void *ptr;
+};
+
+// CHECK: @svars2 = {{(dso_local )?}}global [1 x %struct.svar] [%struct.svar { ptr getelementptr (i8, ptr @cpu, i64 1) }]
+struct svar svars2[] =
+{
+ { &((cpu.pc).b[0][1]) }
+};
diff --git a/clang/test/AST/ByteCode/enable_if.c b/clang/test/AST/ByteCode/enable_if.c
deleted file mode 100644
index 8148db2719449..0000000000000
--- a/clang/test/AST/ByteCode/enable_if.c
+++ /dev/null
@@ -1,202 +0,0 @@
-// RUN: %clang_cc1 -verify=ref,both %s
-// RUN: %clang_cc1 -verify=expected,both %s -fexperimental-new-constant-interpreter
-
-// %clang_cc1 %s -DCODEGEN -emit-llvm -o - | FileCheck %s
-// %clang_cc1 %s -DCODEGEN -emit-llvm -o - -fexperimental-new-constant-interpreter | FileCheck %s
-
-/// This is the same file we have in test/Sema/, but there is one test that doesn't yet pass with the bytecode interpreter.
-/// TODO: Delete this file and add an appropriate RUN line to the file in test/Sema/ instead.
-///
-/// The problem is related to a wrongly computed value in the __builtin_object_size implementation.
-
-#define O_CREAT 0x100
-typedef int mode_t;
-typedef unsigned long size_t;
-
-enum { TRUE = 1 };
-
-int open(const char *pathname, int flags) __attribute__((enable_if(!(flags & O_CREAT), "must specify mode when using O_CREAT"))) __attribute__((overloadable)); // both-note{{candidate disabled: must specify mode when using O_CREAT}}
-int open(const char *pathname, int flags, mode_t mode) __attribute__((overloadable)); // both-note{{candidate function not viable: requires 3 arguments, but 2 were provided}}
-
-void test1(void) {
-#ifndef CODEGEN
- open("path", O_CREAT); // both-error{{no matching function for call to 'open'}}
-#endif
- open("path", O_CREAT, 0660);
- open("path", 0);
- open("path", 0, 0);
-}
-
-size_t __strnlen_chk(const char *s, size_t requested_amount, size_t s_len);
-
-size_t strnlen(const char *s, size_t maxlen)
- __attribute__((overloadable))
- __asm__("strnlen_real1");
-
-__attribute__((always_inline))
-inline size_t strnlen(const char *s, size_t maxlen)
- __attribute__((overloadable))
- __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
- "chosen when target buffer size is known")))
-{
- return __strnlen_chk(s, maxlen, __builtin_object_size(s, 0));
-}
-
-size_t strnlen(const char *s, size_t maxlen)
- __attribute__((overloadable))
- __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
- "chosen when target buffer size is known")))
- __attribute__((enable_if(maxlen <= __builtin_object_size(s, 0),
- "chosen when 'maxlen' is known to be less than or equal to the buffer size")))
- __asm__("strnlen_real2");
-
-size_t strnlen(const char *s, size_t maxlen) // ref-note {{'strnlen' has been explicitly marked unavailable here}}
- __attribute__((overloadable))
- __attribute__((enable_if(__builtin_object_size(s, 0) != -1,
- "chosen when target buffer size is known")))
- __attribute__((enable_if(maxlen > __builtin_object_size(s, 0),
- "chosen when 'maxlen' is larger than the buffer size")))
- __attribute__((unavailable("'maxlen' is larger than the buffer size")));
-
-void test2(const char *s, int i) {
-// CHECK: define {{.*}}void @test2
- const char c[123] = { 0 };
- strnlen(s, i);
-// CHECK: call {{.*}}strnlen_real1
- strnlen(s, 999);
-// CHECK: call {{.*}}strnlen_real1
- strnlen(c, 1);
-// CHECK: call {{.*}}strnlen_real2
- strnlen(c, i);
-// CHECK: call {{.*}}strnlen_chk
-#ifndef CODEGEN
- strnlen(c, 999); // ref-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}}
-#endif
-}
-
-int isdigit(int c) __attribute__((overloadable));
-int isdigit(int c) __attribute__((overloadable)) // both-note {{'isdigit' has been explicitly marked unavailable here}}
- __attribute__((enable_if(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF")))
- __attribute__((unavailable("'c' must have the value of an unsigned char or EOF")));
-
-void test3(int c) {
- isdigit(c); // both-warning{{ignoring return value of function declared with pure attribute}}
- isdigit(10); // both-warning{{ignoring return value of function declared with pure attribute}}
-#ifndef CODEGEN
- isdigit(-10); // both-error{{'isdigit' is unavailable: 'c' must have the value of an unsigned char or EOF}}
-#endif
-}
-
-// Verify that the alternate spelling __enable_if__ works as well.
-int isdigit2(int c) __attribute__((overloadable));
-int isdigit2(int c) __attribute__((overloadable)) // both-note {{'isdigit2' has been explicitly marked unavailable here}}
- __attribute__((__enable_if__(c <= -1 || c > 255, "'c' must have the value of an unsigned char or EOF")))
- __attribute__((unavailable("'c' must have the value of an unsigned char or EOF")));
-
-void test4(int c) {
- isdigit2(c);
- isdigit2(10);
-#ifndef CODEGEN
- isdigit2(-10); // both-error{{'isdigit2' is unavailable: 'c' must have the value of an unsigned char or EOF}}
-#endif
-}
-
-void test5(void) {
- int (*p1)(int) = &isdigit2;
- int (*p2)(int) = isdigit2;
- void *p3 = (void *)&isdigit2;
- void *p4 = (void *)isdigit2;
-}
-
-#ifndef CODEGEN
-__attribute__((enable_if(n == 0, "chosen when 'n' is zero"))) void f1(int n); // both-error{{use of undeclared identifier 'n'}}
-
-int n __attribute__((enable_if(1, "always chosen"))); // both-warning{{'enable_if' attribute only applies to functions}}
-
-void f(int n) __attribute__((enable_if("chosen when 'n' is zero", n == 0))); // both-error{{expected string literal as argument of 'enable_if' attribute}}
-
-void f(int n) __attribute__((enable_if())); // both-error{{'enable_if' attribute requires exactly 2 arguments}}
-
-void f(int n) __attribute__((enable_if(unresolvedid, "chosen when 'unresolvedid' is non-zero"))); // both-error{{use of undeclared identifier 'unresolvedid'}}
-
-int global;
-void f(int n) __attribute__((enable_if(global == 0, "chosen when 'global' is zero"))); // both-error{{'enable_if' attribute expression never produces a constant expression}} \
- // both-note{{subexpression not valid in a constant expression}}
-
-enum { cst = 7 };
-void return_cst(void) __attribute__((overloadable)) __attribute__((enable_if(cst == 7, "chosen when 'cst' is 7")));
-void test_return_cst(void) { return_cst(); }
-
-void f2(void) __attribute__((overloadable)) __attribute__((enable_if(1, "always chosen"))); // #f2_1
-void f2(void) __attribute__((overloadable)) __attribute__((enable_if(0, "never chosen"))); // #f2_2
-void f2(void) __attribute__((overloadable)) __attribute__((enable_if(TRUE, "always chosen #2"))); // #f2_3
-void test6(void) {
- void (*p1)(void) = &f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \
- // both-note@#f2_1 {{candidate function}} \
- // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
- // both-note@#f2_3 {{candidate function}}
- void (*p2)(void) = f2; // both-error {{initializing 'void (*)(void)' with an expression of incompatible type '<overloaded function type>'}} \
- // both-note@#f2_1 {{candidate function}} \
- // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
- // both-note@#f2_3 {{candidate function}}
- void *p3 = (void*)&f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \
- // both-note@#f2_1 {{candidate function}} \
- // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
- // both-note@#f2_3 {{candidate function}}
- void *p4 = (void*)f2; // both-error {{address of overloaded function 'f2' is ambiguous}} \
- // both-note@#f2_1 {{candidate function}} \
- // both-note@#f2_2 {{candidate function made ineligible by enable_if}} \
- // both-note@#f2_3 {{candidate function}}
-}
-
-void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m >= 0, "positive"))); // #f3_1
-void f3(int m) __attribute__((overloadable)) __attribute__((enable_if(m < 0, "negative"))); // #f3_2
-void test7(void) {
- void (*p1)(int) = &f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \
- // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
- // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
- void (*p2)(int) = f3; // both-error {{initializing 'void (*)(int)' with an expression of incompatible type '<overloaded function type>'}} \
- // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
- // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
- void *p3 = (void*)&f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \
- // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
- // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
- void *p4 = (void*)f3; // both-error {{address of overloaded function 'f3' does not match required type 'void'}} \
- // both-note@#f3_1 {{candidate function made ineligible by enable_if}} \
- // both-note@#f3_2 {{candidate function made ineligible by enable_if}}
-}
-
-void f4(int m) __attribute__((enable_if(0, "")));
-void test8(void) {
- void (*p1)(int) = &f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}}
- void (*p2)(int) = f4; // both-error{{cannot take address of function 'f4' because it has one or more non-tautological enable_if conditions}}
-}
-
-void regular_enable_if(int a) __attribute__((enable_if(a, ""))); // both-note 3{{declared here}}
-void PR27122_ext(void) {
- regular_enable_if(0, 2); // both-error{{too many arguments}}
- regular_enable_if(1, 2); // both-error{{too many arguments}}
- regular_enable_if(); // both-error{{too few arguments}}
-}
-
-// We had a bug where we'd crash upon trying to evaluate varargs.
-void variadic_enable_if(int a, ...) __attribute__((enable_if(a, ""))); // both-note 6 {{disabled}}
-void variadic_test(void) {
- variadic_enable_if(1);
- variadic_enable_if(1, 2);
- variadic_enable_if(1, "c", 3);
-
- variadic_enable_if(0); // both-error{{no matching}}
- variadic_enable_if(0, 2); // both-error{{no matching}}
- variadic_enable_if(0, "c", 3); // both-error{{no matching}}
-
- int m;
- variadic_enable_if(1);
- variadic_enable_if(1, m);
- variadic_enable_if(1, m, "c");
-
- variadic_enable_if(0); // both-error{{no matching}}
- variadic_enable_if(0, m); // both-error{{no matching}}
- variadic_enable_if(0, m, 3); // both-error{{no matching}}
-}
-#endif
diff --git a/clang/test/AST/ByteCode/literals.cpp b/clang/test/AST/ByteCode/literals.cpp
index 3b24b166e39a7..97eab655032b6 100644
--- a/clang/test/AST/ByteCode/literals.cpp
+++ b/clang/test/AST/ByteCode/literals.cpp
@@ -30,6 +30,9 @@ static_assert(!__objc_no, "");
static_assert((long long)0x00000000FFFF0000 == 4294901760, "");
+int kk[3];
+static_assert(kk + 3 == &kk[3], "");
+
constexpr bool b = number;
static_assert(b, "");
constexpr int one = true;
diff --git a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
index e840db632957e..272ff5004d3e8 100644
--- a/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
+++ b/clang/test/CodeGen/attr-counted-by-with-sanitizers.c
@@ -1,6 +1,8 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fsanitize=array-bounds,object-size,local-bounds -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=SANITIZE-WITHOUT-ATTR %s
#if !__has_attribute(counted_by)
#error "has attribute broken"
diff --git a/clang/test/CodeGen/attr-counted-by-without-sanitizers.c b/clang/test/CodeGen/attr-counted-by-without-sanitizers.c
index 7ceb51c8986bd..1dd307ff7f72b 100644
--- a/clang/test/CodeGen/attr-counted-by-without-sanitizers.c
+++ b/clang/test/CodeGen/attr-counted-by-without-sanitizers.c
@@ -1,6 +1,10 @@
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 6
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s
-// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s
+
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -DCOUNTED_BY -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=NO-SANITIZE-WITH-ATTR %s
+// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -O2 -Wall -fstrict-flex-arrays=3 -emit-llvm -o - %s -fexperimental-new-constant-interpreter | FileCheck --check-prefix=NO-SANITIZE-WITHOUT-ATTR %s
+
#if !__has_attribute(counted_by)
#error "has attribute broken"
diff --git a/clang/test/Sema/enable_if.c b/clang/test/Sema/enable_if.c
index 38a3dad565657..fd7408c6f4c30 100644
--- a/clang/test/Sema/enable_if.c
+++ b/clang/test/Sema/enable_if.c
@@ -3,6 +3,9 @@
// RUN: %clang_cc1 %s -verify -fexperimental-new-constant-interpreter
// RUN: %clang_cc1 %s -DCODEGEN -emit-llvm -o - -fexperimental-new-constant-interpreter | FileCheck %s
+// RUN: %clang_cc1 %s -fexperimental-new-constant-interpreter -verify
+// RUN: %clang_cc1 %s -fexperimental-new-constant-interpreter -DCODEGEN -emit-llvm -o - | FileCheck %s
+
#define O_CREAT 0x100
typedef int mode_t;
typedef unsigned long size_t;
diff --git a/clang/test/SemaCXX/new-delete.cpp b/clang/test/SemaCXX/new-delete.cpp
index 2a2f91186871e..0c1f95ae3d09a 100644
--- a/clang/test/SemaCXX/new-delete.cpp
+++ b/clang/test/SemaCXX/new-delete.cpp
@@ -718,12 +718,22 @@ int *fail = dependent_array_size("hello"); // expected-note {{instantiation of}}
// FIXME: Our behavior here is incredibly inconsistent. GCC allows
// constant-folding in array bounds in new-expressions.
int (*const_fold)[12] = new int[3][&const_fold + 12 - &const_fold];
-#if __cplusplus >= 201402L && !defined(NEW_INTERP)
+#if __cplusplus >= 201402L
// expected-error at -2 {{array size is not a constant expression}}
// expected-note at -3 {{cannot refer to element 12 of non-array}}
+#elif __cplusplus == 201103L
+#if defined(NEW_INTERP)
+// expected-error at -6 {{only the first dimension of an allocated array may have dynamic size}}
+// expected-note at -7 {{cannot refer to element 12 of non-array}}
+#endif
#elif __cplusplus < 201103L
-// expected-error at -5 {{cannot allocate object of variably modified type}}
-// expected-warning at -6 {{variable length arrays in C++ are a Clang extension}}
+#if defined(NEW_INTERP)
+// expected-error at -11 {{only the first dimension of an allocated array may have dynamic size}}
+// expected-note at -12 {{cannot refer to element 12 of non-array}}
+#else
+// expected-error at -14 {{cannot allocate object of variably modified type}}
+// expected-warning at -15 {{variable length arrays in C++ are a Clang extension}}
+#endif
#endif
#if __cplusplus >= 201103L
>From dc3e9a6ff6fd3d4cff51a3cb46dd3923812efeba Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Sat, 8 Aug 2026 17:31:55 +0200
Subject: [PATCH 3/3] extendPointerPath
---
clang/lib/AST/ByteCode/Interp.cpp | 6 +++---
clang/lib/AST/ByteCode/InterpState.h | 18 +++++++++++-------
clang/lib/AST/ByteCode/Pointer.cpp | 6 ++----
3 files changed, 16 insertions(+), 14 deletions(-)
diff --git a/clang/lib/AST/ByteCode/Interp.cpp b/clang/lib/AST/ByteCode/Interp.cpp
index 4f0f2c2739155..e05ed96c251fb 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1634,7 +1634,7 @@ static bool getField(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
if (!F)
return false;
- PointerPathEntry *NewPath = S.allocPointerPath(
+ PointerPathEntry *NewPath = S.extendPointerPath(
OP.PathLength + 1, OP.Path, PointerPathEntry::field(F->Decl));
S.Stk.push<Pointer>(OP.withPath(NewPath, OP.PathLength + 1,
@@ -1690,7 +1690,7 @@ static bool getBase(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
if (!B)
return false;
- PointerPathEntry *NewPath = S.allocPointerPath(
+ PointerPathEntry *NewPath = S.extendPointerPath(
OP.PathLength + 1, OP.Path,
PointerPathEntry::base(cast<CXXRecordDecl>(B->Decl)));
S.Stk.push<Pointer>(
@@ -3320,7 +3320,7 @@ bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
NewPathLength = OP.PathLength + 1;
}
- PointerPathEntry *NewPath = S.allocPointerPath(
+ PointerPathEntry *NewPath = S.extendPointerPath(
NewPathLength, OP.Path, PointerPathEntry::array(Index));
S.Stk.push<Pointer>(
OP.withPath(NewPath, NewPathLength, ElemType.getTypePtr()),
diff --git a/clang/lib/AST/ByteCode/InterpState.h b/clang/lib/AST/ByteCode/InterpState.h
index 410116be56b9a..b4d3203c34c46 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -133,15 +133,19 @@ class InterpState final : public State, public SourceMapper {
auto *PP = reinterpret_cast<PointerPathEntry *>(
this->allocate(Length * sizeof(PointerPathEntry)));
if (OldPP)
- std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * (Length - 1));
+ std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * Length);
return PP;
}
- PointerPathEntry *allocPointerPath(unsigned Length,
- const PointerPathEntry *OldPP,
- PointerPathEntry Entry) {
- assert(Length != 0);
- auto *PP = allocPointerPath(Length, OldPP);
- PP[Length - 1] = Entry;
+ /// Allocate a new pointer path of Length \c NewLength.
+ /// NewLength - 1 elements are copied form \c OldPP.
+ PointerPathEntry *extendPointerPath(unsigned NewLength,
+ const PointerPathEntry *OldPP,
+ PointerPathEntry NewEntry) {
+ auto *PP = reinterpret_cast<PointerPathEntry *>(
+ this->allocate(NewLength * sizeof(PointerPathEntry)));
+ if (OldPP)
+ std::memcpy(PP, OldPP, sizeof(PointerPathEntry) * (NewLength - 1));
+ PP[NewLength - 1] = NewEntry;
return PP;
}
diff --git a/clang/lib/AST/ByteCode/Pointer.cpp b/clang/lib/AST/ByteCode/Pointer.cpp
index 1964890da2877..aa2a10c4f329a 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -225,10 +225,8 @@ APValue Pointer::toAPValue(const ASTContext &ASTCtx) const {
/*OnePastTheEnd=*/false, /*IsNull=*/false);
}
- if (isOpaquePointer()) {
- return APValue(APValue::LValueBase(), CharUnits::Zero(), Path,
- /*IsOnePastEnd=*/false, /*IsNullPtr=*/true);
- }
+ if (isOpaquePointer())
+ llvm_unreachable("this shouldn't happen yet");
// Build the lvalue base from the block.
const Descriptor *Desc = getDeclDesc();
More information about the cfe-commits
mailing list