[clang] [Clang] Don't mutate string literal initializers. (PR #234691)
Chris Carlon via cfe-commits
cfe-commits at lists.llvm.org
Thu Oct 22 09:51:02 PDT 2026
https://github.com/cjc25 created https://github.com/llvm/llvm-project/pull/234691
When a string literal initializes a character array, Clang updates the initializer expression's type to the target array type. Previously, this was done by mutating the existing AST nodes in place.
Because template instantiation reuses non-dependent string literal AST nodes across instantiations, mutating the shared node in place caused earlier or later instantiations of the same template to observe the wrong array bound, type qualifier, or value category. Depending on instantiation order, this led to rejecting valid instantiations, crashing during constant emission, or silently emitting out-of-bounds copies.
Fix this during array initialization by building new string literal (and any surrounding wrapper) expressions with the adjusted array type rather than modifying existing AST nodes in place. This preserves the original string literal expression for other template instantiations, while CodeGen continues to pool identical constant arrays.
Fixes #72738
Fixes #112189
>From a761f284be378bca0935d8b82220356447b53bf7 Mon Sep 17 00:00:00 2001
From: Chris Carlon <carlon at google.com>
Date: Tue, 20 Oct 2026 12:39:47 -0400
Subject: [PATCH] [Clang] Don't mutate string literal initializers.
When a string literal initializes a character array, Clang updates the
initializer expression's type to the target array type. Previously, this
was done by mutating the existing AST nodes in place.
Because template instantiation reuses non-dependent string literal AST
nodes across instantiations, mutating the shared node in place caused
earlier or later instantiations of the same template to observe the
wrong array bound, type qualifier, or value category. Depending on
instantiation order, this led to rejecting valid instantiations,
crashing during constant emission, or silently emitting out-of-bounds
copies.
Fix this during array initialization by building new string literal (and
any surrounding wrapper) expressions with the adjusted array type rather
than modifying existing AST nodes in place. This preserves the original
string literal expression for other template instantiations, while
CodeGen continues to pool identical constant arrays.
Fixes #72738
Fixes #112189
---
clang/docs/ReleaseNotes.md | 5 ++
clang/include/clang/AST/IgnoreExpr.h | 88 +++++++++++++++-----
clang/lib/Sema/SemaInit.cpp | 58 +++++++------
clang/lib/Sema/SemaPseudoObject.cpp | 86 +++----------------
clang/test/CodeGenCXX/instantiate-init.cpp | 45 ++++++++++
clang/test/SemaTemplate/instantiate-init.cpp | 25 ++++++
clang/unittests/AST/ASTExprTest.cpp | 41 +++++++++
7 files changed, 231 insertions(+), 117 deletions(-)
diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md
index 79de46f09a8352..415349ff8a6cf2 100644
--- a/clang/docs/ReleaseNotes.md
+++ b/clang/docs/ReleaseNotes.md
@@ -1049,6 +1049,11 @@ features cannot lower the translation-unit ABI level;
whose parameter type uses pack indexing, and makes such valid matches
succeed instead of being rejected. (#GH232220)
+- Fixed a bug where initializing a character array from a string literal in a
+ template mutated the shared `StringLiteral` AST node in place, causing other
+ instantiations of the template to use the wrong type or value category.
+ (#GH72738, #GH112189)
+
#### Bug Fixes to AST Handling
- Fixed a non-deterministic ordering of unused local typedefs that made
diff --git a/clang/include/clang/AST/IgnoreExpr.h b/clang/include/clang/AST/IgnoreExpr.h
index 3b50b8115c6b85..843cc3f5e45310 100644
--- a/clang/include/clang/AST/IgnoreExpr.h
+++ b/clang/include/clang/AST/IgnoreExpr.h
@@ -154,31 +154,81 @@ inline Expr *IgnoreParensOnlySingleStep(Expr *E) {
return E;
}
-inline Expr *IgnoreParensSingleStep(Expr *E) {
- if (auto *PE = dyn_cast<ParenExpr>(E))
- return PE->getSubExpr();
-
- if (auto *UO = dyn_cast<UnaryOperator>(E)) {
- if (UO->getOpcode() == UO_Extension)
- return UO->getSubExpr();
+template <typename StepFn> Expr *MapParensSingleStep(Expr *E, StepFn &&Step) {
+ if (auto *PE = dyn_cast<ParenExpr>(E)) {
+ return Step(PE->getSubExpr(), [&](ASTContext &Ctx, Expr *Sub) {
+ return new (Ctx) ParenExpr(PE->getLParen(), PE->getRParen(), Sub);
+ });
}
- else if (auto *GSE = dyn_cast<GenericSelectionExpr>(E)) {
- if (!GSE->isResultDependent())
- return GSE->getResultExpr();
+ if (auto *UO = dyn_cast<UnaryOperator>(E)) {
+ if (UO->getOpcode() == UO_Extension) {
+ return Step(UO->getSubExpr(), [&](ASTContext &Ctx, Expr *Sub) {
+ return UnaryOperator::Create(Ctx, Sub, UO->getOpcode(), Sub->getType(),
+ Sub->getValueKind(), Sub->getObjectKind(),
+ UO->getOperatorLoc(), UO->canOverflow(),
+ UO->getFPOptionsOverride());
+ });
+ }
+ } else if (auto *GSE = dyn_cast<GenericSelectionExpr>(E)) {
+ if (!GSE->isResultDependent()) {
+ return Step(GSE->getResultExpr(), [&](ASTContext &Ctx, Expr *Sub) {
+ SmallVector<Expr *, 4> AssocExprs(GSE->getAssocExprs());
+ unsigned ResultIdx = GSE->getResultIndex();
+ AssocExprs[ResultIdx] = Sub;
+ if (GSE->isExprPredicate())
+ return GenericSelectionExpr::Create(
+ Ctx, GSE->getGenericLoc(), GSE->getControllingExpr(),
+ GSE->getAssocTypeSourceInfos(), AssocExprs, GSE->getDefaultLoc(),
+ GSE->getRParenLoc(), GSE->containsUnexpandedParameterPack(),
+ ResultIdx);
+ return GenericSelectionExpr::Create(
+ Ctx, GSE->getGenericLoc(), GSE->getControllingType(),
+ GSE->getAssocTypeSourceInfos(), AssocExprs, GSE->getDefaultLoc(),
+ GSE->getRParenLoc(), GSE->containsUnexpandedParameterPack(),
+ ResultIdx);
+ });
+ }
+ } else if (auto *CE = dyn_cast<ChooseExpr>(E)) {
+ if (!CE->isConditionDependent()) {
+ return Step(CE->getChosenSubExpr(), [&](ASTContext &Ctx, Expr *Sub) {
+ Expr *LHS = CE->isConditionTrue() ? Sub : CE->getLHS();
+ Expr *RHS = CE->isConditionTrue() ? CE->getRHS() : Sub;
+ return new (Ctx) ChooseExpr(CE->getBuiltinLoc(), CE->getCond(), LHS,
+ RHS, Sub->getType(), Sub->getValueKind(),
+ Sub->getObjectKind(), CE->getRParenLoc(),
+ CE->isConditionTrue());
+ });
+ }
+ } else if (auto *PE = dyn_cast<PredefinedExpr>(E)) {
+ if (PE->isTransparent() && PE->getFunctionName()) {
+ return Step(PE->getFunctionName(), [&](ASTContext &Ctx, Expr *Sub) {
+ auto *Result = PredefinedExpr::Create(
+ Ctx, PE->getLocation(), Sub->getType(), PE->getIdentKind(),
+ PE->isTransparent(), cast<StringLiteral>(Sub));
+ Result->setValueKind(Sub->getValueKind());
+ Result->setObjectKind(Sub->getObjectKind());
+ return Result;
+ });
+ }
}
- else if (auto *CE = dyn_cast<ChooseExpr>(E)) {
- if (!CE->isConditionDependent())
- return CE->getChosenSubExpr();
- }
+ return E;
+}
- else if (auto *PE = dyn_cast<PredefinedExpr>(E)) {
- if (PE->isTransparent() && PE->getFunctionName())
- return PE->getFunctionName();
- }
+inline Expr *IgnoreParensSingleStep(Expr *E) {
+ return MapParensSingleStep(E, [](Expr *Sub, auto &&) { return Sub; });
+}
- return E;
+template <typename LeafFn>
+Expr *RebuildParens(ASTContext &Ctx, Expr *E, LeafFn &&RebuildLeaf) {
+ bool IsParen = false;
+ Expr *Rebuilt =
+ MapParensSingleStep(E, [&](Expr *Sub, auto &&RebuildWrapper) -> Expr * {
+ IsParen = true;
+ return RebuildWrapper(Ctx, RebuildParens(Ctx, Sub, RebuildLeaf));
+ });
+ return IsParen ? Rebuilt : RebuildLeaf(E);
}
} // namespace clang
diff --git a/clang/lib/Sema/SemaInit.cpp b/clang/lib/Sema/SemaInit.cpp
index 1cea790da1d27f..f567aaa11cee8f 100644
--- a/clang/lib/Sema/SemaInit.cpp
+++ b/clang/lib/Sema/SemaInit.cpp
@@ -170,16 +170,26 @@ bool Sema::IsStringInit(Expr *Init, const ArrayType *AT) {
return ::IsStringInit(Init, AT, Context) == SIF_None;
}
-/// Update the type of a string literal, including any surrounding parentheses,
-/// to match the type of the object which it is initializing.
-static void updateStringLiteralType(Expr *E, QualType Ty) {
- while (true) {
- E->setType(Ty);
- E->setValueKind(VK_PRValue);
- if (isa<StringLiteral>(E) || isa<ObjCEncodeExpr>(E))
- break;
- E = IgnoreParensSingleStep(E);
- }
+/// Rebuild a string literal, including any surrounding parentheses or wrapper
+/// expressions, to match the type of the object which it is initializing
+/// without mutating existing AST nodes in place.
+static Expr *updateStringLiteralType(Expr *E, QualType Ty,
+ ASTContext &Context) {
+ return RebuildParens(Context, E, [&](Expr *Leaf) -> Expr * {
+ if (auto *SL = dyn_cast<StringLiteral>(Leaf)) {
+ auto *Result = StringLiteral::Create(
+ Context, SL->getBytes(), SL->getKind(), SL->isPascal(), Ty,
+ llvm::ArrayRef(SL->tokloc_begin(), SL->tokloc_end()));
+ Result->setValueKind(VK_PRValue);
+ return Result;
+ }
+ auto *OEE = cast<ObjCEncodeExpr>(Leaf);
+ auto *Result =
+ new (Context) ObjCEncodeExpr(Ty, OEE->getEncodedTypeSourceInfo(),
+ OEE->getAtLoc(), OEE->getRParenLoc());
+ Result->setValueKind(VK_PRValue);
+ return Result;
+ });
}
/// Fix a compound literal initializing an array so it's correctly marked
@@ -211,7 +221,7 @@ static bool initializingConstexprVariable(const InitializedEntity &Entity) {
static void CheckC23ConstexprInitStringLiteral(const StringLiteral *SE,
Sema &SemaRef, QualType &TT);
-static void CheckStringInit(Expr *Str, QualType &DeclT, const ArrayType *AT,
+static void CheckStringInit(Expr *&Str, QualType &DeclT, const ArrayType *AT,
Sema &S, const InitializedEntity &Entity,
bool CheckC23ConstexprInit = false) {
// Get the length of the string as parsed.
@@ -230,7 +240,7 @@ static void CheckStringInit(Expr *Str, QualType &DeclT, const ArrayType *AT,
// Return a new array type (C99 6.7.8p22).
DeclT = S.Context.getConstantArrayType(
IAT->getElementType(), ConstVal, nullptr, ArraySizeModifier::Normal, 0);
- updateStringLiteralType(Str, DeclT);
+ Str = updateStringLiteralType(Str, DeclT, S.Context);
return;
}
@@ -304,7 +314,7 @@ static void CheckStringInit(Expr *Str, QualType &DeclT, const ArrayType *AT,
// something like:
// char x[1] = "foo";
// then this will set the string literal's type to char[1].
- updateStringLiteralType(Str, DeclT);
+ Str = updateStringLiteralType(Str, DeclT, S.Context);
}
void emitUninitializedExplicitInitFields(Sema &S, const RecordDecl *R) {
@@ -2158,8 +2168,8 @@ void InitListChecker::CheckArrayType(const InitializedEntity &Entity,
// Check for the special-case of initializing an array with a string.
if (Index < IList->getNumInits()) {
- if (IsStringInit(IList->getInit(Index), arrayType, SemaRef.Context) ==
- SIF_None) {
+ Expr *Str = IList->getInit(Index);
+ if (IsStringInit(Str, arrayType, SemaRef.Context) == SIF_None) {
// We place the string literal directly into the resulting
// initializer list. This is the only place where the structure
// of the structured initializer list doesn't match exactly,
@@ -2167,12 +2177,11 @@ void InitListChecker::CheckArrayType(const InitializedEntity &Entity,
// constant for each string.
// FIXME: Should we do these checks in verify-only mode too?
if (!VerifyOnly)
- CheckStringInit(
- IList->getInit(Index), DeclType, arrayType, SemaRef, Entity,
- SemaRef.getLangOpts().C23 && initializingConstexprVariable(Entity));
+ CheckStringInit(Str, DeclType, arrayType, SemaRef, Entity,
+ SemaRef.getLangOpts().C23 &&
+ initializingConstexprVariable(Entity));
if (StructuredList) {
- UpdateStructuredListElement(StructuredList, StructuredIndex,
- IList->getInit(Index));
+ UpdateStructuredListElement(StructuredList, StructuredIndex, Str);
StructuredList->resizeInits(SemaRef.Context, StructuredIndex);
}
++Index;
@@ -8622,10 +8631,11 @@ ExprResult InitializationSequence::Perform(Sema &S,
case SK_StringInit: {
QualType Ty = Step->Type;
bool UpdateType = ResultType && Entity.getType()->isIncompleteArrayType();
- CheckStringInit(CurInit.get(), UpdateType ? *ResultType : Ty,
- S.Context.getAsArrayType(Ty), S, Entity,
- S.getLangOpts().C23 &&
- initializingConstexprVariable(Entity));
+ Expr *Str = CurInit.get();
+ CheckStringInit(
+ Str, UpdateType ? *ResultType : Ty, S.Context.getAsArrayType(Ty), S,
+ Entity, S.getLangOpts().C23 && initializingConstexprVariable(Entity));
+ CurInit = Str;
break;
}
diff --git a/clang/lib/Sema/SemaPseudoObject.cpp b/clang/lib/Sema/SemaPseudoObject.cpp
index 66736670984efb..aac52a6c49b6ab 100644
--- a/clang/lib/Sema/SemaPseudoObject.cpp
+++ b/clang/lib/Sema/SemaPseudoObject.cpp
@@ -32,6 +32,7 @@
#include "clang/Sema/SemaPseudoObject.h"
#include "clang/AST/ExprCXX.h"
#include "clang/AST/ExprObjC.h"
+#include "clang/AST/IgnoreExpr.h"
#include "clang/Basic/CharInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Initialization.h"
@@ -104,80 +105,17 @@ namespace {
}
Expr *rebuild(Expr *e) {
- // Fast path: nothing to look through.
- if (auto *PRE = dyn_cast<ObjCPropertyRefExpr>(e))
- return rebuildObjCPropertyRefExpr(PRE);
- if (auto *SRE = dyn_cast<ObjCSubscriptRefExpr>(e))
- return rebuildObjCSubscriptRefExpr(SRE);
- if (auto *MSPRE = dyn_cast<MSPropertyRefExpr>(e))
- return rebuildMSPropertyRefExpr(MSPRE);
- if (auto *MSPSE = dyn_cast<MSPropertySubscriptExpr>(e))
- return rebuildMSPropertySubscriptExpr(MSPSE);
-
- // Otherwise, we should look through and rebuild anything that
- // IgnoreParens would.
-
- if (ParenExpr *parens = dyn_cast<ParenExpr>(e)) {
- e = rebuild(parens->getSubExpr());
- return new (S.Context) ParenExpr(parens->getLParen(),
- parens->getRParen(),
- e);
- }
-
- if (UnaryOperator *uop = dyn_cast<UnaryOperator>(e)) {
- assert(uop->getOpcode() == UO_Extension);
- e = rebuild(uop->getSubExpr());
- return UnaryOperator::Create(
- S.Context, e, uop->getOpcode(), uop->getType(), uop->getValueKind(),
- uop->getObjectKind(), uop->getOperatorLoc(), uop->canOverflow(),
- S.CurFPFeatureOverrides());
- }
-
- if (GenericSelectionExpr *gse = dyn_cast<GenericSelectionExpr>(e)) {
- assert(!gse->isResultDependent());
- unsigned resultIndex = gse->getResultIndex();
- unsigned numAssocs = gse->getNumAssocs();
-
- SmallVector<Expr *, 8> assocExprs;
- SmallVector<TypeSourceInfo *, 8> assocTypes;
- assocExprs.reserve(numAssocs);
- assocTypes.reserve(numAssocs);
-
- for (const GenericSelectionExpr::Association assoc :
- gse->associations()) {
- Expr *assocExpr = assoc.getAssociationExpr();
- if (assoc.isSelected())
- assocExpr = rebuild(assocExpr);
- assocExprs.push_back(assocExpr);
- assocTypes.push_back(assoc.getTypeSourceInfo());
- }
-
- if (gse->isExprPredicate())
- return GenericSelectionExpr::Create(
- S.Context, gse->getGenericLoc(), gse->getControllingExpr(),
- assocTypes, assocExprs, gse->getDefaultLoc(), gse->getRParenLoc(),
- gse->containsUnexpandedParameterPack(), resultIndex);
- return GenericSelectionExpr::Create(
- S.Context, gse->getGenericLoc(), gse->getControllingType(),
- assocTypes, assocExprs, gse->getDefaultLoc(), gse->getRParenLoc(),
- gse->containsUnexpandedParameterPack(), resultIndex);
- }
-
- if (ChooseExpr *ce = dyn_cast<ChooseExpr>(e)) {
- assert(!ce->isConditionDependent());
-
- Expr *LHS = ce->getLHS(), *RHS = ce->getRHS();
- Expr *&rebuiltExpr = ce->isConditionTrue() ? LHS : RHS;
- rebuiltExpr = rebuild(rebuiltExpr);
-
- return new (S.Context)
- ChooseExpr(ce->getBuiltinLoc(), ce->getCond(), LHS, RHS,
- rebuiltExpr->getType(), rebuiltExpr->getValueKind(),
- rebuiltExpr->getObjectKind(), ce->getRParenLoc(),
- ce->isConditionTrue());
- }
-
- llvm_unreachable("bad expression to rebuild!");
+ return RebuildParens(S.Context, e, [&](Expr *e) {
+ if (auto *PRE = dyn_cast<ObjCPropertyRefExpr>(e))
+ return rebuildObjCPropertyRefExpr(PRE);
+ if (auto *SRE = dyn_cast<ObjCSubscriptRefExpr>(e))
+ return rebuildObjCSubscriptRefExpr(SRE);
+ if (auto *MSPRE = dyn_cast<MSPropertyRefExpr>(e))
+ return rebuildMSPropertyRefExpr(MSPRE);
+ if (auto *MSPSE = dyn_cast<MSPropertySubscriptExpr>(e))
+ return rebuildMSPropertySubscriptExpr(MSPSE);
+ llvm_unreachable("bad expression to rebuild!");
+ });
}
};
diff --git a/clang/test/CodeGenCXX/instantiate-init.cpp b/clang/test/CodeGenCXX/instantiate-init.cpp
index d303938c0984db..b43d7521c63854 100644
--- a/clang/test/CodeGenCXX/instantiate-init.cpp
+++ b/clang/test/CodeGenCXX/instantiate-init.cpp
@@ -27,3 +27,48 @@ namespace ParenBraceInitList {
}
template void f<0>();
}
+
+namespace StringLiteralInit {
+ template <unsigned L> struct V {
+ char dep[L] = "foo";
+ char nondep[4] = "bar";
+ V() {}
+ };
+ template <unsigned N> void braced() { char buf[sizeof("foo") + N]{"foo"}; }
+ void test() {
+ V<4> v1;
+ V<10> v2;
+ braced<0>();
+ braced<1>();
+ }
+
+ const char *G;
+ template <typename T> void f() { T v = "foo"; G = v; }
+ template void f<char[]>();
+ template void f<const char *>();
+
+ // CHECK-LABEL: define weak_odr void @_ZN17StringLiteralInit1fIA_cEEvv()
+ // CHECK: %v = alloca [4 x i8], align 1
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %v, ptr align 1 @__const._ZN17StringLiteralInit1fIA_cEEvv.v, i64 4, i1 false)
+
+ // CHECK-LABEL: define weak_odr void @_ZN17StringLiteralInit1fIPKcEEvv()
+ // CHECK-NOT: alloca [4 x i8]
+ // CHECK: store ptr @.str, ptr %v, align 8
+ // CHECK: ret void
+
+ // CHECK-LABEL: define linkonce_odr void @_ZN17StringLiteralInit6bracedILj0EEEvv()
+ // CHECK: %buf = alloca [4 x i8], align 1
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %buf, ptr align 1 @__const._ZN17StringLiteralInit6bracedILj0EEEvv.buf, i64 4, i1 false)
+
+ // CHECK-LABEL: define linkonce_odr void @_ZN17StringLiteralInit6bracedILj1EEEvv()
+ // CHECK: %buf = alloca [5 x i8], align 1
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %buf, ptr align 1 @__const._ZN17StringLiteralInit6bracedILj1EEEvv.buf, i64 5, i1 false)
+
+ // CHECK-LABEL: define linkonce_odr void @_ZN17StringLiteralInit1VILj4EEC2Ev(
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %dep, ptr align 1 @.str, i64 4, i1 false)
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %nondep, ptr align 1 [[BAR:@.str.[0-9]+]], i64 4, i1 false)
+
+ // CHECK-LABEL: define linkonce_odr void @_ZN17StringLiteralInit1VILj10EEC2Ev(
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %dep, ptr align 1 @.str.{{[0-9]+}}, i64 10, i1 false)
+ // CHECK: call void @llvm.memcpy.p0.p0.i64(ptr align 1 %nondep, ptr align 1 [[BAR]], i64 4, i1 false)
+}
diff --git a/clang/test/SemaTemplate/instantiate-init.cpp b/clang/test/SemaTemplate/instantiate-init.cpp
index 5fc3e83114e28e..57d3dc938f945d 100644
--- a/clang/test/SemaTemplate/instantiate-init.cpp
+++ b/clang/test/SemaTemplate/instantiate-init.cpp
@@ -178,3 +178,28 @@ namespace RebuildStdInitList {
template<typename U> void f() { PES({1, 2, 3}); }
void g() { f<int>(); }
}
+
+namespace StringLiteralDefaultInit {
+ template <unsigned L> struct V {
+ char rest[L] = "at least 18 bytes";
+ char paren[L] = ("foo");
+ char ext[L] = __extension__("foo");
+ char gen[L] = _Generic(0, int: "foo");
+ char choose[L] = __builtin_choose_expr(1, "foo", "bar");
+ struct Inner { char s[L]; } inner = {"foo"};
+ };
+
+ template <unsigned L> void braced() { char buf[L]{"foo"}; }
+
+ const char *G;
+ template <typename T> void f() { T v = "foo"; G = v; }
+ template void f<char[]>();
+ template void f<const char *>();
+
+ void test() {
+ V<100> v1;
+ V<18> v2;
+ braced<5>();
+ braced<4>();
+ }
+}
diff --git a/clang/unittests/AST/ASTExprTest.cpp b/clang/unittests/AST/ASTExprTest.cpp
index 5c9e7928bb804f..eccc01bfa58407 100644
--- a/clang/unittests/AST/ASTExprTest.cpp
+++ b/clang/unittests/AST/ASTExprTest.cpp
@@ -87,6 +87,47 @@ TEST(ASTExpr, IgnoreExprCallbackForwarded) {
}
}
+TEST(ASTExpr, RebuildParens) {
+ auto AST = buildASTFromCodeWithArgs(R"c(
+ void f(void) {
+ const char *p = (__extension__ _Generic(
+ 0, int: __builtin_choose_expr(1, (__FUNCTION__), "")));
+ }
+ )c",
+ {"-xc", "-std=c11", "-fms-extensions"});
+ ASTContext &Ctx = AST->getASTContext();
+ Expr *Orig = const_cast<Expr *>(getVariableNode(AST.get(), "p")->getInit())
+ ->IgnoreImpCasts();
+ QualType OrigTy = Orig->getType();
+ QualType NewTy = Ctx.getConstantArrayType(
+ Ctx.CharTy, llvm::APInt(32, 16), nullptr, ArraySizeModifier::Normal, 0);
+
+ Expr *Rebuilt = RebuildParens(Ctx, Orig, [&](Expr *Leaf) {
+ auto *SL = cast<StringLiteral>(Leaf);
+ auto *NewSL = StringLiteral::Create(
+ Ctx, SL->getBytes(), SL->getKind(), SL->isPascal(), NewTy,
+ llvm::ArrayRef(SL->tokloc_begin(), SL->tokloc_end()));
+ NewSL->setValueKind(VK_PRValue);
+ return NewSL;
+ });
+
+ unsigned Steps = 0;
+ for (Expr *O = Orig, *R = Rebuilt;;
+ O = IgnoreParensSingleStep(O), R = IgnoreParensSingleStep(R), ++Steps) {
+ EXPECT_NE(R, O);
+ EXPECT_EQ(R->getStmtClass(), O->getStmtClass());
+ EXPECT_EQ(R->getType(), NewTy);
+ EXPECT_EQ(R->getValueKind(), VK_PRValue);
+ EXPECT_EQ(O->getType(), OrigTy);
+ EXPECT_EQ(O->getValueKind(), VK_LValue);
+ if (IgnoreParensSingleStep(O) == O) {
+ EXPECT_EQ(IgnoreParensSingleStep(R), R);
+ break;
+ }
+ }
+ EXPECT_EQ(Steps, 6u);
+}
+
TEST(ASTExpr, InitListIsConstantInitialized) {
auto AST = buildASTFromCode(R"cpp(
struct Empty {};
More information about the cfe-commits
mailing list