[clang] [clang][analyzer] Add checker 'unix.cstring.MissingTerminatingZero' (PR #146664)
via cfe-commits
cfe-commits at lists.llvm.org
Wed Jul 2 03:18:52 PDT 2025
llvmbot wrote:
<!--LLVM PR SUMMARY COMMENT-->
@llvm/pr-subscribers-clang-static-analyzer-1
Author: Balázs Kéri (balazske)
<details>
<summary>Changes</summary>
A new checker for checking if terminating zero is missing from a string. There is an existing `unix.cstring.NotNullTerminated` checker that looks similar but checks just if a non-string like object is passed to a function. The new checker tests if really the terminating zero is missing. This is the initial version, it can be improved to handle string (or memory) copy functions.
---
Patch is 21.65 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/146664.diff
9 Files Affected:
- (modified) clang/docs/analyzer/checkers.rst (+52)
- (modified) clang/include/clang/StaticAnalyzer/Checkers/Checkers.td (+28)
- (modified) clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt (+1)
- (added) clang/lib/StaticAnalyzer/Checkers/MissingTerminatingZeroChecker.cpp (+295)
- (modified) clang/test/Analysis/analyzer-config.c (+2)
- (modified) clang/test/Analysis/analyzer-enabled-checkers.c (+1)
- (added) clang/test/Analysis/cstring-missingterminatingzero-ignore.c (+27)
- (added) clang/test/Analysis/cstring-missingterminatingzero.c (+91)
- (modified) clang/test/Analysis/std-c-library-functions-arg-enabled-checkers.c (+1)
``````````diff
diff --git a/clang/docs/analyzer/checkers.rst b/clang/docs/analyzer/checkers.rst
index 26c5028e04955..16265a9742f16 100644
--- a/clang/docs/analyzer/checkers.rst
+++ b/clang/docs/analyzer/checkers.rst
@@ -2098,6 +2098,58 @@ Check the size argument passed into C string functions for common erroneous patt
// warn: potential buffer overflow
}
+.. _unix-cstring-MissingTerminatingZero:
+
+unix.cstring.MissingTerminatingZero (C)
+"""""""""""""""""""""""""""""""""""""""
+Check for string arguments passed to C library functions where the terminating
+zero is missing.
+
+The checker can only follow initializations with constant values and assignment
+of constant values to string elements.
+
+.. code-block:: c
+
+ int test1() {
+ char buf[4] = {1, 2, 3, 4};
+ return strlen(buf); // warn
+ }
+
+ int test2() {
+ char buf[] = "abcd";
+ buf[4] = 'e';
+ return strlen(buf); // warn
+ }
+
+ int test3() {
+ char buf[4];
+ buf[3] = 100;
+ return strlen(buf + 3); // warn
+ }
+
+**Options**
+
+By default the checker assumes that any parameter of type ``const char *`` to a
+global C system function should be a null-terminated string. Additionally there
+is a list of exceptions which are identified by the function name and parameter
+index. This list is called "ignore list" and contains these default values:
+(``stpncpy``, 1), (``strncat``, 1), (``strncmp``, 0), (``strncmp``, 1),
+(``strncpy``, 1), (``strndup``, 0), (``strnlen``, 0)
+These functions are ignored because they have a length parameter and can work
+with non-null terminated strings. The list can be changed by the following
+options:
+
+* ``OmitDefaultIgnoreFunctions`` (boolean). If true, the default ignore list is
+ cleared. (Independently of ``IgnoreFunctionArgs`` contains values or not.)
+
+* ``IgnoreFunctionArgs`` (string). Can be used to add functions to the ignore
+ list. It should contain entries in form of "<function name> <parameter index>"
+ separated by comma. These values are added to the ignore list. For example
+ ``strlen 0, strcpy 0, strcpy 1`` adds ``strlen`` and ``strcpy`` (both
+ parameters) to the ignore list. A function name can be used at most 2 times
+ (with different parameter values). Default value of the option is an empty
+ string.
+
.. _unix-cstring-NotNullTerminated:
unix.cstring.NotNullTerminated (C)
diff --git a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
index 2a96df80d1001..31a67c2992edf 100644
--- a/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
+++ b/clang/include/clang/StaticAnalyzer/Checkers/Checkers.td
@@ -480,6 +480,34 @@ def CStringSyntaxChecker : Checker<"BadSizeArg">,
Dependencies<[CStringModeling]>,
Documentation<HasDocumentation>;
+def MissingTerminatingZeroChecker
+ : Checker<"MissingTerminatingZero">,
+ HelpText<
+ "Check for string arguments passed to C library functions where the "
+ "terminating zero is missing">,
+ CheckerOptions<
+ [CmdLineOption<
+ Boolean, "OmitDefaultIgnoreFunctions",
+ "The checker checks by default all 'const char *' arguments "
+ "of system library C functions, except for a built-in list "
+ "of functions. If this parameter is set to true, this ignore "
+ "list is not used, but functions can be still specified by "
+ "the 'IgnoreFunctionArgs' option.",
+ "false", Released>,
+ CmdLineOption<
+ String, "IgnoreFunctionArgs",
+ "Specifies a list of functions to ignore by the checker. This "
+ "list should contain comma-separated values in the format "
+ "'<name> <parm>' where <name> is the function name and "
+ "<parm> is the zero-based index of the parameter (with "
+ "'const char *' type). The same function can be specified "
+ "with different parameters at most 2 times. These functions "
+ "are added to the default ignore list, unless "
+ "'OmitDefaultIgnoreFunctions' is true.",
+ "", Released>,
+]>,
+ Documentation<HasDocumentation>;
+
} // end "unix.cstring"
let ParentPackage = CStringAlpha in {
diff --git a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
index 22dd3f0374849..d4960431402c2 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
+++ b/clang/lib/StaticAnalyzer/Checkers/CMakeLists.txt
@@ -64,6 +64,7 @@ add_clang_library(clangStaticAnalyzerCheckers
MallocChecker.cpp
MallocSizeofChecker.cpp
MismatchedIteratorChecker.cpp
+ MissingTerminatingZeroChecker.cpp
MmapWriteExecChecker.cpp
MIGChecker.cpp
MoveChecker.cpp
diff --git a/clang/lib/StaticAnalyzer/Checkers/MissingTerminatingZeroChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/MissingTerminatingZeroChecker.cpp
new file mode 100644
index 0000000000000..292f0c809ac1a
--- /dev/null
+++ b/clang/lib/StaticAnalyzer/Checkers/MissingTerminatingZeroChecker.cpp
@@ -0,0 +1,295 @@
+//=== MissingTerminatingZeroChecker.cpp -------------------------*- C++ -*-===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+//
+// Check for string arguments passed to C library functions where the
+// terminating zero is missing.
+//
+//===----------------------------------------------------------------------===//
+
+#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
+#include "clang/StaticAnalyzer/Core/Checker.h"
+#include "clang/StaticAnalyzer/Core/CheckerManager.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CallDescription.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/CheckerContext.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/DynamicExtent.h"
+#include "clang/StaticAnalyzer/Core/PathSensitive/MemRegion.h"
+#include "llvm/ADT/BitVector.h"
+#include <sstream>
+
+using namespace clang;
+using namespace ento;
+
+namespace {
+
+struct StringData {
+ const MemRegion *StrRegion;
+ int64_t StrLength;
+ unsigned int Offset;
+ const llvm::BitVector *NonNullData;
+};
+
+class MissingTerminatingZeroChecker
+ : public Checker<check::Bind, check::PreCall> {
+public:
+ void checkBind(SVal L, SVal V, const Stmt *S, CheckerContext &C) const;
+ void checkPreCall(const CallEvent &Call, CheckerContext &C) const;
+
+ void initOptions(bool NoDefaultIgnore, StringRef IgnoreList);
+
+private:
+ const BugType BT{this, "Missing terminating zero"};
+
+ using IgnoreEntry = std::pair<int, int>;
+ /// Functions (identified by name only) to ignore.
+ /// The entry stores a parameter index, or -1.
+ llvm::StringMap<IgnoreEntry> FunctionsToIgnore = {
+ {"stpncpy", {1, -1}}, {"strncat", {1, -1}}, {"strncmp", {0, 1}},
+ {"strncpy", {1, -1}}, {"strndup", {0, -1}}, {"strnlen", {0, -1}},
+ };
+
+ bool checkArg(unsigned int ArgI, CheckerContext &C,
+ const CallEvent &Call) const;
+ bool getStringData(StringData &DataOut, ProgramStateRef State,
+ SValBuilder &SVB, const MemRegion *StrReg) const;
+ ProgramStateRef setStringData(ProgramStateRef State, Loc L,
+ const llvm::BitVector &NonNullData) const;
+ void reportBug(ExplodedNode *N, const Expr *E, CheckerContext &C,
+ const char Msg[]) const;
+};
+
+} // namespace
+
+namespace llvm {
+template <> struct FoldingSetTrait<llvm::BitVector> {
+ static inline void Profile(llvm::BitVector X, FoldingSetNodeID &ID) {
+ ID.AddInteger(X.size());
+ for (unsigned int I = 0; I < X.size(); ++I)
+ ID.AddBoolean(X[I]);
+ }
+};
+} // end namespace llvm
+
+// Contains for a "string" (character array) region if elements are known to be
+// non-zero. The bit vector is indexed by the array element index and is true
+// if the element is known to be non-zero. Size of the vector does not
+// correspond to the extent of the memory region (can be smaller), the missing
+// elements are considered to be false.
+// (A value of 'false' means that the string element is zero or unknown.)
+REGISTER_MAP_WITH_PROGRAMSTATE(NonNullnessData, const MemRegion *,
+ llvm::BitVector)
+
+void MissingTerminatingZeroChecker::checkBind(SVal L, SVal V, const Stmt *S,
+ CheckerContext &C) const {
+ ProgramStateRef State = C.getState();
+ const MemRegion *MR = L.getAsRegion();
+
+ if (const ElementRegion *ER = dyn_cast_or_null<ElementRegion>(MR)) {
+ // Assign value to the index of an array.
+ // Check for applicable array type.
+ QualType ElemType = ER->getValueType().getCanonicalType();
+ if (!ElemType->isCharType())
+ return;
+ if (C.getASTContext().getTypeSizeInChars(ElemType).getQuantity() != 1)
+ return;
+
+ RegionRawOffset ROffset = ER->getAsArrayOffset();
+ unsigned int Index = ROffset.getOffset().getQuantity();
+
+ // If the checker has data about the value to bind, use this information.
+ // Otherwise try to get it from the analyzer.
+ auto GetKnownToBeNonNull = [this, State, &C](SVal V) -> ConditionTruthVal {
+ StringData ExistingSData;
+ if (getStringData(ExistingSData, State, C.getSValBuilder(),
+ V.getAsRegion()) &&
+ ExistingSData.Offset < ExistingSData.NonNullData->size()) {
+ return ExistingSData.NonNullData->test(ExistingSData.Offset);
+ } else {
+ return State->isNonNull(V);
+ }
+ };
+ ConditionTruthVal VKnownToBeNonNull = GetKnownToBeNonNull(V);
+
+ if (const llvm::BitVector *NNData =
+ State->get<NonNullnessData>(ROffset.getRegion())) {
+ // Update existing data.
+ unsigned int NewSize =
+ Index < NNData->size() ? NNData->size() : Index + 1;
+ // Only extend the vector with 'true' value.
+ if (NewSize > NNData->size() && !VKnownToBeNonNull.isConstrainedTrue())
+ return;
+ llvm::BitVector NNData1(NewSize);
+ NNData1 |= *NNData;
+ NNData1[Index] = VKnownToBeNonNull.isConstrainedTrue();
+ State = State->set<NonNullnessData>(ROffset.getRegion(), NNData1);
+ } else {
+ // Only add new data if 'true' is found.
+ if (!VKnownToBeNonNull.isConstrainedTrue())
+ return;
+ llvm::BitVector NNData1(Index + 1);
+ NNData1[Index] = true;
+ State = State->set<NonNullnessData>(ROffset.getRegion(), NNData1);
+ }
+ } else if (const TypedValueRegion *TR =
+ dyn_cast_or_null<TypedValueRegion>(MR)) {
+ // Initialize a region with compound value from list or string literal.
+ QualType Type = TR->getValueType().getCanonicalType();
+ if (!Type->isArrayType())
+ return;
+ if (!Type->castAsArrayTypeUnsafe()->getElementType()->isCharType())
+ return;
+
+ if (auto CVal = V.getAs<nonloc::CompoundVal>()) {
+ llvm::BitVector NNData;
+ for (auto Val = CVal->begin(); Val != CVal->end(); ++Val)
+ NNData.push_back(State->isNonNull(*Val).isConstrainedTrue());
+ State = State->set<NonNullnessData>(MR, NNData);
+ } else if (auto MRV = V.getAs<loc::MemRegionVal>()) {
+ if (auto *StrReg = MRV->stripCasts()->getAs<StringRegion>()) {
+ StringRef Str = StrReg->getStringLiteral()->getString();
+ size_t StrL = Str.size();
+ llvm::BitVector NNData(StrL + 1, true);
+ for (unsigned int I = 0; I < StrL; ++I)
+ if (Str[I] == 0)
+ NNData.reset(I);
+ NNData.reset(StrL);
+ State = State->set<NonNullnessData>(MR, NNData);
+ }
+ }
+ }
+ C.addTransition(State);
+}
+
+void MissingTerminatingZeroChecker::checkPreCall(const CallEvent &Call,
+ CheckerContext &C) const {
+ if (!Call.isInSystemHeader() || !Call.isGlobalCFunction())
+ return;
+
+ auto Ignore = [this](StringRef Name) -> IgnoreEntry {
+ auto FIgnore = FunctionsToIgnore.find(Name);
+ if (FIgnore != FunctionsToIgnore.end())
+ return FIgnore->getValue();
+ return IgnoreEntry{-1, -1};
+ }(cast<NamedDecl>(Call.getDecl())->getNameAsString());
+
+ for (const auto &[ArgI, ParmD] : enumerate(Call.parameters())) {
+ QualType ArgTy = ParmD->getType();
+ if (!ArgTy->isPointerType())
+ continue;
+ QualType ArgPointeeTy = ArgTy->getPointeeType();
+ if (!ArgPointeeTy->isCharType() || !ArgPointeeTy.isConstQualified())
+ continue;
+ if (static_cast<int>(ArgI) == Ignore.first ||
+ static_cast<int>(ArgI) == Ignore.second)
+ continue;
+
+ if (checkArg(ArgI, C, Call))
+ return;
+ }
+}
+
+bool MissingTerminatingZeroChecker::checkArg(unsigned int ArgI,
+ CheckerContext &C,
+ const CallEvent &Call) const {
+ SVal StrArgVal = Call.getArgSVal(ArgI);
+
+ StringData SData;
+ if (!getStringData(SData, C.getState(), C.getSValBuilder(),
+ StrArgVal.getAsRegion()))
+ return false;
+
+ // Check if all elements in the string are known to be non-zero.
+ // The stored bitvector can have a smaller size.
+ if (SData.NonNullData->size() < SData.StrLength)
+ return false;
+ for (int64_t I = SData.Offset; I < SData.StrLength; ++I)
+ if (!SData.NonNullData->test(I))
+ return false;
+
+ reportBug(C.getPredecessor(), Call.getArgExpr(ArgI), C,
+ "String contains no terminating zero; At this place a "
+ "null-terminated string is expected");
+ return true;
+}
+
+bool MissingTerminatingZeroChecker::getStringData(
+ StringData &DataOut, ProgramStateRef State, SValBuilder &SVB,
+ const MemRegion *StrReg) const {
+ if (!StrReg)
+ return false;
+
+ unsigned int Offset = 0;
+ if (const auto *ElemReg = dyn_cast_or_null<ElementRegion>(StrReg)) {
+ RegionRawOffset ROffset = ElemReg->getAsArrayOffset();
+ StrReg = ROffset.getRegion();
+ if (!StrReg)
+ return false;
+ Offset = ROffset.getOffset().getQuantity();
+ }
+
+ const llvm::BitVector *NNData = State->get<NonNullnessData>(StrReg);
+ if (!NNData)
+ return false;
+
+ DefinedOrUnknownSVal Extent = getDynamicExtent(State, StrReg, SVB);
+ if (Extent.isUnknown())
+ return false;
+ const llvm::APSInt *KnownExtent = SVB.getKnownValue(State, Extent);
+ if (!KnownExtent)
+ return false;
+
+ DataOut.StrRegion = StrReg;
+ DataOut.StrLength = KnownExtent->getExtValue();
+ DataOut.Offset = Offset;
+ DataOut.NonNullData = NNData;
+
+ return true;
+}
+
+void MissingTerminatingZeroChecker::reportBug(ExplodedNode *N, const Expr *E,
+ CheckerContext &C,
+ const char Msg[]) const {
+ auto R = std::make_unique<PathSensitiveBugReport>(BT, Msg, N);
+ bugreporter::trackExpressionValue(N, E, *R);
+ C.emitReport(std::move(R));
+}
+
+void MissingTerminatingZeroChecker::initOptions(bool NoDefaultIgnore,
+ StringRef IgnoreList) {
+ if (NoDefaultIgnore)
+ FunctionsToIgnore.clear();
+ std::istringstream IgnoreInput{std::string(IgnoreList)};
+ std::array<char, 100> I;
+ while (IgnoreInput.getline(&I[0], 100, ';')) {
+ std::istringstream FunctionInput{std::string(&I[0])};
+ std::string FName;
+ int FArg;
+ if (FunctionInput >> FName >> FArg) {
+ IgnoreEntry &E =
+ FunctionsToIgnore.insert({FName, {-1, -1}}).first->getValue();
+ if (E.first == -1)
+ E.first = FArg;
+ else if (E.second == -1)
+ E.second = FArg;
+ }
+ }
+}
+
+void ento::registerMissingTerminatingZeroChecker(CheckerManager &Mgr) {
+ auto *Checker = Mgr.registerChecker<MissingTerminatingZeroChecker>();
+ bool NoDefaultIgnore = Mgr.getAnalyzerOptions().getCheckerBooleanOption(
+ Checker, "OmitDefaultIgnoreFunctions");
+ StringRef IgnoreList = Mgr.getAnalyzerOptions().getCheckerStringOption(
+ Checker, "IgnoreFunctionArgs");
+ Checker->initOptions(NoDefaultIgnore, IgnoreList);
+}
+
+bool ento::shouldRegisterMissingTerminatingZeroChecker(
+ const CheckerManager &Mgr) {
+ // Check if the char type has size of 8 bits (to avoid indexing differences)?
+ return true;
+}
diff --git a/clang/test/Analysis/analyzer-config.c b/clang/test/Analysis/analyzer-config.c
index 7936273415ad4..d477987e51642 100644
--- a/clang/test/Analysis/analyzer-config.c
+++ b/clang/test/Analysis/analyzer-config.c
@@ -137,6 +137,8 @@
// CHECK-NEXT: unix.StdCLibraryFunctions:DisplayLoadedSummaries = false
// CHECK-NEXT: unix.StdCLibraryFunctions:ModelPOSIX = true
// CHECK-NEXT: unix.Stream:Pedantic = false
+// CHECK-NEXT: unix.cstring.MissingTerminatingZero:IgnoreFunctionArgs = ""
+// CHECK-NEXT: unix.cstring.MissingTerminatingZero:OmitDefaultIgnoreFunctions = false
// CHECK-NEXT: unroll-loops = false
// CHECK-NEXT: verbose-report-filename = false
// CHECK-NEXT: widen-loops = false
diff --git a/clang/test/Analysis/analyzer-enabled-checkers.c b/clang/test/Analysis/analyzer-enabled-checkers.c
index 66b9be9795f12..127bd3298c76c 100644
--- a/clang/test/Analysis/analyzer-enabled-checkers.c
+++ b/clang/test/Analysis/analyzer-enabled-checkers.c
@@ -57,6 +57,7 @@
// CHECK-NEXT: unix.StdCLibraryFunctions
// CHECK-NEXT: unix.Vfork
// CHECK-NEXT: unix.cstring.BadSizeArg
+// CHECK-NEXT: unix.cstring.MissingTerminatingZero
// CHECK-NEXT: unix.cstring.NotNullTerminated
// CHECK-NEXT: unix.cstring.NullArg
diff --git a/clang/test/Analysis/cstring-missingterminatingzero-ignore.c b/clang/test/Analysis/cstring-missingterminatingzero-ignore.c
new file mode 100644
index 0000000000000..8b96bf81c55cd
--- /dev/null
+++ b/clang/test/Analysis/cstring-missingterminatingzero-ignore.c
@@ -0,0 +1,27 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.MissingTerminatingZero \
+// RUN: -analyzer-config unix.cstring.MissingTerminatingZero:IgnoreFunctionArgs='strlen 0;strcpy 1' -verify=all,ignore %s
+// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.MissingTerminatingZero \
+// RUN: -analyzer-config unix.cstring.MissingTerminatingZero:IgnoreFunctionArgs='strlen 0;strcpy 1' \
+// RUN: -analyzer-config unix.cstring.MissingTerminatingZero:OmitDefaultIgnoreFunctions=true -verify=all,omitdefault %s
+
+#include "Inputs/system-header-simulator.h"
+
+size_t test1(int i) {
+ char buf[1] = {1};
+ return strlen(buf);
+}
+
+void test2(char *dst) {
+ char src[1] = {1};
+ strcpy(dst, src);
+}
+
+int test3() {
+ const char buf[1] = {1};
+ return execl("path", buf, 4); // all-warning{{String contains no terminating zero}}
+}
+
+void test4(char *dst) {
+ char src[3] = {1, 2, 3};
+ strncpy(dst, src, 3); // omitdefault-warning{{String contains no terminating zero}}
+}
diff --git a/clang/test/Analysis/cstring-missingterminatingzero.c b/clang/test/Analysis/cstring-missingterminatingzero.c
new file mode 100644
index 0000000000000..d0ae6cb19db87
--- /dev/null
+++ b/clang/test/Analysis/cstring-missingterminatingzero.c
@@ -0,0 +1,91 @@
+// RUN: %clang_analyze_cc1 -analyzer-checker=unix.cstring.MissingTerminatingZero -verify %s
+
+#include "Inputs/system-header-simulator.h"
+
+void clang_analyzer_eval(int);
+
+size_t test_init_compound(int i) {
+ char src1[6] = {1,2,3,4,5,6};
+ char src2[6] = {1,2,3,0,5,6};
+ switch (i) {
+ case 1:
+ return strlen(src1); // expected-warning{{String contains no terminating zero}}
+ case 2:
+ return strlen(src1 + 1); // expected-warning{{String contains no terminating zero}}
+ case 3:
+ return strlen(src2);
+ case 4:
+ return strlen(src2 + 4); // expected-warning{{String contains no terminating zero}}
+ case 5:
+ return strlen(src2 + 3);
+ }
+ src1[5] = 0;
+ return strlen(src1);
+}
+
+typedef char CHAR;
+
+size_t test_init_literal(int i) {
+ CHAR src1[] = "abcdef";
+ int l = strlen(src1);
+ src1[6] = '.';
+ src1[3] = 0;
+ switch (i) {
+ case 1:
+ return strlen(src1);
+ case 2:
+ return strlen(src1 + 4); // expected-warning{{String contains no terminating zero}}
+ }
+ return l;
+}
+
+size_t test_init_assign(int i, char a) {
+ char src[6];
+ src[1] = '1';
+ src[2] = '2';
+ src[4] = '4';
+ src[5] = '5';
+
+ switch (i) {
+ case 0:
+ return ...
[truncated]
``````````
</details>
https://github.com/llvm/llvm-project/pull/146664
More information about the cfe-commits
mailing list