Skip to content

Commit 2a7c39e

Browse files
committed
[clang] Add support for consteval null terminated strings
Adds support for null terminated strings produced by constexpr evaluation. This makes it possible to perform analysis of format strings that previously were not possible, and is needed in the future to support __ptrauth qualifier options.
1 parent a8f1925 commit 2a7c39e

File tree

7 files changed

+141
-15
lines changed

7 files changed

+141
-15
lines changed

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1746,15 +1746,22 @@ def subst_user_defined_msg : TextSubstitution<
17461746
"%select{the message|the expression}0 in "
17471747
"%select{a static assertion|this asm operand}0">;
17481748

1749-
def err_user_defined_msg_invalid : Error<
1750-
"%sub{subst_user_defined_msg}0 must be a string literal or an "
1751-
"object with 'data()' and 'size()' member functions">;
1749+
def err_user_defined_msg_invalid
1750+
: Error<"%sub{subst_user_defined_msg}0 must be a null terminated constant "
1751+
"string or an "
1752+
"object with 'data()' and 'size()' member functions">;
17521753
def err_user_defined_msg_missing_member_function : Error<
17531754
"the %select{message|string}0 object in "
17541755
"%select{this static assertion|this asm operand}0 is missing %select{"
17551756
"a 'size()' member function|"
17561757
"a 'data()' member function|"
17571758
"'data()' and 'size()' member functions}1">;
1759+
def err_user_defined_msg_not_null_terminated_string
1760+
: Error<"%sub{subst_user_defined_msg}0 is not null terminated">;
1761+
def ext_consteval_string_constants
1762+
: Extension<"consteval string constants are an extension">,
1763+
DefaultWarn,
1764+
InGroup<DiagGroup<"consteval-string-constants-extension">>;
17581765
def err_user_defined_msg_invalid_mem_fn_ret_ty : Error<
17591766
"%sub{subst_user_defined_msg}0 must have a '%select{size|data}1()' member "
17601767
"function returning an object convertible to '%select{std::size_t|const char *}1'">;

clang/include/clang/Sema/Sema.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -831,10 +831,11 @@ enum class CCEKind {
831831
ArrayBound, ///< Array bound in array declarator or new-expression.
832832
ExplicitBool, ///< Condition in an explicit(bool) specifier.
833833
Noexcept, ///< Condition in a noexcept(bool) specifier.
834-
StaticAssertMessageSize, ///< Call to size() in a static assert
835-
///< message.
836-
StaticAssertMessageData, ///< Call to data() in a static assert
837-
///< message.
834+
StaticAssertMessageSize, ///< Call to size() in a static assert
835+
///< message.
836+
StaticAssertMessageData, ///< Call to data() in a static assert
837+
///< message.
838+
StaticAssertNullTerminatedString, ///< tryEvaluateStrLen
838839
};
839840

840841
/// Enums for the diagnostics of target, target_version and target_clones.

clang/lib/AST/ByteCode/Context.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -294,13 +294,15 @@ bool Context::evaluateStrlen(State &Parent, const Expr *E, uint64_t &Result) {
294294
if (!FieldDesc->isPrimitiveArray())
295295
return false;
296296

297-
if (Ptr.isDummy() || Ptr.isUnknownSizeArray())
297+
if (Ptr.isDummy() || Ptr.isUnknownSizeArray() || Ptr.isPastEnd())
298298
return false;
299299

300300
unsigned N = Ptr.getNumElems();
301301
if (Ptr.elemSize() == 1) {
302-
Result = strnlen(reinterpret_cast<const char *>(Ptr.getRawAddress()), N);
303-
return Result != N;
302+
unsigned Size = N - Ptr.getIndex();
303+
Result =
304+
strnlen(reinterpret_cast<const char *>(Ptr.getRawAddress()), Size);
305+
return Result != Size;
304306
}
305307

306308
PrimType ElemT = FieldDesc->getPrimType();

clang/lib/Sema/SemaDeclCXX.cpp

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17693,6 +17693,44 @@ void Sema::DiagnoseStaticAssertDetails(const Expr *E) {
1769317693
}
1769417694
}
1769517695

17696+
template <typename ResultType>
17697+
static bool EvaluateAsNullTerminatedCharBuffer(
17698+
Sema &SemaRef, Expr *Message, ResultType &Result, ASTContext &Ctx,
17699+
Sema::StringEvaluationContext EvalContext, bool ErrorOnInvalidMessage) {
17700+
SourceLocation Loc = Message->getBeginLoc();
17701+
QualType SizeT = Ctx.getSizeType();
17702+
QualType ConstCharPtr = Ctx.getPointerType(Ctx.getConstType(Ctx.CharTy));
17703+
Expr::EvalResult Status;
17704+
SmallVector<PartialDiagnosticAt, 8> Notes;
17705+
Status.Diag = &Notes;
17706+
17707+
auto DiagnoseInvalidConstantString = [&]() {
17708+
SemaRef.Diag(Loc, diag::err_user_defined_msg_not_null_terminated_string)
17709+
<< EvalContext;
17710+
for (const auto &Note : Notes)
17711+
SemaRef.Diag(Note.first, Note.second);
17712+
return !ErrorOnInvalidMessage;
17713+
};
17714+
ExprResult EvaluatedData = SemaRef.BuildConvertedConstantExpression(
17715+
Message, ConstCharPtr, CCEKind::StaticAssertNullTerminatedString);
17716+
if (EvaluatedData.isInvalid())
17717+
return DiagnoseInvalidConstantString();
17718+
17719+
uint64_t Length = 0;
17720+
if (!EvaluatedData.get()->tryEvaluateStrLen(Length, Ctx))
17721+
return DiagnoseInvalidConstantString();
17722+
17723+
llvm::APInt SizeVal(Ctx.getIntWidth(SizeT), Length);
17724+
Expr *SizeExpr = IntegerLiteral::Create(Ctx, SizeVal, SizeT, Loc);
17725+
17726+
bool EvalResult = Message->EvaluateCharRangeAsString(
17727+
Result, SizeExpr, EvaluatedData.get(), Ctx, Status);
17728+
if (!EvalResult || !Notes.empty())
17729+
return DiagnoseInvalidConstantString();
17730+
SemaRef.Diag(Loc, diag::ext_consteval_string_constants);
17731+
return true;
17732+
}
17733+
1769617734
template <typename ResultType>
1769717735
static bool EvaluateAsStringImpl(Sema &SemaRef, Expr *Message,
1769817736
ResultType &Result, ASTContext &Ctx,
@@ -17726,6 +17764,10 @@ static bool EvaluateAsStringImpl(Sema &SemaRef, Expr *Message,
1772617764

1772717765
SourceLocation Loc = Message->getBeginLoc();
1772817766
QualType T = Message->getType().getNonReferenceType();
17767+
if (T->isPointerType() && T->getPointeeType()->isCharType())
17768+
return EvaluateAsNullTerminatedCharBuffer(
17769+
SemaRef, Message, Result, Ctx, EvalContext, ErrorOnInvalidMessage);
17770+
1772917771
auto *RD = T->getAsCXXRecordDecl();
1773017772
if (!RD) {
1773117773
SemaRef.Diag(Loc, diag::err_user_defined_msg_invalid) << EvalContext;

clang/test/Parser/asm.cpp

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ struct string_view {
3535
int foo1 asm ((string_view("test"))); // expected-error {{expected string literal in 'asm'}}
3636
int func() asm ((string_view("test"))); // expected-error {{expected string literal in 'asm'}}
3737

38+
constexpr const char* getConstantString(const char* s) {
39+
return s;
40+
}
3841

3942
void f2() {
4043
asm(string_view("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
@@ -44,6 +47,13 @@ void f2() {
4447
asm("" :: string_view("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
4548
asm(::string_view("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
4649

50+
asm(getConstantString("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
51+
asm("" : getConstantString("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
52+
asm("" : : getConstantString("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
53+
asm("" : : : getConstantString("")); // expected-error {{expected ')'}}
54+
asm("" :: getConstantString("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
55+
asm(::getConstantString("")); // expected-error {{expected string literal or parenthesized constant expression in 'asm'}}
56+
4757
int i;
4858

4959
asm((string_view("")));
@@ -55,5 +65,22 @@ void f2() {
5565
asm("" : (::string_view("+g")) (i) : (::string_view("g")) (0) : (string_view("memory")));
5666

5767

58-
asm((0)); // expected-error {{the expression in this asm operand must be a string literal or an object with 'data()' and 'size()' member functions}}
68+
asm((getConstantString("")));
69+
// expected-warning@-1 {{consteval string constants are an extension}}
70+
asm((::getConstantString("")));
71+
// expected-warning@-1 {{consteval string constants are an extension}}
72+
asm("" : (::getConstantString("+g")) (i));
73+
// expected-warning@-1 {{consteval string constants are an extension}}
74+
asm("" : (::getConstantString("+g"))); // expected-error {{expected '(' after 'asm operand'}}
75+
// expected-warning@-1 {{consteval string constants are an extension}}
76+
asm("" : (::getConstantString("+g")) (i) : (::getConstantString("g")) (0));
77+
// expected-warning@-1 2 {{consteval string constants are an extension}}
78+
asm("" : (::getConstantString("+g")) (i) : (::getConstantString("g"))); // expected-error {{expected '(' after 'asm operand'}}
79+
// expected-warning@-1 2 {{consteval string constants are an extension}}
80+
asm("" : (::getConstantString("+g")) (i) : (::getConstantString("g")) (0) : (getConstantString("memory")));
81+
// expected-warning@-1 3 {{consteval string constants are an extension}}
82+
83+
84+
85+
asm((0)); // expected-error {{the expression in this asm operand must be a null terminated constant string or an object with 'data()' and 'size()' member functions}}
5986
}

clang/test/SemaCXX/gnu-asm-constexpr.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ struct string_view {
7777

7878

7979
void f() {
80-
asm(("")); // expected-error {{the expression in this asm operand must be a string literal or an object with 'data()' and 'size()' member functions}}
80+
asm(("")); // expected-error {{the expression in this asm operand must be a null terminated constant string or an object with 'data()' and 'size()' member functions}}
8181
asm((NotAString{})); // expected-error {{the string object in this asm operand is missing 'data()' and 'size()' member functions}}
8282
asm((MessageInvalidData{})); // expected-error {{the expression in this asm operand must have a 'data()' member function returning an object convertible to 'const char *'}} \
8383
// expected-error {{too few arguments to function call, expected 1, have 0}}
@@ -106,7 +106,7 @@ void test_dependent1(int i) {
106106

107107
template void test_dependent1<int>(int);
108108
// expected-note@-1 {{in instantiation of function template specialization}}
109-
// expected-error@#err-int {{the expression in this asm operand must be a string literal or an object with 'data()' and 'size()' member functions}}
109+
// expected-error@#err-int {{the expression in this asm operand must be a null terminated constant string or an object with 'data()' and 'size()' member functions}}
110110
// expected-error@#err-int2 {{cannot initialize a value of type 'int' with an lvalue of type 'const char[3]'}}
111111
// expected-error@#err-int3 {{cannot initialize a value of type 'int' with an lvalue of type 'const char[2]'}}
112112
// expected-error@#err-int4 {{cannot initialize a value of type 'int' with an lvalue of type 'const char[7]'}}

clang/test/SemaCXX/static-assert-cxx26.cpp

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
// RUN: %clang_cc1 -std=c++2c -triple=x86_64-linux -fsyntax-only %s -verify -fexperimental-new-constant-interpreter
33

44
static_assert(true, "");
5-
static_assert(true, 0); // expected-error {{the message in a static assertion must be a string literal or an object with 'data()' and 'size()' member functions}}
5+
static_assert(true, 0); // expected-error {{the message in a static assertion must be a null terminated constant string or an object with 'data()' and 'size()' member functions}}
66
struct Empty{};
77
static_assert(true, Empty{}); // expected-error {{the message object in this static assertion is missing 'data()' and 'size()' member functions}}
88
struct NoData {
@@ -288,7 +288,7 @@ struct Good {
288288

289289
template <typename Ty>
290290
struct Bad {
291-
static_assert(false, Ty{}); // expected-error {{the message in a static assertion must be a string literal or an object with 'data()' and 'size()' member functions}} \
291+
static_assert(false, Ty{}); // expected-error {{the message in a static assertion must be a null terminated constant string or an object with 'data()' and 'size()' member functions}} \
292292
// expected-error {{static assertion failed}}
293293
};
294294

@@ -416,3 +416,50 @@ static_assert(
416416
// expected-note@-1 {{read of dereferenced one-past-the-end pointer is not allowed in a constant expression}}
417417
);
418418
}
419+
420+
static_assert(false, &(" basic test"[1]));
421+
// expected-error@-1 {{static assertion failed: basic test}}
422+
// expected-warning@-2 {{consteval string constants are an extension}}
423+
424+
constexpr const char *constexpr_global = "global_constexpr";
425+
constexpr const char null_terminated_buffer[] = { 'n', 'u', 'l', 'l', 't', 'e', 'r', 'm', 0 };
426+
constexpr const char no_null_buffer[] = { 'n', 'o', 'n', 'u', 'l', 'l', 't', 'e', 'r', 'm' };
427+
428+
constexpr const char *selector(int i) {
429+
constexpr const char * a_constant = "a_constant";
430+
const char *non_constexpr = "non-constexpr string";
431+
switch (i) {
432+
case 0: return "case 0";
433+
case 1: return a_constant;
434+
case 2: return constexpr_global;
435+
case 3: return null_terminated_buffer;
436+
case 4: return &(""[1]); // point to after the null terminator
437+
case 5: return nullptr;
438+
case 6: return no_null_buffer;
439+
}
440+
};
441+
442+
static_assert(false, selector(0));
443+
// expected-error@-1 {{static assertion failed: case 0}}
444+
// expected-warning@-2 {{consteval string constants are an extension}}
445+
static_assert(false, selector(1));
446+
// expected-error@-1 {{static assertion failed: a_constant}}
447+
// expected-warning@-2 {{consteval string constants are an extension}}
448+
static_assert(false, selector(2));
449+
// expected-error@-1 {{static assertion failed: global_constexpr}}
450+
// expected-warning@-2 {{consteval string constants are an extension}}
451+
static_assert(false, selector(3));
452+
// expected-error@-1 {{static assertion failed: nullterm}}
453+
// expected-warning@-2 {{consteval string constants are an extension}}
454+
static_assert(false, selector(4));
455+
// expected-error@-1 {{the message in a static assertion is not null terminated}}
456+
// expected-error@-2 {{static assertion failed}}
457+
static_assert(false, selector(5));
458+
// expected-error@-1 {{the message in a static assertion is not null terminated}}
459+
// expected-error@-2 {{static assertion failed}}
460+
static_assert(false, selector(6));
461+
// expected-error@-1 {{the message in a static assertion is not null terminated}}
462+
// expected-error@-2 {{static assertion failed}}
463+
static_assert(false, selector(7));
464+
// expected-error@-1 {{the message in a static assertion is not null terminated}}
465+
// expected-error@-2 {{static assertion failed}}

0 commit comments

Comments
 (0)