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); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
