[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/30] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,175 @@ +//===--===// +// +// 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 "FormatvStringCheck.h" +#include "../utils/OptionsUtils.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/Support/Error.h" +#include zeyi2 wrote: ```suggestion ``` https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/localspook edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/zeyi2 approved this pull request. LGTM with a nit, thank you! https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,175 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g.
{0:$[,]}).
localspook wrote:
Ah, sorry, one more thing: I believe this comment is now stale:
```suggestion
```
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/33] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/32] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/localspook edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/zeyi2 edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,42 @@
+//===--===//
+//
+// 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
+//
+//===--===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_FORMATVSTRINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_FORMATVSTRINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringMap.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Validates llvm::formatv format strings against the provided arguments.
+///
+/// Checks that:
+/// - The number of format indices matches the number of arguments.
+/// - Every argument is used by the format string.
+/// - Automatic and explicit indices are not mixed.
+class FormatvStringCheck : public ClangTidyCheck {
+public:
zwuis wrote:
Sorry that I just realized this prevent matching nodes in template
instantiations:
```cpp
template
void f(Ts... Args) {
formatv("{}", Args...);
}
void g() {
f(1, 2); // no warning
}
```
, but I don't know how rare it is. It's up to you to revert it.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/29] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/28] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,183 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g.
{0:$[,]}).
+StringRef IndexStr = Content;
+
+const size_t ColonPos = Content.find(':');
+if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+const size_t CommaPos = IndexStr.find(',');
+if (CommaPos != StringRef::npos)
+ IndexStr = IndexStr.substr(0, CommaPos);
kastiglione wrote:
looks good
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+ std::vector CustomFunctions =
+ utils::options::parseStringList(AdditionalFunctions);
+ copy(CustomFunctions, std::back_inserter(Functions));
kastiglione wrote:
`hasAnyName` uses unqualified matching if any of the names are unqualified. If
my understanding is correct `::llvm::formatv` would match against
`my_custom_namespace::llvm::formatv` – but only if the `AdditionalFunctions`
contains unqualified names.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,183 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g.
{0:$[,]}).
+StringRef IndexStr = Content;
+
+const size_t ColonPos = Content.find(':');
+if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+const size_t CommaPos = IndexStr.find(',');
+if (CommaPos != StringRef::npos)
+ IndexStr = IndexStr.substr(0, CommaPos);
localspook wrote:
Up to you if you prefer it, but this can be written as:
```suggestion
const StringRef IndexStr = Content.substr(0, Content.find_first_of(",:");
```
(If `find_first_of` returns `npos`, that's okay, `substr` handles it)
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,183 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+// Strip the format part first, since it may contain commas (e.g.
{0:$[,]}).
+StringRef IndexStr = Content;
+
+const size_t ColonPos = Content.find(':');
+if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+const size_t CommaPos = IndexStr.find(',');
+if (CommaPos != StringRef::npos)
+ IndexStr = IndexStr.substr(0, CommaPos);
+
+IndexStr = IndexStr.trim();
localspook wrote:
If I'm understanding right, this line allows us to successfully parse something
like `{ 0 :x}` as a valid index? But does `formatv` actually allow that? If
it does, can we add tests for it?
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+ std::vector CustomFunctions =
+ utils::options::parseStringList(AdditionalFunctions);
+ copy(CustomFunctions, std::back_inserter(Functions));
localspook wrote:
> Now I wonder, after parsing the AdditionalFunctions, should a :: namespace
> qualifier be added to its functions (if not present)?
(For context, the motivation for fully qualifying is to not match on things
like `my_custom_namespace::llvm::formatv`)
While I agree that a user probably *intended* to fully qualify their own
`AdditionalFunctions`, adding the `::` wouold be an invisible behavior-changing
transformation, and we don't do it any other check, so I lean towards not
touching what the user wrote.
> there could be a mix of qualified and unqualified names given to `hasAnyName`.
Having a mix is okay AFAIK, `hasAnyName` considers each name independently of
the others.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+ std::vector CustomFunctions =
+ utils::options::parseStringList(AdditionalFunctions);
+ copy(CustomFunctions, std::back_inserter(Functions));
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Functions),
+ ast_matchers::isTemplateInstantiation())),
+ argumentCountAtLeast(1))
+ .bind("call"),
+ this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs("call");
+ assert(Call && Call->getNumArgs() > 0);
+
+ const auto *FD = Call->getDirectCallee();
+ assert(FD);
+
+ // Find the format string index from the template signature: it's the
+ // parameter immediately before the trailing parameter pack.
+ const FunctionDecl *TemplateDecl = FD;
+ if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+ const unsigned NumDeclParams = TemplateDecl->getNumParams();
+ if (NumDeclParams < 2)
+return;
+
+ const unsigned PackParamIndex = NumDeclParams - 1;
+ if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+ const unsigned FmtStringIndex = PackParamIndex - 1;
+
+ if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+ // Extract the format string literal.
+ const Expr *FmtArg = Call->getArg(FmtStringIndex)->IgnoreParenImpCasts();
+ const auto *FmtLiteral = dyn_cast(FmtArg);
+ if (!FmtLiteral)
+return;
+
+ const StringRef FmtString = FmtLiteral->getString();
+ const int NumFmtArgs = Call->getNumArgs() - PackParamIndex;
+
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/22] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/27] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
+ llvm::SmallVector Entries;
+ Input.split(Entries, ';', -1, false);
+ for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+ Functions.insert(Entry);
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
kastiglione wrote:
updated here
https://github.com/llvm/llvm-project/pull/195974/commits/404b03fe4ccd3ec5f503e9164c65d809555a84b6
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
kastiglione wrote:
I'm running this against llvm, and it surfaced a bug:
```
/Users/dlee43/src/llvm-project/llvm/lib/Support/BalancedPartitioning.cpp:25:17:
warning: invalid replacement index in format string [llvm-formatv-string]
25 | OS << formatv("{{ID={0} Utilities={{{1:$[,]}} Bucket={2}}", Id,
| ^
```
The index parsing breaks on `{1:$[,]}`. I'll have to make updates to the PR.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/26] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+ std::vector CustomFunctions =
+ utils::options::parseStringList(AdditionalFunctions);
+ copy(CustomFunctions, std::back_inserter(Functions));
kastiglione wrote:
Thanks, I applied your patch.
Now I wonder, after parsing the `AdditionalFunctions`, should a `::` namespace
qualifier be added to its functions (if not present)? If not, there could be a
mix of qualified and unqualified names given to `hasAnyName`.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/25] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+ std::vector CustomFunctions =
+ utils::options::parseStringList(AdditionalFunctions);
+ copy(CustomFunctions, std::back_inserter(Functions));
localspook wrote:
I would reorder this like so, to avoid allocating the extra `CustomFunctions`
vector:
```suggestion
Functions = utils::options::parseStringList(AdditionalFunctions);
Functions.emplace_back("::llvm::formatv");
Functions.emplace_back("::llvm::createStringErrorV");
```
(also note that the functions should be fully qualified)
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
github-actions[bot] wrote: # :penguin: Linux x64 Test Results The build failed before running any tests. Click on a failure below to see the details. tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o ``` FAILED: tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o sccache /opt/llvm/bin/clang++ -D_DEBUG -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_USE_CXX11_ABI=1 -D_GNU_SOURCE -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/clang-tidy/llvm -I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm -I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/tools/extra/clang-tidy -I/home/gha/actions-runner/_work/llvm-project/llvm-project/clang/include -I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/tools/clang/include -I/home/gha/actions-runner/_work/llvm-project/llvm-project/build/include -I/home/gha/actions-runner/_work/llvm-project/llvm-project/llvm/include -gmlt -fPIC -fno-semantic-interposition -fvisibility-inlines-hidden -Werror -Werror=date-time -Werror=unguarded-availability-new -Wall -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -pedantic -Wno-long-long -Wc++98-compat-extra-semi -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion -Wno-pass-failed -Wmisleading-indentation -Wctad-maybe-unsupported -fdiagnostics-color -ffunction-sections -fdata-sections -fno-common -Woverloaded-virtual -Wno-nested-anon-types -O3 -DNDEBUG -std=c++17 -UNDEBUG -fno-exceptions -funwind-tables -fno-rtti -MD -MT tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o -MF tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o.d -o tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.o -c /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:176:7: error: use of undeclared identifier 'UsedIndices'; did you mean 'UnusedIndices'? 176 | UsedIndices.reset(Index); | ^~~ | UnusedIndices /home/gha/actions-runner/_work/llvm-project/llvm-project/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:174:26: note: 'UnusedIndices' declared here 174 | llvm::SmallBitVector UnusedIndices(NumRequiredArgs, true); | ^ 1 error generated. ``` If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the `infrastructure` label. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
github-actions[bot] wrote: # :window: Windows x64 Test Results The build failed before running any tests. Click on a failure below to see the details. [code=1] tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.obj ``` FAILED: [code=1] tools/clang/tools/extra/clang-tidy/llvm/CMakeFiles/obj.clangTidyLLVMModule.dir/FormatvStringCheck.cpp.obj sccache C:\clang\clang-msvc\bin\clang-cl.exe /nologo -TP -DCLANG_BUILD_STATIC -DUNICODE -D_CRT_NONSTDC_NO_DEPRECATE -D_CRT_NONSTDC_NO_WARNINGS -D_CRT_SECURE_NO_DEPRECATE -D_CRT_SECURE_NO_WARNINGS -D_GLIBCXX_ASSERTIONS -D_HAS_EXCEPTIONS=0 -D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE -D_SCL_SECURE_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE -D__STDC_CONSTANT_MACROS -D__STDC_FORMAT_MACROS -D__STDC_LIMIT_MACROS -IC:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\clang-tidy\llvm -IC:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm -IC:\_work\llvm-project\llvm-project\build\tools\clang\tools\extra\clang-tidy -IC:\_work\llvm-project\llvm-project\clang\include -IC:\_work\llvm-project\llvm-project\build\tools\clang\include -IC:\_work\llvm-project\llvm-project\build\include -IC:\_work\llvm-project\llvm-project\llvm\include /DWIN32 /D_WINDOWS /Zc:inline /Zc:__cplusplus /Oi /Brepro /bigobj /permissive- -Werror=unguarded-availability-new /W4 -Wextra -Wno-unused-parameter -Wwrite-strings -Wcast-qual -Wmissing-field-initializers -Wimplicit-fallthrough -Wcovered-switch-default -Wno-noexcept-type -Wnon-virtual-dtor -Wdelete-non-virtual-dtor -Wsuggest-override -Wstring-conversion -Wno-pass-failed -Wmisleading-indentation -Wctad-maybe-unsupported /Gw /O2 /Ob2 -std:c++17 -MD -UNDEBUG /EHs-c- /GR- /showIncludes /Fotools\clang\tools\extra\clang-tidy\llvm\CMakeFiles\obj.clangTidyLLVMModule.dir\FormatvStringCheck.cpp.obj /Fdtools\clang\tools\extra\clang-tidy\llvm\CMakeFiles\obj.clangTidyLLVMModule.dir\ -c -- C:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm\FormatvStringCheck.cpp C:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm\FormatvStringCheck.cpp(176,7): error: use of undeclared identifier 'UsedIndices'; did you mean 'UnusedIndices'? 176 | UsedIndices.reset(Index); | ^~~ | UnusedIndices C:\_work\llvm-project\llvm-project\clang-tools-extra\clang-tidy\llvm\FormatvStringCheck.cpp(174,26): note: 'UnusedIndices' declared here 174 | llvm::SmallBitVector UnusedIndices(NumRequiredArgs, true); | ^ 1 error generated. ``` If these failures are unrelated to your changes (for example tests are broken or flaky at HEAD), please open an issue at https://github.com/llvm/llvm-project/issues and add the `infrastructure` label. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/24] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/23] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
kastiglione wrote:
here are all such drops of explicit `llvm::` namespace use
https://github.com/llvm/llvm-project/pull/195974/commits/bd2f88264e9cb1b3791d7f85b47bbc2a5d91fe51
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
+ llvm::SmallVector Entries;
+ Input.split(Entries, ';', -1, false);
+ for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+ Functions.insert(Entry);
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Names),
+ ast_matchers::isTemplateInstantiation())),
+ argumentCountAtLeast(1))
+ .bind("call"),
+ this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs("call");
+ assert(Call && Call->getNumArgs() > 0);
+
+ const auto *FD = Call->getDirectCallee();
+ assert(FD);
+
+ // Find the format string index from the template signature: it's the
+ // parameter immediately before the trailing parameter pack.
+ const FunctionDecl *TemplateDecl = FD;
+ if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+ const unsigned NumDeclParams = TemplateDecl->getNumParams();
+ if (NumDeclParams < 2)
+return;
+
+ const unsigned PackParamIndex = NumDeclParams - 1;
+ if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+ const unsigned FmtStringIndex = PackParamIndex - 1;
+
+ if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+ /
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,69 @@
+// RUN: %check_clang_tidy %s llvm-formatv-string %t
+
+namespace llvm {
+
+template
+void formatv(const char *Fmt, Ts &&...Vals) {}
+
+template
+void formatv(bool Validate, const char *Fmt, Ts &&...Vals) {}
+
+} // namespace llvm
+
+void correct() {
+ llvm::formatv("{0}", 1);
+ llvm::formatv("{0} {1}", 1, 2);
+ llvm::formatv("{0} {0}", 1);
+ llvm::formatv("{1} {0}", 1, 2);
+ llvm::formatv("{0,10}", 1);
+ llvm::formatv("{0,-10}", 1);
+ llvm::formatv("{0:x}", 1);
+ llvm::formatv("{0,10:x}", 1);
+ llvm::formatv("no replacements");
+ llvm::formatv("escaped {{ braces }}");
+ llvm::formatv("{}", 1);
+ llvm::formatv("{} {}", 1, 2);
+ llvm::formatv(false, "{0}", 1);
+}
+
+void too_few_args() {
+ llvm::formatv("{0} {1}", 1);
kastiglione wrote:
done
https://github.com/llvm/llvm-project/pull/195974/commits/1a28bca0f0c6b645acf066ff3cc59a1443753a08
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/localspook edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,189 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "../utils/OptionsUtils.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+#include
+#include
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static Expected parseFormatvString(StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions = {"llvm::formatv", "llvm::createStringErrorV"};
+
+ std::vector CustomFunctions =
+ utils::options::parseStringList(AdditionalFunctions);
+ copy(CustomFunctions, std::back_inserter(Functions));
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Functions),
+ ast_matchers::isTemplateInstantiation())),
+ argumentCountAtLeast(1))
+ .bind("call"),
+ this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs("call");
+ assert(Call && Call->getNumArgs() > 0);
+
+ const auto *FD = Call->getDirectCallee();
+ assert(FD);
+
+ // Find the format string index from the template signature: it's the
+ // parameter immediately before the trailing parameter pack.
+ const FunctionDecl *TemplateDecl = FD;
+ if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+ const unsigned NumDeclParams = TemplateDecl->getNumParams();
+ if (NumDeclParams < 2)
+return;
+
+ const unsigned PackParamIndex = NumDeclParams - 1;
+ if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+ const unsigned FmtStringIndex = PackParamIndex - 1;
+
+ if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+ // Extract the format string literal.
+ const Expr *FmtArg = Call->getArg(FmtStringIndex)->IgnoreParenImpCasts();
+ const auto *FmtLiteral = dyn_cast(FmtArg);
+ if (!FmtLiteral)
+return;
+
+ const StringRef FmtString = FmtLiteral->getString();
+ const int NumFmtArgs = Call->getNumArgs() - PackParamIndex;
+
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
zeyi2 wrote: > Have you tried running the check over the LLVM codebase? It would be > interesting to see if it finds anything. I'm testing it now, will post the comment here after the run finishes :) https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
kastiglione wrote: I ran it on the lldb codebase and found over 20 bugs. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
localspook wrote: Have you tried running the check over the LLVM codebase? It would be interesting to see if it finds anything. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
+ llvm::SmallVector Entries;
+ Input.split(Entries, ';', -1, false);
+ for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+ Functions.insert(Entry);
+ }
kastiglione wrote:
updated here
https://github.com/llvm/llvm-project/pull/195974/commits/404b03fe4ccd3ec5f503e9164c65d809555a84b6
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/18] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,24 @@
+// RUN: %check_clang_tidy %s llvm-formatv-string %t
+
+namespace llvm {
+
+template
+void createStringErrorV(int EC, const char *Fmt, Ts &&...Vals) {}
+
+template
+void createStringErrorV(const char *Fmt, Ts &&...Vals) {}
+
+} // namespace llvm
+
+void correct() {
+ llvm::createStringErrorV(0, "{0} {1}", 1, 2);
+ llvm::createStringErrorV("{0}", 42);
+}
+
+void wrong_count() {
+ llvm::createStringErrorV(0, "{0} {1}", 1);
+ // CHECK-MESSAGES: :[[@LINE-1]]:31: warning: formatv() format string
requires 2 arguments, but 1 argument was provided
zwuis wrote:
```txt
warning: formatv() format
^^^
```
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,47 @@
+//===--===//
+//
+// 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
+//
+//===--===//
+
+#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_FORMATVSTRINGCHECK_H
+#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_LLVM_FORMATVSTRINGCHECK_H
+
+#include "../ClangTidyCheck.h"
+#include "llvm/ADT/StringSet.h"
+
+namespace clang::tidy::llvm_check {
+
+/// Validates llvm::formatv format strings against the provided arguments.
+///
+/// Checks that:
+/// - The number of format indices matches the number of arguments.
+/// - Every argument is used by the format string.
+/// - Automatic and explicit indices are not mixed.
+///
+/// For the user-facing documentation see:
+/// https://clang.llvm.org/extra/clang-tidy/checks/llvm/formatv-string.html
+class FormatvStringCheck : public ClangTidyCheck {
+public:
+ FormatvStringCheck(StringRef Name, ClangTidyContext *Context);
+ bool isLanguageVersionSupported(const LangOptions &LangOpts) const override {
+return LangOpts.CPlusPlus;
+ }
+ void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
+ void registerMatchers(ast_matchers::MatchFinder *Finder) override;
+ void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
+
+ std::optional getCheckTraversalKind() const override {
+return TK_IgnoreUnlessSpelledInSource;
+ }
+
+private:
+ llvm::StringSet<> Functions;
+ const std::string AdditionalFunctions;
localspook wrote:
```suggestion
const StringRef AdditionalFunctions;
```
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/17] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,69 @@
+// RUN: %check_clang_tidy %s llvm-formatv-string %t
+
+namespace llvm {
+
+template
+void formatv(const char *Fmt, Ts &&...Vals) {}
+
+template
+void formatv(bool Validate, const char *Fmt, Ts &&...Vals) {}
+
+} // namespace llvm
+
+void correct() {
+ llvm::formatv("{0}", 1);
+ llvm::formatv("{0} {1}", 1, 2);
+ llvm::formatv("{0} {0}", 1);
+ llvm::formatv("{1} {0}", 1, 2);
+ llvm::formatv("{0,10}", 1);
+ llvm::formatv("{0,-10}", 1);
+ llvm::formatv("{0:x}", 1);
+ llvm::formatv("{0,10:x}", 1);
+ llvm::formatv("no replacements");
+ llvm::formatv("escaped {{ braces }}");
+ llvm::formatv("{}", 1);
+ llvm::formatv("{} {}", 1, 2);
+ llvm::formatv(false, "{0}", 1);
+}
+
+void too_few_args() {
+ llvm::formatv("{0} {1}", 1);
localspook wrote:
Can we add a test with replacement fields but no format args passed? i.e.:
```cpp
llvm::formatv("{0} {1}");
```
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/localspook commented: I have some comments, but overall the functionality, docs, etc. all look solid! https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
+ llvm::SmallVector Entries;
+ Input.split(Entries, ';', -1, false);
+ for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+ Functions.insert(Entry);
+ }
localspook wrote:
There's a function for this, `utils::options::parseStringList` (it's in
`"../utils/OptionsUtils.h"`).
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
+ llvm::SmallVector Entries;
+ Input.split(Entries, ';', -1, false);
+ for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+ Functions.insert(Entry);
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
localspook wrote:
`Functions` can be a `std::vector` directly, instead of being a `StringSet` and
requiring this extra translation code. Yes, it means it'll have duplicate
entries if a user specified duplicate names, but that's okay, it doesn't cause
any problems.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
+ llvm::SmallVector Entries;
+ Input.split(Entries, ';', -1, false);
+ for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+ Functions.insert(Entry);
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Names),
+ ast_matchers::isTemplateInstantiation())),
+ argumentCountAtLeast(1))
+ .bind("call"),
+ this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs("call");
+ assert(Call && Call->getNumArgs() > 0);
+
+ const auto *FD = Call->getDirectCallee();
+ assert(FD);
+
+ // Find the format string index from the template signature: it's the
+ // parameter immediately before the trailing parameter pack.
+ const FunctionDecl *TemplateDecl = FD;
+ if (const FunctionTemplateDecl *Primary = FD->getPrimaryTemplate())
+TemplateDecl = Primary->getTemplatedDecl();
+
+ const unsigned NumDeclParams = TemplateDecl->getNumParams();
+ if (NumDeclParams < 2)
+return;
+
+ const unsigned PackParamIndex = NumDeclParams - 1;
+ if (!TemplateDecl->getParamDecl(PackParamIndex)->isParameterPack())
+return;
+
+ const unsigned FmtStringIndex = PackParamIndex - 1;
+
+ if (Call->getNumArgs() <= FmtStringIndex)
+return;
+
+ /
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/localspook edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,198 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError(
+"invalid replacement index in format string");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
localspook wrote:
clang-tidy code avoids `llvm::` qualifiers where possible:
```suggestion
const StringRef Input(AdditionalFunctions);
```
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,199 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
zwuis wrote:
I'm surprised that `}}` is not a escape sequence, which is different from
`std::format`.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/zwuis edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,199 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/DeclTemplate.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ Functions.insert("llvm::formatv");
+ Functions.insert("llvm::createStringErrorV");
+
+ // Parse semicolon-separated function names from AdditionalFunctions.
+ const llvm::StringRef Input(AdditionalFunctions);
+ llvm::SmallVector Entries;
+ Input.split(Entries, ';', -1, false);
+ for (llvm::StringRef Entry : Entries) {
+Entry = Entry.trim();
+if (!Entry.empty())
+ Functions.insert(Entry);
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Names),
+ ast_matchers::isTemplateInstantiation())),
zwuis wrote:
Redundant `ast_matchers::`.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/13] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,49 @@
+.. title:: clang-tidy - llvm-formatv-string
+
+llvm-formatv-string
+===
+
+Validates ``llvm::formatv`` format strings against the arguments provided,
+similar to how the compiler validates ``printf`` format strings.
+
+This check diagnoses the following issues:
+
+- The number of replacement indices in the format string does not match the
+ number of arguments provided.
+- A format string does not use one of the given arguments.
+- Mixing of automatic and explicit indices (e.g. ``{} {1}``).
+
+.. code-block:: c++
+
+ // warning: formatv() format string requires 2 arguments, but 1 argument was
provided
+ llvm::formatv("{0} {1}", x);
+
+ // warning: formatv() format string mixes automatic and explicit indices
+ llvm::formatv("{} {1}", x, y);
+
+ // warning: formatv() argument unused in format string
+ llvm::formatv("{0} {2}", x, y, z);
+
+ // OK.
+ llvm::formatv("{0} {1}", x, y);
+ llvm::formatv("{} {}", x, y);
+ llvm::formatv("{0} {0}", x);
+
+The check only operates on calls where the format string is a string literal.
+Dynamic format strings are not diagnosed.
+
+Options
+---
+
+.. option:: AdditionalFunctions
+
+ A semicolon-separated list of additional fully qualified function names to
+ check, beyond ``llvm::formatv`` and ``llvm::createStringErrorV``. Each
+ function must be a variadic template whose last parameter is a parameter
+ pack. The format string is assumed to be the parameter immediately preceding
+ the pack.
+
+ For example, to check ``mylib::log(Level, const char *Fmt, Ts&&...)`` set
+ this option to ``mylib::log``.
EugeneZelenko wrote:
```suggestion
this option to `mylib::log`.
```
Option values should in in single back-ticks.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/zwuis commented: > question for reviewers, should any of these diagnostics be an error? right > now they're all warnings AFAIK clang-tidy checks always emit warnings and notes. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -133,6 +125,12 @@ New checks Finds functions where throwing exceptions is unsafe but the function is still marked as potentially throwing. +- New :doc:`llvm-formatv-string + ` check. + + Validates ``llvm::formatv`` format strings against the provided arguments, EugeneZelenko wrote: Please synchronize with first statement in documentation. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/12] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
kastiglione wrote:
I misunderstood, yes some of this validation could be reused for `std::format`
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
kastiglione wrote: question for reviewers, should any of these diagnostics be an error? right now they're all warnings https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
kastiglione wrote:
I am not particularly familiar with `std::format`. According to my searching,
the format string validation is done at compile time by the standard library.
In which case it seems unlikely to be able to reuse that validation?
Quoting https://en.cppreference.com/cpp/utility/format/format
> Since [P2216R3](https://wg21.link/P2216R3), `std::format` does a compile-time
> check on the format string (via the helper type `std::format_string` or
> `std::wformat_string`). If it is found to be invalid for the types of the
> arguments to be formatted, a compilation error will be emitted. If the format
> string cannot be a compile-time constant, or the compile-time check needs to
> be avoided, use `std::vformat` or
> [`std::dynamic_format`](https://en.cppreference.com/cpp/utility/format/dynamic_format)
> on `fmt`(since C++26) instead.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
kastiglione wrote: thank you for the helpful reviews 🙏 I believe I have addressed all the reviews. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 01/10] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,202 @@ +//===--===// +// +// 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 "FormatvStringCheck.h" +#include "clang/AST/Expr.h" +#include "clang/ASTMatchers/ASTMatchers.h" +#include "llvm/ADT/STLExtras.h" +#include "llvm/ADT/SmallBitVector.h" +#include "llvm/ADT/SmallVector.h" +#include "llvm/ADT/StringRef.h" zeyi2 wrote: Nit: no need to include this file, `clang-tidy` already heavily relies on transitive rule for header inclusion :) https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/zwuis commented: > Please add entry to Release Notes. FYI you can run clang-tools-extra/clang-tidy/add_new_check.py to create a new check. https://clang.llvm.org/extra/clang-tidy/Contributing.html https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] = 0;
+
+ // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+ llvm::StringRef Input(AdditionalFunctions);
+ while (!Input.empty()) {
+const auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+ continue;
+const auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.getAsInteger(10, Idx)) {
+ configurationDiag("invalid entry '%0' in option AdditionalFunctions, "
+"expected 'fully::qualified::name:fmt_arg_index'")
+ << Entry;
+ continue;
+}
+Functions[Name] = Idx;
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Names.bind("call"), this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs("call");
+ if (!Call || Call->getNumArgs() == 0)
zwuis wrote:
Use `assert(Call)`. `check` will be called only if the matcher matches.
---
We can use `argumentCountAtLeast` matcher.
https://clang.llvm.org/docs/LibASTMatchersReference.html
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] = 0;
+
+ // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+ llvm::StringRef Input(AdditionalFunctions);
+ while (!Input.empty()) {
+const auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+ continue;
+const auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.getAsInteger(10, Idx)) {
+ configurationDiag("invalid entry '%0' in option AdditionalFunctions, "
+"expected 'fully::qualified::name:fmt_arg_index'")
+ << Entry;
+ continue;
+}
+Functions[Name] = Idx;
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Names.bind("call"), this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs("call");
+ if (!Call || Call->getNumArgs() == 0)
+return;
+
+ const auto *FD = Call->getDirectCallee();
+ if (!FD)
zwuis wrote:
Use `assert`. We expect the matcher works.
https://github.com/llvm/llvm-project/pull/195974
___
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+const llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] = 0;
+
+ // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+ llvm::StringRef Input(AdditionalFunctions);
+ while (!Input.empty()) {
+const auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+ continue;
+const auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.getAsInteger(10, Idx)) {
+ configurationDiag("invalid entry '%0' in option AdditionalFunctions, "
+"expected 'fully::qualified::name:fmt_arg_index'")
+ << Entry;
+ continue;
+}
+Functions[Name] = Idx;
+ }
+}
+
+void FormatvStringCheck::storeOptions(ClangTidyOptions::OptionMap &Opts) {
+ Options.store(Opts, "AdditionalFunctions", AdditionalFunctions);
+}
+
+void FormatvStringCheck::registerMatchers(MatchFinder *Finder) {
+ // Build a matcher for all configured function names.
+ std::vector Names;
+ Names.reserve(Functions.size());
+ llvm::copy(Functions.keys(), std::back_inserter(Names));
+
+ Finder->addMatcher(
+ callExpr(callee(functionDecl(hasAnyName(Names.bind("call"), this);
+}
+
+void FormatvStringCheck::check(const MatchFinder::MatchResult &Result) {
+ const auto *Call = Result.Nodes.getNodeAs("call");
+ if (!Call || Call->getNumArgs() == 0)
+return;
+
+ const auto *FD = Call->getDirectCallee();
+ if (!FD)
+return;
+
+ // Look up the format string parameter index for this function.
+ const std::string QualName = FD->getQualifiedNameAsString();
+ assert(Functions.contains(QualName) &&
+ "matched function not in Functions map");
+ unsigned FmtArgIdx = Functions.lookup(QualName);
+
+ // For llvm::formatv, also handle the (bool, const char*, ...) overload.
+ if (QualName == "llvm::formatv" && FD->getNumParams() > 0 &&
+ FD->getParamDecl(0)->getType()->isBooleanType())
+FmtArgIdx = 1;
+
+ if (Call->getNu
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/zwuis edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
kastiglione wrote: This is my first clang-tidy check. Feedback and direction welcome. https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione updated
https://github.com/llvm/llvm-project/pull/195974
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH 1/2] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] = 0;
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
github-actions[bot] wrote: :warning: C/C++ code linter, clang-tidy found issues in your code. :warning: You can test this locally with the following command: ```bash git diff -U0 origin/main...HEAD -- clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp | python3 clang-tools-extra/clang-tidy/tool/clang-tidy-diff.py -path build -p1 -quiet ``` View the output from clang-tidy here. ``` clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:56:5: warning: variable 'Content' of type 'llvm::StringRef' can be declared 'const' [misc-const-correctness] 56 | llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1); | ^ | const clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:162:3: warning: variable 'FmtStr' of type 'llvm::StringRef' can be declared 'const' [misc-const-correctness] 162 | llvm::StringRef FmtStr = FmtLiteral->getString(); | ^ | const clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp:187:10: warning: variable 'Index' of type 'unsigned int' can be declared 'const' [misc-const-correctness] 187 | for (unsigned Index : Parsed.Indices) | ^ | const ``` https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione edited https://github.com/llvm/llvm-project/pull/195974 ___ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
llvmorg-github-actions[bot] wrote:
@llvm/pr-subscribers-clang-tidy
Author: Dave Lee (kastiglione)
Changes
---
Full diff: https://github.com/llvm/llvm-project/pull/195974.diff
8 Files Affected:
- (modified) clang-tools-extra/clang-tidy/llvm/CMakeLists.txt (+1)
- (added) clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp (+202)
- (added) clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h (+42)
- (modified) clang-tools-extra/clang-tidy/llvm/LLVMTidyModule.cpp (+2)
- (modified) clang-tools-extra/docs/clang-tidy/checks/list.rst (+1)
- (added) clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
(+60)
- (added)
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
(+28)
- (added) clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
(+58)
``diff
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] = 0;
+
+ // Parse "name1:idx1;name2:idx2;..." from AdditionalFunctions.
+ llvm::StringRef Input(AdditionalFunctions);
+ while (!Input.empty()) {
+auto [Entry, Rest] = Input.split(';');
+Input = Rest;
+if (Entry.empty())
+ continue;
+auto [Name, IdxStr] = Entry.rsplit(':');
+unsigned Idx = 0;
+if (Name.empty() || IdxStr.empty() || IdxStr.ge
[clang-tools-extra] [clang-tidy] Add `llvm-formatv-string` (PR #195974)
https://github.com/kastiglione created
https://github.com/llvm/llvm-project/pull/195974
None
>From 4a3911083c1d07236693b6bd40d1e02344a05b6c Mon Sep 17 00:00:00 2001
From: Dave Lee
Date: Tue, 5 May 2026 18:34:33 -0700
Subject: [PATCH] [clang-tidy] Add `llvm-formatv-string`
---
.../clang-tidy/llvm/CMakeLists.txt| 1 +
.../clang-tidy/llvm/FormatvStringCheck.cpp| 202 ++
.../clang-tidy/llvm/FormatvStringCheck.h | 42
.../clang-tidy/llvm/LLVMTidyModule.cpp| 2 +
.../docs/clang-tidy/checks/list.rst | 1 +
.../clang-tidy/checks/llvm/formatv-string.rst | 60 ++
.../llvm/formatv-string-additional.cpp| 28 +++
.../checkers/llvm/formatv-string.cpp | 58 +
8 files changed, 394 insertions(+)
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
create mode 100644 clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.h
create mode 100644
clang-tools-extra/docs/clang-tidy/checks/llvm/formatv-string.rst
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string-additional.cpp
create mode 100644
clang-tools-extra/test/clang-tidy/checkers/llvm/formatv-string.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
index c81882e0e2024..bec3ba50c81c5 100644
--- a/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
+++ b/clang-tools-extra/clang-tidy/llvm/CMakeLists.txt
@@ -4,6 +4,7 @@ set(LLVM_LINK_COMPONENTS
)
add_clang_library(clangTidyLLVMModule STATIC
+ FormatvStringCheck.cpp
HeaderGuardCheck.cpp
IncludeOrderCheck.cpp
LLVMTidyModule.cpp
diff --git a/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
new file mode 100644
index 0..6bfd66b002b47
--- /dev/null
+++ b/clang-tools-extra/clang-tidy/llvm/FormatvStringCheck.cpp
@@ -0,0 +1,202 @@
+//===--===//
+//
+// 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 "FormatvStringCheck.h"
+#include "clang/AST/Expr.h"
+#include "clang/ASTMatchers/ASTMatchers.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallBitVector.h"
+#include "llvm/ADT/SmallVector.h"
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+
+using namespace clang::ast_matchers;
+
+namespace clang::tidy::llvm_check {
+
+namespace {
+
+struct ParseResult {
+ llvm::SmallVector Indices;
+ unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected parseFormatvString(llvm::StringRef Fmt) {
+ ParseResult Result;
+ unsigned NextAutoIndex = 0;
+ bool HasAutomatic = false;
+ bool HasExplicit = false;
+
+ while (!Fmt.empty()) {
+const size_t OpenBrace = Fmt.find('{');
+if (OpenBrace == llvm::StringRef::npos)
+ break;
+
+Fmt = Fmt.drop_front(OpenBrace);
+
+// Handle escaped braces '{{'.
+if (Fmt.size() > 1 && Fmt[1] == '{') {
+ Fmt = Fmt.drop_front(2);
+ continue;
+}
+
+// Find the closing '}'.
+const size_t CloseBrace = Fmt.find('}');
+if (CloseBrace == llvm::StringRef::npos)
+ return llvm::createStringError("unterminated brace in format string");
+
+// Extract the content between braces.
+llvm::StringRef Content = Fmt.substr(1, CloseBrace - 1);
+Fmt = Fmt.drop_front(CloseBrace + 1);
+
+// Parse the replacement field: [index] ["," layout] [":" format]
+llvm::StringRef IndexStr = Content;
+
+// Strip layout and format parts for index parsing.
+const size_t CommaPos = Content.find(',');
+const size_t ColonPos = Content.find(':');
+if (CommaPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, CommaPos);
+else if (ColonPos != llvm::StringRef::npos)
+ IndexStr = Content.substr(0, ColonPos);
+
+IndexStr = IndexStr.trim();
+
+unsigned Index = 0;
+if (IndexStr.empty()) {
+ Index = NextAutoIndex++;
+ HasAutomatic = true;
+} else {
+ if (IndexStr.getAsInteger(10, Index))
+return llvm::createStringError("invalid replacement index");
+ HasExplicit = true;
+}
+
+Result.Indices.push_back(Index);
+Result.MaxIndex = std::max(Result.MaxIndex, Index);
+ }
+
+ if (HasAutomatic && HasExplicit)
+return llvm::createStringError(
+"format string mixes automatic and explicit indices");
+
+ return Result;
+}
+
+FormatvStringCheck::FormatvStringCheck(StringRef Name,
+ ClangTidyContext *Context)
+: ClangTidyCheck(Name, Context),
+ AdditionalFunctions(Options.get("AdditionalFunctions", "")) {
+ // Always check llvm::formatv (both overloads).
+ Functions["llvm::formatv"] =
