[clang] [clang][bytecode] Add opaque pointers to support type-only pointers (PR #213017)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Sun Aug 2 21:20:34 PDT 2026
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 eee6b8d155322296b96488f470f3367e6119e7c0 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/2] test changes
---
clang/test/CodeGen/object-size.c | 92 +--------------------------
clang/test/CodeGen/pass-object-size.c | 5 +-
2 files changed, 5 insertions(+), 92 deletions(-)
diff --git a/clang/test/CodeGen/object-size.c b/clang/test/CodeGen/object-size.c
index 00ab18cb22e84..ad8969f3644d3 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 | FileCheck %s
+// RUN: %clang_cc1 -no-enable-noundef-analysis -DDYNAMIC -triple x86_64-apple-darwin -emit-llvm %s -o - 2>&1 | FileCheck %s
+
#ifndef DYNAMIC
#define OBJECT_SIZE_BUILTIN __builtin_object_size
@@ -59,93 +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
- gi = OBJECT_SIZE_BUILTIN(gp++, 0);
- // CHECK: store i32 -1
- gi = OBJECT_SIZE_BUILTIN(gp++, 1);
- // CHECK: store i32 0
- gi = OBJECT_SIZE_BUILTIN(gp++, 2);
- // CHECK: store i32 0
- gi = OBJECT_SIZE_BUILTIN(gp++, 3);
-}
-
// CHECK-LABEL: @test18
unsigned test18(int cond) {
int a[4], b[4];
@@ -337,8 +253,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..64a544642bfe0 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,8 +218,6 @@ 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 %{{.*}})
- gi = NoViableOverloadObjectSize1(&t[1]);
// CHECK: call i32 @_Z27NoViableOverloadObjectSize2PvU17pass_object_size2(ptr noundef %{{.*}}, i64 noundef %{{.*}})
gi = NoViableOverloadObjectSize2(&t[1]);
// CHECK: call i32 @_Z27NoViableOverloadObjectSize3PvU17pass_object_size3(ptr noundef %{{.*}}, i64 noundef 0)
>From 56bdee1758178becb54f429780bd857748ae2b07 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/2] opaque pointers
---
clang/lib/AST/ByteCode/Compiler.cpp | 44 +-
clang/lib/AST/ByteCode/Context.cpp | 16 +-
clang/lib/AST/ByteCode/Context.h | 2 +-
clang/lib/AST/ByteCode/Interp.cpp | 131 ++++
clang/lib/AST/ByteCode/Interp.h | 123 ++--
clang/lib/AST/ByteCode/InterpBuiltin.cpp | 232 +------
.../AST/ByteCode/InterpBuiltinObjectSize.cpp | 601 ++++++++++++++++++
clang/lib/AST/ByteCode/InterpHelpers.h | 3 +-
clang/lib/AST/ByteCode/InterpState.h | 4 +
clang/lib/AST/ByteCode/Opcodes.td | 6 +
clang/lib/AST/ByteCode/Pointer.cpp | 54 ++
clang/lib/AST/ByteCode/Pointer.h | 94 ++-
clang/lib/AST/ByteCode/Program.cpp | 6 +-
clang/lib/AST/CMakeLists.txt | 1 +
clang/lib/AST/ExprConstant.cpp | 5 +-
.../builtin-object-size-codegen-cxx23.cpp | 15 +
.../ByteCode/builtin-object-size-codegen.c | 64 ++
.../ByteCode/builtin-object-size-codegen.cpp | 188 ++++++
clang/test/AST/ByteCode/enable_if.c | 4 +-
.../CodeGen/attr-counted-by-with-sanitizers.c | 6 +-
.../attr-counted-by-without-sanitizers.c | 8 +-
clang/test/SemaCXX/new-delete.cpp | 16 +-
22 files changed, 1347 insertions(+), 276 deletions(-)
create mode 100644 clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
create mode 100644 clang/test/AST/ByteCode/builtin-object-size-codegen-cxx23.cpp
diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp
index 479b5251b9f57..1c13fe7abfd6a 100644
--- a/clang/lib/AST/ByteCode/Compiler.cpp
+++ b/clang/lib/AST/ByteCode/Compiler.cpp
@@ -453,8 +453,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);
@@ -6037,6 +6041,33 @@ bool Compiler<Emitter>::visitAPValueInitializer(const APValue &Val,
return false;
}
+/// A more selective version of E->IgnoreParenCasts for
+/// tryEvaluateBuiltinObjectSize. This ignores some casts/parens that serve only
+/// to change the type of E.
+/// Ex. For E = `(short*)((char*)(&foo))`, returns `&foo`
+///
+/// Always returns an RValue with a pointer representation.
+static const Expr *ignorePointerCastsAndParens(const Expr *E) {
+ assert(E->isPRValue() && E->getType()->hasPointerRepresentation());
+
+ const Expr *NoParens = E->IgnoreParens();
+ const auto *Cast = dyn_cast<CastExpr>(NoParens);
+ if (Cast == nullptr)
+ return NoParens;
+
+ // We only conservatively allow a few kinds of casts, because this code is
+ // inherently a simple solution that seeks to support the common case.
+ auto CastKind = Cast->getCastKind();
+ if (CastKind != CK_NoOp && CastKind != CK_BitCast &&
+ CastKind != CK_AddressSpaceConversion)
+ return NoParens;
+
+ const auto *SubExpr = Cast->getSubExpr();
+ if (!SubExpr->getType()->hasPointerRepresentation() || !SubExpr->isPRValue())
+ return NoParens;
+ return ignorePointerCastsAndParens(SubExpr);
+}
+
template <class Emitter>
bool Compiler<Emitter>::VisitBuiltinCallExpr(const CallExpr *E,
unsigned BuiltinID) {
@@ -6095,7 +6126,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 +8737,13 @@ bool Compiler<Emitter>::emitDestructionPop(const Descriptor *Desc,
template <class Emitter>
bool Compiler<Emitter>::emitDummyPtr(const DeclTy &D, const Expr *E, bool CU) {
assert(!DiscardResult && "Should've been checked before");
- unsigned DummyID = P.getOrCreateDummy(D, CU);
+ if (ToLValue) {
+ if (auto *VD = dyn_cast_if_present<ValueDecl>(D.dyn_cast<const Decl *>()))
+ return this->emitGetOpaquePtr(VD, 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 b913d2a9f539c..6e3b93e2107b5 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -366,26 +366,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 475d206356de8..8e5d6ec28e246 100644
--- a/clang/lib/AST/ByteCode/Interp.cpp
+++ b/clang/lib/AST/ByteCode/Interp.cpp
@@ -1612,6 +1612,31 @@ 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);
+ if (OP.Path)
+ std::memcpy(NewPath, Ptr.asOpaquePointer().Path,
+ sizeof(PointerPathEntry) * OP.PathLength);
+
+ NewPath[OP.PathLength] = PointerPathEntry::field(F->Decl);
+
+ S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, F->Decl->getType().getTypePtr(),
+ NewPath, OP.PathLength + 1);
+ return true;
+ }
+
if (!Ptr.isBlockPointer()) {
// If we're trying to get the field of a TypeId pointer, try to produce a
// proper diagnostic.
@@ -1646,6 +1671,33 @@ 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;
+
+ unsigned NewPathLength = OP.PathLength + 1;
+ PointerPathEntry *NewPath = S.allocPointerPath(NewPathLength);
+ if (OP.Path)
+ std::memcpy(NewPath, OP.Path,
+ sizeof(PointerPathEntry) * (NewPathLength - 1));
+
+ NewPath[NewPathLength - 1] =
+ PointerPathEntry::base(cast<CXXRecordDecl>(B->Decl));
+ S.Stk.push<Pointer>(
+ OpaqueTag{}, OP.Base,
+ S.getASTContext().getCanonicalTagType(B->Decl).getTypePtr(), NewPath,
+ NewPathLength);
+ return true;
+ }
+
if (!Ptr.isBlockPointer()) {
if (!Ptr.isIntegralPointer())
return false;
@@ -1906,6 +1958,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) {
@@ -3221,6 +3274,84 @@ bool CastFloatingIntegralAPS(InterpState &S, CodePtr OpPC, uint32_t BitWidth,
return floatAPCast<true>(S, OpPC, F, BitWidth, FPOI);
}
+bool arrayElemPtrOpaque(InterpState &S, CodePtr OpPC, const OpaquePointer &OP,
+ int64_t Offset) {
+ QualType ArrTy;
+ if (OP.PathLength > 0) {
+ if (OP.path().back().Kind == PointerPathEntry::Array) {
+ ArrTy = OP.getSurroundingArray(S.getASTContext());
+ } else {
+ ArrTy = OP.getFieldType();
+ }
+ } else {
+ ArrTy = OP.getObjectType();
+ }
+
+ QualType ElemType;
+ bool PastEnd = true;
+ if (ArrTy->isArrayType()) {
+ const auto *AT = ArrTy->getAsArrayTypeUnsafe();
+ ElemType = AT->getElementType();
+ if (const auto *CAT = dyn_cast<ConstantArrayType>(AT))
+ PastEnd = Offset >= CAT->getSExtSize();
+ } else if (ArrTy->isRecordType()) {
+ ElemType = ArrTy;
+ } else {
+ ElemType = ArrTy;
+ }
+ if (ElemType.isNull())
+ return false;
+
+ unsigned NewPathLength = OP.PathLength + 1;
+ PointerPathEntry *NewPath = S.allocPointerPath(NewPathLength);
+ if (OP.Path)
+ std::memcpy(NewPath, OP.Path,
+ sizeof(PointerPathEntry) * (NewPathLength - 1));
+
+ NewPath[NewPathLength - 1] = PointerPathEntry::array(Offset);
+ S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, ElemType.getTypePtr(), NewPath,
+ NewPathLength, PastEnd);
+ return true;
+}
+
+bool addSubOffsetOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ uint64_t Offset, bool Add) {
+ assert(Ptr.isOpaquePointer());
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+ QualType ArrTy = OP.getFieldType();
+ QualType ElemTy;
+ if (ArrTy->isArrayType())
+ ElemTy = ArrTy->getAsArrayTypeUnsafe()->getElementType();
+ else
+ ElemTy = ArrTy;
+
+ if (Offset != 0) {
+ if (isa<IncompleteArrayType>(ArrTy)) {
+ const SourceInfo &E = S.Current->getSource(OpPC);
+ S.FFDiag(E, diag::note_constexpr_unsized_array_indexed);
+ return false;
+ }
+
+ S.CCEDiag(S.Current->getSource(OpPC), diag::note_constexpr_array_index)
+ << Offset << /*non-array*/ true << 0;
+ }
+
+ unsigned ElemSize =
+ S.getASTContext().getTypeSizeInChars(ElemTy).getQuantity();
+ unsigned NewOffset;
+ if (Add)
+ NewOffset = Ptr.getByteOffset() + (ElemSize * Offset);
+ else
+ NewOffset = Ptr.getByteOffset() - (ElemSize * Offset);
+
+ bool PastEnd = NewOffset > 0;
+
+ S.Stk.push<Pointer>(OpaqueTag{}, OP.Base, OP.FieldType.getPointer(), OP.Path,
+ OP.PathLength, PastEnd, NewOffset);
+ return true;
+}
+
// 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 405f4a29ec982..85d5fcd4dc9fb 100644
--- a/clang/lib/AST/ByteCode/Interp.h
+++ b/clang/lib/AST/ByteCode/Interp.h
@@ -2252,6 +2252,45 @@ 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 (!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>(OpaqueTag{}, OP.Base, T.getTypePtr(), OP.Path,
+ OP.PathLength, OP.isOnePastEnd(),
+ Ptr.getByteOffset());
+
+ } else {
+ S.Stk.push<Pointer>(OpaqueTag{}, Ptr.getDeclDesc()->asValueDecl(),
+ Ptr.getType()->getPointeeType().getTypePtr());
+ }
+
+ } 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>();
@@ -2646,11 +2685,17 @@ std::optional<Pointer> OffsetHelper(InterpState &S, CodePtr OpPC,
return Ptr.atIndex(static_cast<uint64_t>(Result));
}
+bool addSubOffsetOpaque(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
+ uint64_t Offset, bool Add);
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())
+ return addSubOffsetOpaque(S, OpPC, Ptr, static_cast<uint64_t>(Offset),
+ /*Add=*/true);
+
if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Add>(
S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
S.Stk.push<Pointer>(Result->narrow());
@@ -2664,6 +2709,10 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
const T &Offset = S.Stk.pop<T>();
const Pointer &Ptr = S.Stk.pop<Pointer>().expand();
+ if (Ptr.isOpaquePointer())
+ return addSubOffsetOpaque(S, OpPC, Ptr, static_cast<uint64_t>(Offset),
+ /*Add=*/false);
+
if (std::optional<Pointer> Result = OffsetHelper<T, ArithOp::Sub>(
S, OpPC, Offset, Ptr, /*IsPointerArith=*/true)) {
S.Stk.push<Pointer>(Result->narrow());
@@ -2672,6 +2721,12 @@ bool SubOffset(InterpState &S, CodePtr OpPC) {
return false;
}
+inline bool GetOpaquePtr(InterpState &S, const ValueDecl *VD) {
+ S.Stk.push<Pointer>(OpaqueTag{}, VD);
+
+ return true;
+}
+
template <ArithOp Op>
static inline bool IncDecPtrHelper(InterpState &S, CodePtr OpPC,
const Pointer &Ptr) {
@@ -3431,18 +3486,16 @@ 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 OpaquePointer &OP,
+ int64_t Offset);
+
+// 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.asOpaquePointer(),
+ static_cast<int64_t>(Offset));
if (!Ptr.isZero() && !Offset.isZero()) {
if (!CheckArray(S, OpPC, Ptr))
@@ -3466,38 +3519,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>
@@ -3569,6 +3615,11 @@ inline bool ArrayDecay(InterpState &S, CodePtr OpPC) {
return false;
}
+ if (Ptr.isOpaquePointer()) {
+ S.Stk.push<Pointer>(Ptr);
+ return true;
+ }
+
if (Ptr.isRoot() || !Ptr.isUnknownSizeArray()) {
S.Stk.push<Pointer>(Ptr.atIndex(0).narrow());
return true;
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index a539fb26abc08..7ba47a99cbb6e 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
@@ -2528,10 +2319,24 @@ static bool interp__builtin_object_size(InterpState &S, CodePtr OpPC,
return true;
}
- if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr)) {
+ if (auto Result = evaluateBuiltinObjectSize(ASTCtx, Kind, Ptr,
+ Call->getArg(0), IsDynamic)) {
pushInteger(S, *Result, Call->getType());
return true;
}
+
+ 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) ? 0 : -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..306d3c739e62b
--- /dev/null
+++ b/clang/lib/AST/ByteCode/InterpBuiltinObjectSize.cpp
@@ -0,0 +1,601 @@
+//===------------- 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;
+
+static bool b = false;
+
+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) {
+ if (auto p = Ptr.computeLayoutOffset(ASTCtx))
+ return *p;
+ return 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, int Kind,
+ bool &OffsetIsNegative) {
+ if (b)
+ llvm::errs() << __PRETTY_FUNCTION__ << '\n';
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+
+ unsigned 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))
+ 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 (OP.PathLength == 1 && OP.path().back().Kind == PointerPathEntry::Field &&
+ isa<IncompleteArrayType>(CurType)) {
+
+ if (Kind == 1)
+ return std::nullopt;
+
+ return Offset;
+ }
+
+ if (isa<IncompleteArrayType>(CurType))
+ return std::nullopt;
+
+ if (UseClosestSurroundingVariable)
+ return 0;
+
+ return Offset;
+}
+
+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);
+ return isa<IncompleteArrayType>(FieldType);
+}
+
+static std::optional<unsigned>
+computeOpaqueSize(const ASTContext &ASTCtx, const Pointer &Ptr,
+ bool UseClosestSurroundingVariable) {
+
+ if (b) {
+ llvm::errs() << __PRETTY_FUNCTION__ << '\n';
+ for (auto &E : Ptr.asOpaquePointer().path())
+ llvm::errs() << "Kind: " << E.Kind << '\n';
+ }
+
+ 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);
+ }
+
+ // Check if we need to add the flexible array member size.
+ const VarDecl *Base = dyn_cast<VarDecl>(OP.Base);
+ if (!Base || !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 (b) {
+ llvm::errs() << __PRETTY_FUNCTION__ << '\n';
+ llvm::errs() << Ptr << '\n';
+ E->dumpColor();
+ }
+
+ if (Ptr.isZero()) {
+ return std::nullopt;
+ }
+
+ if (Ptr.isDummy() && Ptr.getType()->isPointerType()) {
+ if (b)
+ llvm::errs() << "err2\n";
+ return std::nullopt;
+ }
+
+ bool InvalidBase = false;
+
+ if (Ptr.isDummy()) {
+ if (const VarDecl *VD = Ptr.getRootVarDecl();
+ VD && VD->getType()->isPointerType())
+ InvalidBase = true;
+ }
+
+ if (Ptr.isOpaquePointer()) {
+ bool UseClosestSurroundingVariable = (Kind == 1) || (Kind == 3);
+ const OpaquePointer &OP = Ptr.asOpaquePointer();
+ if (b)
+ llvm::errs() << "------------ OPAQUE BOS\n";
+ InvalidBase = OP.Base->getType()->isPointerType();
+ bool DetermineForCompleteObject = pointsToCompleteObject(ASTCtx, Ptr);
+
+ if (b) {
+ llvm::errs() << "DetermineForCompleteObject: "
+ << DetermineForCompleteObject << '\n';
+ // llvm::errs() << "UseFieldDesc: " << UseFieldDesc << '\n';
+ // llvm::errs() << "ReportMinimum: " << ReportMinimum << '\n';
+ llvm::errs() << "InvalidBase: " << InvalidBase << '\n';
+ llvm::errs() << "WOTE: " << isUserWritingOffTheEnd(ASTCtx, OP) << '\n';
+ llvm::errs() << "UseClosestSurroundingVariable: "
+ << UseClosestSurroundingVariable << '\n';
+ llvm::errs() << "Pointer Byte offset: " << Ptr.getByteOffset() << '\n';
+ }
+
+ // 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);
+
+ if (!FullSize)
+ return std::nullopt;
+ if (b) {
+ llvm::errs() << "COMPUTED SIZE: " << *FullSize << '\n';
+ }
+
+ // 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, Kind, 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()) {
+ if (b)
+ llvm::errs() << "FME check\n";
+ return std::nullopt;
+ }
+ }
+
+ if (!(Kind & 1) || false || DetermineForCompleteObject) {
+ // Kind=3 wants a lower bound, so we can't fall back to this.
+ if (Kind == 3 && !DetermineForCompleteObject)
+ return std::nullopt;
+
+ // llvm::APInt APEndOffset;
+ // if (isBaseAnAllocSizeCall(LVal.getLValueBase()) &&
+ // getBytesReturnedByAllocSizeCall(Info.Ctx, LVal, APEndOffset))
+ // return convertUnsignedAPIntToCharUnits(APEndOffset, EndOffset);
+
+ if (OP.Base->getType()->isPointerType()) {
+ // llvm::errs() << "Lval has invalid base\n";
+ return std::nullopt;
+ }
+
+ // QualType BaseTy = getObjectType(LVal.getLValueBase());
+ // const bool Ret = CheckedHandleSizeof(BaseTy, EndOffset);
+ // addFlexibleArrayMemberInitSize(Info, BaseTy, LVal, EndOffset);
+ // return Ret;
+ }
+
+ if (b) {
+ llvm::errs() << "FullSize: " << *FullSize << '\n';
+ llvm::errs() << "Offset : " << Offset << '\n';
+ llvm::errs() << "Offset : " << (*Offset + Ptr.getByteOffset()) << '\n';
+
+ llvm::errs() << *FullSize << " - " << (*Offset + Ptr.getByteOffset())
+ << '\n';
+ }
+ *Offset += Ptr.getByteOffset();
+
+ if (*Offset > *FullSize)
+ return 0u;
+
+ if (!UseClosestSurroundingVariable || DetermineForCompleteObject) {
+ if (InvalidBase) {
+ if (b)
+ llvm::errs() << "err4\n";
+ return std::nullopt;
+ }
+ }
+
+ if (InvalidBase && isUserWritingOffTheEnd(ASTCtx, OP)) {
+ if (Kind == 1)
+ return std::nullopt;
+ }
+
+ assert(*Offset <= *FullSize);
+ return static_cast<unsigned>(*FullSize - *Offset);
+ }
+
+ // ----------------------------------------------------------------------------------------------------
+
+ if (Ptr.isZero() || !Ptr.isBlockPointer())
+ return std::nullopt;
+
+ 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()) {
+ llvm::errs() << "err3\n";
+ return std::nullopt;
+ }
+
+ if (InvalidBase) {
+ llvm::errs() << "err4\n";
+ 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) {
+ llvm::errs() << "err5\n";
+ 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;
+}
+} // 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..4fdae266e5d86 100644
--- a/clang/lib/AST/ByteCode/InterpState.h
+++ b/clang/lib/AST/ByteCode/InterpState.h
@@ -127,6 +127,10 @@ class InterpState final : public State, public SourceMapper {
return reinterpret_cast<const CXXRecordDecl **>(
this->allocate(Length * sizeof(CXXRecordDecl *)));
}
+ PointerPathEntry *allocPointerPath(unsigned Length) {
+ return reinterpret_cast<PointerPathEntry *>(
+ this->allocate(Length * sizeof(PointerPathEntry)));
+ }
/// 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 c2f0114c81957..8dc299dc80e84 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];
+}
+
// [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..bc80eda50d45a 100644
--- a/clang/lib/AST/ByteCode/Pointer.cpp
+++ b/clang/lib/AST/ByteCode/Pointer.cpp
@@ -59,6 +59,9 @@ Pointer::Pointer(const Pointer &P)
case Storage::Typeid:
Typeid = P.Typeid;
break;
+ case Storage::Opaque:
+ Opaque = P.Opaque;
+ break;
}
}
@@ -78,6 +81,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 +133,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 +176,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 +212,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;
@@ -347,6 +366,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,6 +396,8 @@ 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> {
@@ -449,6 +476,8 @@ Pointer::computeLayoutOffset(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> {
@@ -1196,3 +1225,28 @@ IntPointer IntPointer::baseCast(const interp::Context &Ctx,
std::nullopt, RD, false);
return {T.getTypePtr(), Value + BaseLayoutOffset.getQuantity()};
}
+
+QualType OpaquePointer::getSurroundingArray(const ASTContext &ASTCtx) const {
+ assert(PathLength != 0);
+ assert(Path[PathLength - 1].Kind == PointerPathEntry::Array);
+
+ QualType CurType = getObjectType();
+ for (const PointerPathEntry &Entry : path().drop_back(1)) {
+ 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())
+ break;
+ const ArrayType *AT = CurType->getAsArrayTypeUnsafe();
+ assert(AT);
+ CurType = AT->getElementType();
+ }
+ }
+ }
+ return CurType;
+}
diff --git a/clang/lib/AST/ByteCode/Pointer.h b/clang/lib/AST/ByteCode/Pointer.h
index c06347318dafa..a3ddf75f94180 100644
--- a/clang/lib/AST/ByteCode/Pointer.h
+++ b/clang/lib/AST/ByteCode/Pointer.h
@@ -370,7 +370,66 @@ 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 bit.
+ llvm::PointerIntPair<const Type *, 1, bool> FieldType = {};
+ const PointerPathEntry *Path = nullptr;
+ unsigned PathLength = 0;
+
+ ArrayRef<PointerPathEntry> path() const { return ArrayRef(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);
+ }
+
+ /// If this is pointing to an array element, return the array.
+ QualType getSurroundingArray(const ASTContext &ASTCtx) const;
+
+ bool isOnePastEnd() const { return FieldType.getInt(); }
+};
+struct OpaqueTag {};
+
+enum class Storage { Int, Block, Fn, Typeid, Opaque };
/// A pointer to a memory block, live or dead.
///
@@ -420,6 +479,30 @@ class Pointer {
Typeid.TypePtr = TypePtr;
Typeid.TypeInfoType = TypeInfoType;
}
+ Pointer(OpaqueTag, const ValueDecl *Base, const Type *FieldType,
+ uint64_t Offset = 0)
+ : Offset(Offset), StorageKind(Storage::Opaque) {
+ Opaque.FieldType = {FieldType, /*IsOnePastEnd=*/false};
+ Opaque.Path = nullptr;
+ Opaque.PathLength = 0;
+ Opaque.Base = Base;
+ }
+ Pointer(OpaqueTag, const ValueDecl *Base, const Type *FieldType,
+ const PointerPathEntry *Path, unsigned PathLength, bool OPE = false,
+ uint64_t Offset = 0)
+ : Offset(Offset), StorageKind(Storage::Opaque) {
+ Opaque.FieldType = {FieldType, OPE};
+ Opaque.Path = Path;
+ Opaque.PathLength = PathLength;
+ Opaque.Base = Base;
+ }
+ Pointer(OpaqueTag, const ValueDecl *Base, uint64_t Offset = 0)
+ : Offset(Offset), StorageKind(Storage::Opaque) {
+ Opaque.FieldType = {Base->getType().getTypePtr(), /*IsOnePastEnd=*/false};
+ Opaque.Path = nullptr;
+ Opaque.PathLength = 0;
+ Opaque.Base = Base;
+ }
Pointer(Block *Pointee, unsigned Base, uint64_t Offset);
explicit Pointer(PtrView V) : Pointer(V.Pointee, V.Base, V.Offset) {}
@@ -514,6 +597,7 @@ class Pointer {
case Storage::Fn:
return !Fn.Func;
case Storage::Typeid:
+ case Storage::Opaque:
return false;
}
llvm_unreachable("Unknown clang::interp::Storage enum");
@@ -581,6 +665,8 @@ class Pointer {
return Fn.Func->getDecl()->getType();
case Storage::Typeid:
return QualType(Typeid.TypeInfoType, 0);
+ case Storage::Opaque:
+ return QualType(Opaque.FieldType.getPointer(), 0);
}
llvm_unreachable("Unhandled StorageKind");
}
@@ -675,11 +761,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 {
@@ -1072,6 +1163,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 378a190184be9..51f945d4d4308 100644
--- a/clang/lib/AST/ByteCode/Program.cpp
+++ b/clang/lib/AST/ByteCode/Program.cpp
@@ -143,14 +143,16 @@ unsigned Program::getOrCreateDummy(DeclTy D, bool IsConstexprUnknown) {
const auto *VD = cast<ValueDecl>(cast<const Decl *>(D));
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/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index 9d69de2a7c6fd..351d69b48cabe 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -23001,7 +23001,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..45d91ba197323 100644
--- a/clang/test/AST/ByteCode/builtin-object-size-codegen.c
+++ b/clang/test/AST/ByteCode/builtin-object-size-codegen.c
@@ -45,3 +45,67 @@ 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);
+}
+
+
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/enable_if.c b/clang/test/AST/ByteCode/enable_if.c
index 8148db2719449..9d81964a45998 100644
--- a/clang/test/AST/ByteCode/enable_if.c
+++ b/clang/test/AST/ByteCode/enable_if.c
@@ -50,7 +50,7 @@ size_t strnlen(const char *s, size_t maxlen)
"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}}
+size_t strnlen(const char *s, size_t maxlen) // both-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")))
@@ -70,7 +70,7 @@ void test2(const char *s, int i) {
strnlen(c, i);
// CHECK: call {{.*}}strnlen_chk
#ifndef CODEGEN
- strnlen(c, 999); // ref-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}}
+ strnlen(c, 999); // both-error{{'strnlen' is unavailable: 'maxlen' is larger than the buffer size}}
#endif
}
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/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
More information about the cfe-commits
mailing list