[clang] [clang][AST] Add `StringLiteral::findZeroCharUnit()` (PR #218601)
Timm Baeder via cfe-commits
cfe-commits at lists.llvm.org
Mon Aug 24 23:53:48 PDT 2026
https://github.com/tbaederr created https://github.com/llvm/llvm-project/pull/218601
Which can be used to implement strlen-like functionality.
>From d1a9be9afc4e9ab1f91cce8da47ac4f4bfd2a0d9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Timm=20B=C3=A4der?= <tbaeder at redhat.com>
Date: Tue, 25 Aug 2026 06:20:16 +0200
Subject: [PATCH] sdf
---
clang/include/clang/AST/Expr.h | 9 ++++
clang/lib/AST/Expr.cpp | 22 ++++++++
clang/lib/AST/ExprConstant.cpp | 26 ++++-----
clang/unittests/AST/CMakeLists.txt | 1 +
clang/unittests/AST/StringLiteral.cpp | 78 +++++++++++++++++++++++++++
5 files changed, 120 insertions(+), 16 deletions(-)
create mode 100644 clang/unittests/AST/StringLiteral.cpp
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 72762c668f26a..2a88b0e0d663c 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -1925,6 +1925,15 @@ class StringLiteral final
return V;
}
+ /// Scan the string literal contents for a code unit with value 0.
+ /// If \p StartIndex is outside of the length of the string, this returns \c
+ /// std::nullopt.
+ ///
+ /// Otherwise, returns the offset (in code units, not bytes) of the zero code
+ /// unit, starting at index \p StartIndex. If no such code unit could be
+ /// found, this returns `getLength() - StartIndex`.
+ UnsignedOrNone findZeroCodeUnit(unsigned StartIndex = 0) const;
+
unsigned getByteLength() const { return getCharByteWidth() * getLength(); }
unsigned getLength() const { return *getTrailingObjects<unsigned>(); }
unsigned getCharByteWidth() const { return StringLiteralBits.CharByteWidth; }
diff --git a/clang/lib/AST/Expr.cpp b/clang/lib/AST/Expr.cpp
index e501527ed9b04..6ce0a29aa3bd7 100644
--- a/clang/lib/AST/Expr.cpp
+++ b/clang/lib/AST/Expr.cpp
@@ -1407,6 +1407,28 @@ StringLiteral::getLocationOfByte(unsigned ByteNo, const SourceManager &SM,
}
}
+UnsignedOrNone StringLiteral::findZeroCodeUnit(unsigned StartIndex) const {
+ unsigned Length = getLength();
+ if (StartIndex > Length)
+ return std::nullopt;
+
+ if (getCharByteWidth() == 1) {
+ StringRef::size_type Pos = getString().substr(StartIndex).find('\0');
+ if (Pos == StringRef::npos)
+ return Length - StartIndex;
+ return Pos;
+ }
+
+ unsigned Result = 0;
+ for (unsigned I = StartIndex; I != Length; ++I) {
+ if (getCodeUnit(I) == 0)
+ break;
+ ++Result;
+ }
+
+ return Result;
+}
+
/// getOpcodeStr - Turn an Opcode enum value into the punctuation char it
/// corresponds to, e.g. "sizeof" or "[pre]++".
StringRef UnaryOperator::getOpcodeStr(Opcode Op) {
diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp
index d55749100658f..23b80e1dc6bec 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -23119,31 +23119,25 @@ EvaluateBuiltinStrLen(const Expr *E, EvalInfo &Info,
if (!EvaluatePointer(E, String, Info))
return std::nullopt;
- QualType CharTy = E->getType()->getPointeeType();
-
// Fast path: if it's a string literal, search the string value.
if (const StringLiteral *S = dyn_cast_or_null<StringLiteral>(
String.getLValueBase().dyn_cast<const Expr *>())) {
StringRef Str = S->getBytes();
int64_t Off = String.Offset.getQuantity();
- if (Off >= 0 && (uint64_t)Off <= (uint64_t)Str.size() &&
- S->getCharByteWidth() == 1 &&
- // FIXME: Add fast-path for wchar_t too.
- Info.Ctx.hasSameUnqualifiedType(CharTy, Info.Ctx.CharTy)) {
- Str = Str.substr(Off);
-
- StringRef::size_type Pos = Str.find(0);
- if (Pos != StringRef::npos)
- Str = Str.substr(0, Pos);
-
- if (StringResult)
+ if (Off >= 0 && (uint64_t)Off <= (uint64_t)Str.size()) {
+ UnsignedOrNone ZeroIndex = S->findZeroCodeUnit(Off);
+ if (StringResult) {
+ if (ZeroIndex)
+ Str = Str.substr(Off, *ZeroIndex);
*StringResult = Str;
- return Str.size();
- }
+ }
- // Fall through to slow path.
+ return ZeroIndex.value_or(Str.size());
+ }
+ // For an invalid index, fall through to the offset handling below.
}
+ QualType CharTy = E->getType()->getPointeeType();
// Slow path: scan the bytes of the string looking for the terminating 0.
for (uint64_t Strlen = 0; /**/; ++Strlen) {
APValue Char;
diff --git a/clang/unittests/AST/CMakeLists.txt b/clang/unittests/AST/CMakeLists.txt
index 81010e0469685..e726cc3299a5a 100644
--- a/clang/unittests/AST/CMakeLists.txt
+++ b/clang/unittests/AST/CMakeLists.txt
@@ -3,6 +3,7 @@ add_subdirectory(ByteCode)
add_clang_unittest(ASTTests
ASTContextParentMapTest.cpp
ASTDumperTest.cpp
+ StringLiteral.cpp
ASTExprTest.cpp
ASTImporterFixtures.cpp
ASTImporterTest.cpp
diff --git a/clang/unittests/AST/StringLiteral.cpp b/clang/unittests/AST/StringLiteral.cpp
new file mode 100644
index 0000000000000..1cd56d1725e35
--- /dev/null
+++ b/clang/unittests/AST/StringLiteral.cpp
@@ -0,0 +1,78 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+#include "clang/AST/ASTContext.h"
+#include "clang/AST/Decl.h"
+#include "clang/AST/DeclCXX.h"
+#include "clang/AST/DeclObjC.h"
+#include "clang/AST/Mangle.h"
+#include "clang/AST/TypeBase.h"
+#include "clang/Basic/LLVM.h"
+#include "clang/Tooling/Tooling.h"
+#include "gtest/gtest.h"
+#include <cassert>
+#include <memory>
+#include <string>
+
+using namespace clang::tooling;
+using namespace clang;
+
+static void ConvertUTF8ToWideString(unsigned CharByteWidth, StringRef Source,
+ SmallString<32> &Target) {
+ Target.resize(CharByteWidth * (Source.size() + 1));
+ char *ResultPtr = &Target[0];
+ const llvm::UTF8 *ErrorPtr;
+ bool success =
+ llvm::ConvertUTF8toWide(CharByteWidth, Source, ResultPtr, ErrorPtr);
+ (void)success;
+ assert(success);
+ Target.resize(ResultPtr - &Target[0]);
+}
+
+TEST(StringLiteral, findZeroCodeUnit) {
+ auto AST = tooling::buildASTFromCodeWithArgs("", {});
+ ASTContext &Ctx = AST->getASTContext();
+
+ auto getCharArrayType = [&Ctx](unsigned Size) -> QualType {
+ return Ctx.getStringLiteralArrayType(Ctx.CharTy.withConst(), Size);
+ };
+ auto getWCharArrayType = [&Ctx](unsigned Size) -> QualType {
+ return Ctx.getStringLiteralArrayType(Ctx.WCharTy.withConst(), Size);
+ };
+
+ const auto *S1 =
+ StringLiteral::Create(Ctx, "abcdef", StringLiteralKind::Ordinary, false,
+ getCharArrayType(7), {});
+ ASSERT_EQ(S1->getLength(), 6u);
+ ASSERT_EQ(*S1->findZeroCodeUnit(), 6u);
+ ASSERT_EQ(*S1->findZeroCodeUnit(4), 2u);
+ ASSERT_FALSE(S1->findZeroCodeUnit(16).has_value());
+
+ const auto *S2 = StringLiteral::Create(Ctx, StringRef("a\0bcd", 6),
+ StringLiteralKind::Ordinary, false,
+ getCharArrayType(6), {});
+ ASSERT_EQ(S2->getLength(), 6u);
+ ASSERT_EQ(*S2->findZeroCodeUnit(), 1u);
+ ASSERT_EQ(*S2->findZeroCodeUnit(1), 0u);
+ ASSERT_EQ(*S2->findZeroCodeUnit(2), 3u);
+
+ SmallString<32> RawChars;
+ ConvertUTF8ToWideString(4, "abcdef", RawChars);
+ const auto *S3 = StringLiteral::Create(
+ Ctx, RawChars, StringLiteralKind::UTF32, false, getWCharArrayType(7), {});
+ ASSERT_EQ(S3->getLength(), 6u);
+ ASSERT_EQ(*S3->findZeroCodeUnit(), 6u);
+ ASSERT_EQ(*S3->findZeroCodeUnit(2), 4u);
+
+ ConvertUTF8ToWideString(4, StringRef("abc\0ef", 6), RawChars);
+ const auto *S4 = StringLiteral::Create(
+ Ctx, RawChars, StringLiteralKind::UTF32, false, getWCharArrayType(7), {});
+ ASSERT_EQ(S4->getLength(), 6u);
+ ASSERT_EQ(S4->findZeroCodeUnit(), 3u);
+ ASSERT_EQ(S4->findZeroCodeUnit(3u), 0u);
+ ASSERT_EQ(S4->findZeroCodeUnit(4u), 2u);
+}
More information about the cfe-commits
mailing list