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

Reply via email to