[clang] a2ac4f6 - [clang][AST] Add `StringLiteral::findZeroCodeUnit()` (#218601)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Aug 26 00:11:42 PDT 2026
Author: Timm Baeder
Date: 2026-08-26T09:11:37+02:00
New Revision: a2ac4f6af9580023d3d4c580983086491ca5ae84
URL: https://github.com/llvm/llvm-project/commit/a2ac4f6af9580023d3d4c580983086491ca5ae84
DIFF: https://github.com/llvm/llvm-project/commit/a2ac4f6af9580023d3d4c580983086491ca5ae84.diff
LOG: [clang][AST] Add `StringLiteral::findZeroCodeUnit()` (#218601)
Which can be used to implement strlen-like functionality.
Added:
clang/unittests/AST/StringLiteral.cpp
Modified:
clang/include/clang/AST/Expr.h
clang/lib/AST/ByteCode/Context.cpp
clang/lib/AST/ByteCode/InterpBuiltin.cpp
clang/lib/AST/Expr.cpp
clang/lib/AST/ExprConstant.cpp
clang/unittests/AST/CMakeLists.txt
Removed:
################################################################################
diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h
index 30ca97cdb2bb2..535086a6c2aa3 100644
--- a/clang/include/clang/AST/Expr.h
+++ b/clang/include/clang/AST/Expr.h
@@ -1929,6 +1929,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;
+
/// \returns The length of the full string in bytes.
unsigned getByteLength() const { return getCharByteWidth() * getLength(); }
/// \returns The length of the full string in characters.
diff --git a/clang/lib/AST/ByteCode/Context.cpp b/clang/lib/AST/ByteCode/Context.cpp
index 79e513d3e857f..75a5b04cdff19 100644
--- a/clang/lib/AST/ByteCode/Context.cpp
+++ b/clang/lib/AST/ByteCode/Context.cpp
@@ -363,13 +363,10 @@ std::optional<uint64_t> Context::evaluateStrlen(State &Parent, const Expr *E) {
if (Off < 0)
return false;
- unsigned Length = 0;
- for (uint64_t I = Off; I != Lit->getLength(); ++I) {
- if (Lit->getCodeUnit(I) == 0)
- break;
- ++Length;
- }
- Result = Length;
+ UnsignedOrNone ZeroIndex = Lit->findZeroCodeUnit(Off);
+ if (!ZeroIndex)
+ return false;
+ Result = *ZeroIndex;
return true;
}
diff --git a/clang/lib/AST/ByteCode/InterpBuiltin.cpp b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
index b9e259a7d7c7b..1a464247b5ce2 100644
--- a/clang/lib/AST/ByteCode/InterpBuiltin.cpp
+++ b/clang/lib/AST/ByteCode/InterpBuiltin.cpp
@@ -397,14 +397,10 @@ static bool interp__builtin_strlen(InterpState &S, CodePtr OpPC,
if (Off < 0)
return false;
- unsigned Length = 0;
- for (uint64_t I = Off; I != Lit->getLength(); ++I) {
- if (Lit->getCodeUnit(I) == 0)
- break;
- ++Length;
- }
-
- pushInteger(S, Length, Call->getType());
+ UnsignedOrNone ZeroIndex = Lit->findZeroCodeUnit(Off);
+ if (!ZeroIndex)
+ return false;
+ pushInteger(S, *ZeroIndex, Call->getType());
return true;
}
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 e5a1d1fe56a56..9702105951b7b 100644
--- a/clang/lib/AST/ExprConstant.cpp
+++ b/clang/lib/AST/ExprConstant.cpp
@@ -23122,31 +23122,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