[Lldb-commits] [clang] [lldb] [llvm] [mlir] [Support] Validate number of arguments passed to formatv() (PR #105745)

Rahul Joshi via lldb-commits lldb-commits at lists.llvm.org
Tue Aug 27 08:21:41 PDT 2024


https://github.com/jurahul updated https://github.com/llvm/llvm-project/pull/105745

>From 9ae8a0361b0d26cf29cc4547658baec0c2654c89 Mon Sep 17 00:00:00 2001
From: Rahul Joshi <rjoshi at nvidia.com>
Date: Thu, 22 Aug 2024 08:47:02 -0700
Subject: [PATCH] [Support] Detect invalid formatv() calls

- Detect formatv() calls where the number of replacement parameters
  expected after parsing the format string does not match the number
  provides in the formatv() call.
- assert() in debug builds, and fail the formatv() call in release
  builds by just emitting an error message in the stream.
---
 .../Checkers/CheckPlacementNew.cpp            |   2 +-
 .../Checkers/StdLibraryFunctionsChecker.cpp   |   2 +-
 .../Plugins/SymbolFile/DWARF/DWARFUnit.cpp    |   2 +-
 llvm/include/llvm/Support/FormatVariadic.h    |  54 ++++--
 llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp    |   3 +-
 .../Orc/CompileOnDemandLayer.cpp              |   8 +-
 llvm/lib/Support/FormatVariadic.cpp           | 172 +++++++++++-------
 .../tools/llvm-pdbutil/ExplainOutputStyle.cpp |   4 +-
 llvm/unittests/Support/FormatVariadicTest.cpp | 115 +++++++++---
 llvm/utils/TableGen/IntrinsicEmitter.cpp      |   4 +-
 .../mlir-linalg-ods-yaml-gen.cpp              |   4 +-
 .../tools/mlir-tblgen/LLVMIRConversionGen.cpp |   2 +-
 mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp   |  13 +-
 mlir/tools/mlir-tblgen/OpFormatGen.cpp        |  14 +-
 mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp      |   3 +-
 15 files changed, 253 insertions(+), 149 deletions(-)

diff --git a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
index 99e11a15c08dc2..1b89951397cfb1 100644
--- a/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/CheckPlacementNew.cpp
@@ -131,7 +131,7 @@ bool PlacementNewChecker::checkPlaceCapacityIsSufficient(
             "Storage provided to placement new is only {0} bytes, "
             "whereas the allocated array type requires more space for "
             "internal needs",
-            SizeOfPlaceCI->getValue(), SizeOfTargetCI->getValue()));
+            SizeOfPlaceCI->getValue()));
       else
         Msg = std::string(llvm::formatv(
             "Storage provided to placement new is only {0} bytes, "
diff --git a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
index 8f4bd17afc8581..60c035612dcd44 100644
--- a/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
+++ b/clang/lib/StaticAnalyzer/Checkers/StdLibraryFunctionsChecker.cpp
@@ -1401,7 +1401,7 @@ void StdLibraryFunctionsChecker::checkPostCall(const CallEvent &Call,
         ErrnoNote =
             llvm::formatv("After calling '{0}' {1}", FunctionName, ErrnoNote);
     } else {
-      CaseNote = llvm::formatv(Case.getNote().str().c_str(), FunctionName);
+      CaseNote = llvm::formatvv(Case.getNote().str().c_str(), FunctionName);
     }
     const SVal RV = Call.getReturnValue();
 
diff --git a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
index feb72f6244a18c..cdb266f70f0e3e 100644
--- a/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
+++ b/lldb/source/Plugins/SymbolFile/DWARF/DWARFUnit.cpp
@@ -655,7 +655,7 @@ DWARFUnit::GetDIE(dw_offset_t die_offset) {
 
   if (!ContainsDIEOffset(die_offset)) {
     GetSymbolFileDWARF().GetObjectFile()->GetModule()->ReportError(
-        "GetDIE for DIE {0:x16} is outside of its CU {0:x16}", die_offset,
+        "GetDIE for DIE {0:x16} is outside of its CU {1:x16}", die_offset,
         GetOffset());
     return DWARFDIE(); // Not found
   }
diff --git a/llvm/include/llvm/Support/FormatVariadic.h b/llvm/include/llvm/Support/FormatVariadic.h
index 595f2cf559a428..b42e24646b31b5 100644
--- a/llvm/include/llvm/Support/FormatVariadic.h
+++ b/llvm/include/llvm/Support/FormatVariadic.h
@@ -66,24 +66,24 @@ struct ReplacementItem {
 class formatv_object_base {
 protected:
   StringRef Fmt;
+  bool Validate;
   ArrayRef<support::detail::format_adapter *> Adapters;
 
-  static bool consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
-                                 size_t &Align, char &Pad);
-
-  static std::pair<ReplacementItem, StringRef>
-  splitLiteralAndReplacement(StringRef Fmt);
-
-  formatv_object_base(StringRef Fmt,
+  formatv_object_base(StringRef Fmt, bool Validate,
                       ArrayRef<support::detail::format_adapter *> Adapters)
-      : Fmt(Fmt), Adapters(Adapters) {}
+      : Fmt(Fmt), Validate(Validate), Adapters(Adapters) {}
 
   formatv_object_base(formatv_object_base const &rhs) = delete;
   formatv_object_base(formatv_object_base &&rhs) = default;
 
 public:
   void format(raw_ostream &S) const {
-    for (auto &R : parseFormatString(Fmt)) {
+    const auto [Replacements, IsValid] =
+        parseFormatString(S, Fmt, Adapters.size(), Validate);
+    if (!IsValid)
+      return;
+
+    for (const auto &R : Replacements) {
       if (R.Type == ReplacementType::Empty)
         continue;
       if (R.Type == ReplacementType::Literal) {
@@ -101,9 +101,13 @@ class formatv_object_base {
       Align.format(S, R.Options);
     }
   }
-  static SmallVector<ReplacementItem, 2> parseFormatString(StringRef Fmt);
 
-  static std::optional<ReplacementItem> parseReplacementItem(StringRef Spec);
+  // Parse format string and return the array of replacement items. If there is
+  // an error in the format string, return false for the second member of the
+  // pair, and print the error message to `S`.
+  static std::pair<SmallVector<ReplacementItem, 2>, bool>
+  parseFormatString(raw_ostream &S, StringRef Fmt, size_t NumArgs,
+                    bool Validate);
 
   std::string str() const {
     std::string Result;
@@ -149,8 +153,8 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
   };
 
 public:
-  formatv_object(StringRef Fmt, Tuple &&Params)
-      : formatv_object_base(Fmt, ParameterPointers),
+  formatv_object(StringRef Fmt, bool ValidateNumArgs, Tuple &&Params)
+      : formatv_object_base(Fmt, ValidateNumArgs, ParameterPointers),
         Parameters(std::move(Params)) {
     ParameterPointers = std::apply(create_adapters(), Parameters);
   }
@@ -174,7 +178,7 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
 //
 // rep_field ::= "{" index ["," layout] [":" format] "}"
 // index     ::= <non-negative integer>
-// layout    ::= [[[char]loc]width]
+// layout    ::= [[[pad]loc]width]
 // format    ::= <any string not containing "{" or "}">
 // char      ::= <any character except "{" or "}">
 // loc       ::= "-" | "=" | "+"
@@ -187,7 +191,7 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
 // format  - A type-dependent string used to provide additional options to
 //           the formatting operation.  Refer to the documentation of the
 //           various individual format providers for per-type options.
-// char    - The padding character.  Defaults to ' ' (space).  Only valid if
+// pad    - The padding character.  Defaults to ' ' (space).  Only valid if
 //           `loc` is also specified.
 // loc     - Where to print the formatted text within the field.  Only valid if
 //           `width` is also specified.
@@ -247,6 +251,8 @@ template <typename Tuple> class formatv_object : public formatv_object_base {
 // assertion.  Otherwise, it will try to do something reasonable, but in general
 // the details of what that is are undefined.
 //
+
+// formatv() with validation enabled.
 template <typename... Ts>
 inline auto formatv(const char *Fmt, Ts &&...Vals)
     -> formatv_object<decltype(std::make_tuple(
@@ -254,8 +260,22 @@ inline auto formatv(const char *Fmt, Ts &&...Vals)
   using ParamTuple = decltype(std::make_tuple(
       support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
   return formatv_object<ParamTuple>(
-      Fmt, std::make_tuple(support::detail::build_format_adapter(
-               std::forward<Ts>(Vals))...));
+      Fmt, /*Validate=*/true,
+      std::make_tuple(
+          support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
+}
+
+// formatvv() perform no argument/format string validation.
+template <typename... Ts>
+inline auto formatvv(const char *Fmt, Ts &&...Vals)
+    -> formatv_object<decltype(std::make_tuple(
+        support::detail::build_format_adapter(std::forward<Ts>(Vals))...))> {
+  using ParamTuple = decltype(std::make_tuple(
+      support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
+  return formatv_object<ParamTuple>(
+      Fmt, /*Validate=*/false,
+      std::make_tuple(
+          support::detail::build_format_adapter(std::forward<Ts>(Vals))...));
 }
 
 } // end namespace llvm
diff --git a/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp b/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp
index 77e8ece9439cf9..eb2751ab30ac50 100644
--- a/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp
+++ b/llvm/lib/DebugInfo/DWARF/DWARFVerifier.cpp
@@ -1518,8 +1518,7 @@ DWARFVerifier::verifyNameIndexAbbrevs(const DWARFDebugNames::NameIndex &NI) {
         error() << formatv("NameIndex @ {0:x}: Indexing multiple compile units "
                            "and abbreviation {1:x} has no DW_IDX_compile_unit "
                            "or DW_IDX_type_unit attribute.\n",
-                           NI.getUnitOffset(), Abbrev.Code,
-                           dwarf::DW_IDX_compile_unit);
+                           NI.getUnitOffset(), Abbrev.Code);
       });
       ++NumErrors;
     }
diff --git a/llvm/lib/ExecutionEngine/Orc/CompileOnDemandLayer.cpp b/llvm/lib/ExecutionEngine/Orc/CompileOnDemandLayer.cpp
index 6448adaa0ceb36..49f239b73e8a01 100644
--- a/llvm/lib/ExecutionEngine/Orc/CompileOnDemandLayer.cpp
+++ b/llvm/lib/ExecutionEngine/Orc/CompileOnDemandLayer.cpp
@@ -348,10 +348,10 @@ void CompileOnDemandLayer::emitPartition(
             HC = hash_combine(HC, hash_combine_range(GVName.begin(), GVName.end()));
           }
           raw_string_ostream(SubModuleName)
-            << ".submodule."
-            << formatv(sizeof(size_t) == 8 ? "{0:x16}" : "{0:x8}",
-                       static_cast<size_t>(HC))
-            << ".ll";
+              << ".submodule."
+              << formatv(sizeof(size_t) == 8 ? "{0:x16}" : "{0:x8}",
+                         static_cast<size_t>(HC))
+              << ".ll";
         }
 
         // Extract the requested partiton (plus any necessary aliases) and
diff --git a/llvm/lib/Support/FormatVariadic.cpp b/llvm/lib/Support/FormatVariadic.cpp
index e25d036cdf1e8c..f86c0d05b5d6b3 100644
--- a/llvm/lib/Support/FormatVariadic.cpp
+++ b/llvm/lib/Support/FormatVariadic.cpp
@@ -6,8 +6,10 @@
 //===----------------------------------------------------------------------===//
 
 #include "llvm/Support/FormatVariadic.h"
+#include "llvm/ADT/SmallSet.h"
 #include <cassert>
 #include <optional>
+#include <variant>
 
 using namespace llvm;
 
@@ -25,8 +27,8 @@ static std::optional<AlignStyle> translateLocChar(char C) {
   LLVM_BUILTIN_UNREACHABLE;
 }
 
-bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
-                                             size_t &Align, char &Pad) {
+static bool consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
+                               size_t &Align, char &Pad) {
   Where = AlignStyle::Right;
   Align = 0;
   Pad = ' ';
@@ -35,8 +37,7 @@ bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
 
   if (Spec.size() > 1) {
     // A maximum of 2 characters at the beginning can be used for something
-    // other
-    // than the width.
+    // other than the width.
     // If Spec[1] is a loc char, then Spec[0] is a pad char and Spec[2:...]
     // contains the width.
     // Otherwise, if Spec[0] is a loc char, then Spec[1:...] contains the width.
@@ -55,8 +56,8 @@ bool formatv_object_base::consumeFieldLayout(StringRef &Spec, AlignStyle &Where,
   return !Failed;
 }
 
-std::optional<ReplacementItem>
-formatv_object_base::parseReplacementItem(StringRef Spec) {
+static std::variant<ReplacementItem, StringRef>
+parseReplacementItem(StringRef Spec) {
   StringRef RepString = Spec.trim("{}");
 
   // If the replacement sequence does not start with a non-negative integer,
@@ -67,14 +68,12 @@ formatv_object_base::parseReplacementItem(StringRef Spec) {
   StringRef Options;
   size_t Index = 0;
   RepString = RepString.trim();
-  if (RepString.consumeInteger(0, Index)) {
-    assert(false && "Invalid replacement sequence index!");
-    return ReplacementItem{};
-  }
+  if (RepString.consumeInteger(0, Index))
+    return "Invalid replacement sequence index!";
   RepString = RepString.trim();
   if (RepString.consume_front(",")) {
     if (!consumeFieldLayout(RepString, Where, Align, Pad))
-      assert(false && "Invalid replacement field layout specification!");
+      return "Invalid replacement field layout specification!";
   }
   RepString = RepString.trim();
   if (RepString.consume_front(":")) {
@@ -82,77 +81,110 @@ formatv_object_base::parseReplacementItem(StringRef Spec) {
     RepString = StringRef();
   }
   RepString = RepString.trim();
-  if (!RepString.empty()) {
-    assert(false && "Unexpected characters found in replacement string!");
-  }
-
+  if (!RepString.empty())
+    return "Unexpected character found in replacement string!";
   return ReplacementItem{Spec, Index, Align, Where, Pad, Options};
 }
 
-std::pair<ReplacementItem, StringRef>
-formatv_object_base::splitLiteralAndReplacement(StringRef Fmt) {
-  while (!Fmt.empty()) {
-    // Everything up until the first brace is a literal.
-    if (Fmt.front() != '{') {
-      std::size_t BO = Fmt.find_first_of('{');
-      return std::make_pair(ReplacementItem{Fmt.substr(0, BO)}, Fmt.substr(BO));
-    }
-
-    StringRef Braces = Fmt.take_while([](char C) { return C == '{'; });
-    // If there is more than one brace, then some of them are escaped.  Treat
-    // these as replacements.
-    if (Braces.size() > 1) {
-      size_t NumEscapedBraces = Braces.size() / 2;
-      StringRef Middle = Fmt.take_front(NumEscapedBraces);
-      StringRef Right = Fmt.drop_front(NumEscapedBraces * 2);
-      return std::make_pair(ReplacementItem{Middle}, Right);
-    }
-    // An unterminated open brace is undefined. Assert to indicate that this is
-    // undefined and that we consider it an error. When asserts are disabled,
-    // build a replacement item with an error message.
-    std::size_t BC = Fmt.find_first_of('}');
-    if (BC == StringRef::npos) {
-      assert(
-          false &&
-          "Unterminated brace sequence. Escape with {{ for a literal brace.");
-      return std::make_pair(
-          ReplacementItem{"Unterminated brace sequence. Escape with {{ for a "
-                          "literal brace."},
-          StringRef());
-    }
-
-    // Even if there is a closing brace, if there is another open brace before
-    // this closing brace, treat this portion as literal, and try again with the
-    // next one.
-    std::size_t BO2 = Fmt.find_first_of('{', 1);
-    if (BO2 < BC)
-      return std::make_pair(ReplacementItem{Fmt.substr(0, BO2)},
-                            Fmt.substr(BO2));
-
-    StringRef Spec = Fmt.slice(1, BC);
-    StringRef Right = Fmt.substr(BC + 1);
-
-    auto RI = parseReplacementItem(Spec);
-    if (RI)
-      return std::make_pair(*RI, Right);
+static std::variant<std::pair<ReplacementItem, StringRef>, StringRef>
+splitLiteralAndReplacement(StringRef Fmt) {
+  // Everything up until the first brace is a literal.
+  if (Fmt.front() != '{') {
+    std::size_t BO = Fmt.find_first_of('{');
+    return std::make_pair(ReplacementItem(Fmt.substr(0, BO)), Fmt.substr(BO));
+  }
 
-    // If there was an error parsing the replacement item, treat it as an
-    // invalid replacement spec, and just continue.
-    Fmt = Fmt.drop_front(BC + 1);
+  StringRef Braces = Fmt.take_while([](char C) { return C == '{'; });
+  // If there is more than one brace, then some of them are escaped.  Treat
+  // these as replacements.
+  if (Braces.size() > 1) {
+    size_t NumEscapedBraces = Braces.size() / 2;
+    StringRef Middle = Fmt.take_front(NumEscapedBraces);
+    StringRef Right = Fmt.drop_front(NumEscapedBraces * 2);
+    return std::make_pair(ReplacementItem(Middle), Right);
   }
-  return std::make_pair(ReplacementItem{Fmt}, StringRef());
+  // An unterminated open brace is undefined. Assert to indicate that this is
+  // undefined and that we consider it an error. When asserts are disabled,
+  // build a replacement item with an error message.
+  std::size_t BC = Fmt.find_first_of('}');
+  if (BC == StringRef::npos)
+    return "Unterminated brace sequence. Escape with {{ for a literal brace.";
+
+  // Even if there is a closing brace, if there is another open brace before
+  // this closing brace, treat this portion as literal, and try again with the
+  // next one.
+  std::size_t BO2 = Fmt.find_first_of('{', 1);
+  if (BO2 < BC)
+    return std::make_pair(ReplacementItem{Fmt.substr(0, BO2)}, Fmt.substr(BO2));
+
+  StringRef Spec = Fmt.slice(1, BC);
+  StringRef Right = Fmt.substr(BC + 1);
+
+  auto RI = parseReplacementItem(Spec);
+  if (const StringRef *ErrMsg = std::get_if<1>(&RI))
+    return *ErrMsg;
+
+  return std::make_pair(std::get<0>(RI), Right);
 }
 
-SmallVector<ReplacementItem, 2>
-formatv_object_base::parseFormatString(StringRef Fmt) {
+std::pair<SmallVector<ReplacementItem, 2>, bool>
+formatv_object_base::parseFormatString(raw_ostream &S, const StringRef Fmt,
+                                       size_t NumArgs, bool Validate) {
   SmallVector<ReplacementItem, 2> Replacements;
   ReplacementItem I;
-  while (!Fmt.empty()) {
-    std::tie(I, Fmt) = splitLiteralAndReplacement(Fmt);
+  size_t NumExpectedArgs = 0;
+
+  // Make a copy for pasring as it updates it.
+  StringRef ParseFmt = Fmt;
+  while (!ParseFmt.empty()) {
+    auto RI = splitLiteralAndReplacement(ParseFmt);
+    if (const StringRef *ErrMsg = std::get_if<1>(&RI)) {
+      // If there was an error parsing the format string, write the error to the
+      // stream, and return false as second member of the pair.
+      errs() << "Invalid format string: " << Fmt << "\n";
+      assert(0 && "Invalid format string");
+      S << *ErrMsg;
+      return {{}, false};
+    }
+    std::tie(I, ParseFmt) = std::get<0>(RI);
     if (I.Type != ReplacementType::Empty)
       Replacements.push_back(I);
+    if (I.Type == ReplacementType::Format)
+      NumExpectedArgs = std::max(NumExpectedArgs, I.Index + 1);
   }
-  return Replacements;
+  if (!Validate)
+    return {Replacements, true};
+
+  // Perform additional validation. Verify that the number of arguments matches
+  // the number of replacement indices and that there are no holes in the
+  // replacement indexes.
+  if (NumExpectedArgs != NumArgs) {
+    errs() << "Expected " << NumExpectedArgs << " Args, but got " << NumArgs
+           << " for format string '" << Fmt << "'\n";
+    assert(0 && "Invalid formatv() call");
+    S << "Expected " << NumExpectedArgs << " Args, but got " << NumArgs
+      << " for format string '" << Fmt << "'\n";
+    return {{}, false};
+  }
+
+  SmallSet<size_t, 2> Indices;
+  for (const ReplacementItem &R : Replacements) {
+    if (R.Type != ReplacementType::Format)
+      continue;
+    Indices.insert(R.Index);
+  }
+
+  if (Indices.size() != NumExpectedArgs) {
+    errs() << "Invalid format string: Replacement field indices "
+              "cannot have holes for format string '"
+           << Fmt << "'\n";
+    assert(0 && "Invalid format string");
+    S << "Replacement field indices cannot have holes for format string '"
+      << Fmt << "'\n";
+    return {{}, false};
+  }
+
+  return {Replacements, true};
 }
 
 void support::detail::format_adapter::anchor() {}
diff --git a/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp b/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp
index ce9d659335b30b..200e9037de3cbe 100644
--- a/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp
+++ b/llvm/tools/llvm-pdbutil/ExplainOutputStyle.cpp
@@ -139,8 +139,8 @@ bool ExplainOutputStyle::explainPdbBlockStatus() {
                  FileOffset, File.pdb().getFileSize());
     return false;
   }
-  P.formatLine("Block:Offset = {2:X-}:{1:X-4}.", FileOffset, pdbBlockOffset(),
-               pdbBlockIndex());
+  P.formatLine("Block:Offset = {0:X-}:{1:X-4}.", pdbBlockIndex(),
+               pdbBlockOffset());
 
   bool IsFree = File.pdb().getMsfLayout().FreePageMap[pdbBlockIndex()];
   P.formatLine("Address is in block {0} ({1}allocated).", pdbBlockIndex(),
diff --git a/llvm/unittests/Support/FormatVariadicTest.cpp b/llvm/unittests/Support/FormatVariadicTest.cpp
index a78b25c53d7e43..23399babe7337e 100644
--- a/llvm/unittests/Support/FormatVariadicTest.cpp
+++ b/llvm/unittests/Support/FormatVariadicTest.cpp
@@ -9,9 +9,11 @@
 #include "llvm/Support/FormatVariadic.h"
 #include "llvm/Support/Error.h"
 #include "llvm/Support/FormatAdapters.h"
+#include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
 using namespace llvm;
+using ::testing::HasSubstr;
 
 // Compile-time tests templates in the detail namespace.
 namespace {
@@ -35,14 +37,38 @@ struct NoFormat {};
 static_assert(uses_missing_provider<NoFormat>::value, "");
 }
 
+// Helper to parse format string with no validation.
+static SmallVector<ReplacementItem, 2> parseFormatString(StringRef Fmt) {
+  std::string Error;
+  raw_string_ostream ErrorStream(Error);
+  auto [Replacements, IsValid] =
+      formatv_object_base::parseFormatString(ErrorStream, Fmt, 0, false);
+  ErrorStream.flush();
+  assert(IsValid && Error.empty());
+  return Replacements;
+}
+
+#if NDEBUG
+// Helper for parse format string with errors.
+static std::string parseFormatStringError(StringRef Fmt) {
+  std::string Error;
+  raw_string_ostream ErrorStream(Error);
+  auto [Replacements, IsValid] =
+      formatv_object_base::parseFormatString(ErrorStream, Fmt, 0, false);
+  ErrorStream.flush();
+  assert(!IsValid && Replacements.empty());
+  return Error;
+}
+#endif
+
 TEST(FormatVariadicTest, EmptyFormatString) {
-  auto Replacements = formatv_object_base::parseFormatString("");
+  auto Replacements = parseFormatString("");
   EXPECT_EQ(0U, Replacements.size());
 }
 
 TEST(FormatVariadicTest, NoReplacements) {
   const StringRef kFormatString = "This is a test";
-  auto Replacements = formatv_object_base::parseFormatString(kFormatString);
+  auto Replacements = parseFormatString(kFormatString);
   ASSERT_EQ(1U, Replacements.size());
   EXPECT_EQ(kFormatString, Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type);
@@ -50,25 +76,25 @@ TEST(FormatVariadicTest, NoReplacements) {
 
 TEST(FormatVariadicTest, EscapedBrace) {
   // {{ should be replaced with {
-  auto Replacements = formatv_object_base::parseFormatString("{{");
+  auto Replacements = parseFormatString("{{");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("{", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type);
 
   // An even number N of braces should be replaced with N/2 braces.
-  Replacements = formatv_object_base::parseFormatString("{{{{{{");
+  Replacements = parseFormatString("{{{{{{");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("{{{", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type);
 
   // } does not require doubling up.
-  Replacements = formatv_object_base::parseFormatString("}");
+  Replacements = parseFormatString("}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("}", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type);
 
   // } does not require doubling up.
-  Replacements = formatv_object_base::parseFormatString("}}}");
+  Replacements = parseFormatString("}}}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("}}}", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Literal, Replacements[0].Type);
@@ -76,14 +102,14 @@ TEST(FormatVariadicTest, EscapedBrace) {
 
 TEST(FormatVariadicTest, ValidReplacementSequence) {
   // 1. Simple replacement - parameter index only
-  auto Replacements = formatv_object_base::parseFormatString("{0}");
+  auto Replacements = parseFormatString("{0}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
   EXPECT_EQ(0u, Replacements[0].Align);
   EXPECT_EQ("", Replacements[0].Options);
 
-  Replacements = formatv_object_base::parseFormatString("{1}");
+  Replacements = parseFormatString("{1}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(1u, Replacements[0].Index);
@@ -92,7 +118,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("", Replacements[0].Options);
 
   // 2. Parameter index with right alignment
-  Replacements = formatv_object_base::parseFormatString("{0,3}");
+  Replacements = parseFormatString("{0,3}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -101,7 +127,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("", Replacements[0].Options);
 
   // 3. And left alignment
-  Replacements = formatv_object_base::parseFormatString("{0,-3}");
+  Replacements = parseFormatString("{0,-3}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -110,7 +136,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("", Replacements[0].Options);
 
   // 4. And center alignment
-  Replacements = formatv_object_base::parseFormatString("{0,=3}");
+  Replacements = parseFormatString("{0,=3}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -119,7 +145,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("", Replacements[0].Options);
 
   // 4. Parameter index with option string
-  Replacements = formatv_object_base::parseFormatString("{0:foo}");
+  Replacements = parseFormatString("{0:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -128,7 +154,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("foo", Replacements[0].Options);
 
   // 5. Parameter index with alignment before option string
-  Replacements = formatv_object_base::parseFormatString("{0,-3:foo}");
+  Replacements = parseFormatString("{0,-3:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -137,7 +163,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("foo", Replacements[0].Options);
 
   // 7. Parameter indices, options, and alignment can all have whitespace.
-  Replacements = formatv_object_base::parseFormatString("{ 0, -3 : foo }");
+  Replacements = parseFormatString("{ 0, -3 : foo }");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -147,7 +173,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
 
   // 8. Everything after the first option specifier is part of the style, even
   // if it contains another option specifier.
-  Replacements = formatv_object_base::parseFormatString("{0:0:1}");
+  Replacements = parseFormatString("{0:0:1}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("0:0:1", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
@@ -157,7 +183,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("0:1", Replacements[0].Options);
 
   // 9. Custom padding character
-  Replacements = formatv_object_base::parseFormatString("{0,p+4:foo}");
+  Replacements = parseFormatString("{0,p+4:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("0,p+4:foo", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
@@ -168,7 +194,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ("foo", Replacements[0].Options);
 
   // Format string special characters are allowed as padding character
-  Replacements = formatv_object_base::parseFormatString("{0,-+4:foo}");
+  Replacements = parseFormatString("{0,-+4:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("0,-+4:foo", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
@@ -178,7 +204,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ('-', Replacements[0].Pad);
   EXPECT_EQ("foo", Replacements[0].Options);
 
-  Replacements = formatv_object_base::parseFormatString("{0,+-4:foo}");
+  Replacements = parseFormatString("{0,+-4:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("0,+-4:foo", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
@@ -188,7 +214,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ('+', Replacements[0].Pad);
   EXPECT_EQ("foo", Replacements[0].Options);
 
-  Replacements = formatv_object_base::parseFormatString("{0,==4:foo}");
+  Replacements = parseFormatString("{0,==4:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("0,==4:foo", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
@@ -198,7 +224,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
   EXPECT_EQ('=', Replacements[0].Pad);
   EXPECT_EQ("foo", Replacements[0].Options);
 
-  Replacements = formatv_object_base::parseFormatString("{0,:=4:foo}");
+  Replacements = parseFormatString("{0,:=4:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ("0,:=4:foo", Replacements[0].Spec);
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
@@ -211,7 +237,7 @@ TEST(FormatVariadicTest, ValidReplacementSequence) {
 
 TEST(FormatVariadicTest, DefaultReplacementValues) {
   // 2. If options string is missing, it defaults to empty.
-  auto Replacements = formatv_object_base::parseFormatString("{0,3}");
+  auto Replacements = parseFormatString("{0,3}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -219,7 +245,7 @@ TEST(FormatVariadicTest, DefaultReplacementValues) {
   EXPECT_EQ("", Replacements[0].Options);
 
   // Including if the colon is present but contains no text.
-  Replacements = formatv_object_base::parseFormatString("{0,3:}");
+  Replacements = parseFormatString("{0,3:}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(0u, Replacements[0].Index);
@@ -227,7 +253,7 @@ TEST(FormatVariadicTest, DefaultReplacementValues) {
   EXPECT_EQ("", Replacements[0].Options);
 
   // 3. If alignment is missing, it defaults to 0, right, space
-  Replacements = formatv_object_base::parseFormatString("{0:foo}");
+  Replacements = parseFormatString("{0:foo}");
   ASSERT_EQ(1u, Replacements.size());
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
   EXPECT_EQ(AlignStyle::Right, Replacements[0].Where);
@@ -238,8 +264,7 @@ TEST(FormatVariadicTest, DefaultReplacementValues) {
 }
 
 TEST(FormatVariadicTest, MultipleReplacements) {
-  auto Replacements =
-      formatv_object_base::parseFormatString("{0} {1:foo}-{2,-3:bar}");
+  auto Replacements = parseFormatString("{0} {1:foo}-{2,-3:bar}");
   ASSERT_EQ(5u, Replacements.size());
   // {0}
   EXPECT_EQ(ReplacementType::Format, Replacements[0].Type);
@@ -271,6 +296,28 @@ TEST(FormatVariadicTest, MultipleReplacements) {
   EXPECT_EQ("bar", Replacements[4].Options);
 }
 
+#ifdef NDEBUG // Disable the test in debug builds where it will assert.
+TEST(FormatVariadicTest, FormatStringError) {
+  // Missing index.
+  std::string Error = parseFormatStringError("{}");
+  EXPECT_THAT(Error, HasSubstr("Invalid replacement sequence index"));
+
+  // Invalid character after layout.
+  Error = parseFormatStringError("{0, +h}");
+  EXPECT_THAT(Error,
+              HasSubstr("Invalid replacement field layout specification"));
+
+  // Invalid character after the alignment.
+  Error = parseFormatStringError("{0, +20,}");
+  EXPECT_THAT(Error,
+              HasSubstr("Unexpected character found in replacement string"));
+
+  // Un-escaped {
+  Error = parseFormatStringError("{");
+  EXPECT_THAT(Error, HasSubstr("Unterminated brace sequence"));
+}
+#endif // NDEBUG
+
 TEST(FormatVariadicTest, FormatNoReplacements) {
   EXPECT_EQ("", formatv("").str());
   EXPECT_EQ("Test", formatv("Test").str());
@@ -500,7 +547,7 @@ struct format_tuple {
   explicit format_tuple(const char *Fmt) : Fmt(Fmt) {}
 
   template <typename... Ts> auto operator()(Ts &&... Values) const {
-    return formatv(Fmt, std::forward<Ts>(Values)...);
+    return formatvv(Fmt, std::forward<Ts>(Values)...);
   }
 };
 
@@ -516,12 +563,12 @@ TEST(FormatVariadicTest, BigTest) {
             .08215f, (void *)nullptr, 0, 6.62E-34, -908234908423,
             908234908422234, std::numeric_limits<double>::infinity(), 0x0)};
   // Test long string formatting with many edge cases combined.
-  const char *Intro =
+  const char Intro[] =
       "There are {{{0}} items in the tuple, and {{{1}} tuple(s) in the array.";
-  const char *Header =
+  const char Header[] =
       "{0,6}|{1,8}|{2,=10}|{3,=10}|{4,=13}|{5,7}|{6,7}|{7,10}|{8,"
       "-7}|{9,10}|{10,16}|{11,17}|{12,6}|{13,4}";
-  const char *Line =
+  const char Line[] =
       "{0,6}|{1,8:X}|{2,=10}|{3,=10:5}|{4,=13}|{5,7:3}|{6,7:P2}|{7,"
       "10:X8}|{8,-7:N}|{9,10:E4}|{10,16:N}|{11,17:D}|{12,6}|{13,"
       "4:X}";
@@ -704,6 +751,16 @@ TEST(FormatVariadicTest, FormatFilterRange) {
   EXPECT_EQ("1, 2, 3", formatv("{0}", Range).str());
 }
 
+#ifdef NDEBUG // Disable the test in debug builds where it will assert.
+TEST(FormatVariadicTest, Validate) {
+  std::string Str = formatv("{0}", 1, 2).str();
+  EXPECT_THAT(Str, HasSubstr("Expected 1 Args, but got 2"));
+
+  Str = formatv("{0} {2}", 1, 2, 3).str();
+  EXPECT_THAT(Str, HasSubstr("eplacement field indices cannot have holes"));
+}
+#endif // NDEBUG
+
 namespace {
 
 enum class Base { First };
diff --git a/llvm/utils/TableGen/IntrinsicEmitter.cpp b/llvm/utils/TableGen/IntrinsicEmitter.cpp
index 4c211cdca84c5f..af8ff889918428 100644
--- a/llvm/utils/TableGen/IntrinsicEmitter.cpp
+++ b/llvm/utils/TableGen/IntrinsicEmitter.cpp
@@ -735,8 +735,8 @@ Intrinsic::getIntrinsicFor{1}Builtin(StringRef TargetPrefix,
     const auto &[Map, CommonPrefix] = Entry;
     if (TargetPrefix.empty())
       continue;
-    OS << formatv(R"(    {{"{0}", {0}Names, "{2}"},)", TargetPrefix,
-                  TargetPrefix, CommonPrefix)
+    OS << formatv(R"(    {{"{0}", {0}Names, "{1}"},)", TargetPrefix,
+                  CommonPrefix)
        << "\n";
   }
   OS << "  };\n";
diff --git a/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp b/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp
index 7311cdd39d0755..a00f12661f7120 100644
--- a/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp
+++ b/mlir/tools/mlir-linalg-ods-gen/mlir-linalg-ods-yaml-gen.cpp
@@ -634,7 +634,7 @@ ArrayAttr {0}::getIndexingMaps() {{
   MLIRContext *context = getContext();
   auto symbolBindings = getSymbolBindings(*this);
   SmallVector<AffineMap> maps;
-  {2}
+  {1}
   cached = Builder(context).getAffineMapArrayAttr(maps);
   getOperation()->setAttr(memoizeAttr, cached);
   return cached;
@@ -929,7 +929,7 @@ exprs.push_back(getAffineConstantExpr(cst{1}, context));
         // TODO: This needs to be memoized and/or converted to non-parser based
         // C++ codegen prior to real use.
         os << llvm::formatv(structuredOpIndexingMapsFormat, className,
-                            dimIdentsStr, interleaveToString(stmts, "\n  "));
+                            interleaveToString(stmts, "\n  "));
       }
     } else {
       os << llvm::formatv(rankPolyStructuredOpIndexingMapsFormat, className);
diff --git a/mlir/tools/mlir-tblgen/LLVMIRConversionGen.cpp b/mlir/tools/mlir-tblgen/LLVMIRConversionGen.cpp
index ebadfe4499a54d..5560298831865f 100644
--- a/mlir/tools/mlir-tblgen/LLVMIRConversionGen.cpp
+++ b/mlir/tools/mlir-tblgen/LLVMIRConversionGen.cpp
@@ -519,7 +519,7 @@ static void emitOneCEnumFromConversion(const llvm::Record *record,
   os << formatv(
       "inline LLVM_ATTRIBUTE_UNUSED {0}::{1} convert{1}FromLLVM(int64_t "
       "value) {{\n",
-      cppNamespace, cppClassName, llvmClass);
+      cppNamespace, cppClassName);
   os << "  switch (value) {\n";
 
   for (const auto &enumerant : enumAttr.getAllCases()) {
diff --git a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
index 66dbb16760ebb0..572c1545b43fcb 100644
--- a/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpDefinitionsGen.cpp
@@ -1378,7 +1378,7 @@ void OpEmitter::genPropertiesSupport() {
                ::llvm::function_ref<::mlir::InFlightDiagnostic()> emitError) -> ::mlir::LogicalResult {{
         {0}
       };
-      {2};
+      {1};
 )decl";
   const char *attrGetNoDefaultFmt = R"decl(;
       if (attr && ::mlir::failed(setFromAttr(prop.{0}, attr, emitError)))
@@ -1419,7 +1419,7 @@ void OpEmitter::genPropertiesSupport() {
                                      &fctx.addSubst("_attr", propertyAttr)
                                           .addSubst("_storage", propertyStorage)
                                           .addSubst("_diag", propertyDiag)),
-                               name, getAttr);
+                               getAttr);
       if (prop.hasStorageTypeValueOverride()) {
         setPropMethod << formatv(attrGetDefaultFmt, name,
                                  prop.getStorageTypeValueOverride());
@@ -2768,11 +2768,10 @@ void OpEmitter::genInferredTypeCollectiveParamBuilder() {
          << "u && \"mismatched number of return types\");";
   body << "\n    " << builderOpState << ".addTypes(inferredReturnTypes);";
 
-  body << formatv(R"(
-  } else {{
+  body << R"(
+  } else {
     ::llvm::report_fatal_error("Failed to infer result type(s).");
-  })",
-                  opClass.getClassName(), builderOpState);
+  })";
 }
 
 void OpEmitter::genUseOperandAsResultTypeSeparateParamBuilder() {
@@ -3882,7 +3881,7 @@ void OpEmitter::genSuccessorVerifier(MethodBody &body) {
 
     auto getSuccessor =
         formatv(successor.isVariadic() ? "{0}()" : getSingleSuccessor,
-                successor.name, it.index())
+                successor.name)
             .str();
     auto constraintFn =
         staticVerifierEmitter.getSuccessorConstraintFn(successor.constraint);
diff --git a/mlir/tools/mlir-tblgen/OpFormatGen.cpp b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
index 9a95f495b77658..6558e76dc6cb0b 100644
--- a/mlir/tools/mlir-tblgen/OpFormatGen.cpp
+++ b/mlir/tools/mlir-tblgen/OpFormatGen.cpp
@@ -1122,7 +1122,7 @@ static void genCustomDirectiveParser(CustomDirective *dir, MethodBody &body,
             "      {0}Operands.append(subRange.begin(), subRange.end());\n"
             "      {0}OperandGroupSizes.push_back(subRange.size());\n"
             "    }\n",
-            var->name, var->constraint.getVariadicOfVariadicSegmentSizeAttr());
+            var->name);
       }
     } else if (auto *dir = dyn_cast<TypeDirective>(param)) {
       ArgumentLengthKind lengthKind;
@@ -1575,9 +1575,7 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body,
     ArgumentLengthKind lengthKind = getArgumentLengthKind(operand->getVar());
     StringRef name = operand->getVar()->name;
     if (lengthKind == ArgumentLengthKind::VariadicOfVariadic)
-      body << llvm::formatv(
-          variadicOfVariadicOperandParserCode, name,
-          operand->getVar()->constraint.getVariadicOfVariadicSegmentSizeAttr());
+      body << llvm::formatv(variadicOfVariadicOperandParserCode, name);
     else if (lengthKind == ArgumentLengthKind::Variadic)
       body << llvm::formatv(variadicOperandParserCode, name);
     else if (lengthKind == ArgumentLengthKind::Optional)
@@ -1656,12 +1654,12 @@ void OperationFormat::genElementParser(FormatElement *element, MethodBody &body,
           dir->shouldBeQualified() ? qualifiedTypeParserCode : typeParserCode;
       TypeSwitch<FormatElement *>(dir->getArg())
           .Case<OperandVariable, ResultVariable>([&](auto operand) {
-            body << formatv(parserCode,
-                            operand->getVar()->constraint.getCppType(),
-                            listName);
+            body << formatvv(parserCode,
+                             operand->getVar()->constraint.getCppType(),
+                             listName);
           })
           .Default([&](auto operand) {
-            body << formatv(parserCode, "::mlir::Type", listName);
+            body << formatvv(parserCode, "::mlir::Type", listName);
           });
     }
   } else if (auto *dir = dyn_cast<FunctionalTypeDirective>(element)) {
diff --git a/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp b/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp
index 9aeb14d14eeca5..ec211ad3519ce3 100644
--- a/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp
+++ b/mlir/tools/mlir-tblgen/SPIRVUtilsGen.cpp
@@ -780,8 +780,7 @@ static void emitSerializationFunction(const Record *attrClass,
     os << formatv("  (void)emitDebugLine(functionBody, {0}.getLoc());\n",
                   opVar);
     os << formatv("  (void)encodeInstructionInto("
-                  "functionBody, spirv::Opcode::{1}, {2});\n",
-                  op.getQualCppClassName(),
+                  "functionBody, spirv::Opcode::{0}, {1});\n",
                   record->getValueAsString("spirvOpName"), operands);
   }
 



More information about the lldb-commits mailing list