================
@@ -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<unsigned, 4> Indices;
+  unsigned MaxIndex = 0;
+};
+
+} // namespace
+
+static llvm::Expected<ParseResult> 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<llvm::StringRef> 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<CallExpr>("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

Reply via email to