[Mlir-commits] [mlir] [MLIR] Fix use-after-free in Remark by owning string data (PR #179889)
Guray Ozen
llvmlistbot at llvm.org
Thu Feb 5 02:29:48 PST 2026
https://github.com/grypp updated https://github.com/llvm/llvm-project/pull/179889
>From 94458c2df4453c04f8a9d497489237701cbc939b Mon Sep 17 00:00:00 2001
From: Guray Ozen <gozen at nvidia.com>
Date: Thu, 5 Feb 2026 10:01:16 +0100
Subject: [PATCH] [MLIR] Fix use-after-free in Remark by owning string data
Change Remark's StringRef members to std::string to ensure remarks own
their data, preventing dangling pointers when used with RemarkEmittingPolicyFinal.
---
mlir/include/mlir/IR/Remarks.h | 27 ++++++-----
mlir/unittests/IR/RemarkTest.cpp | 79 ++++++++++++++++++++++++++++++++
2 files changed, 95 insertions(+), 11 deletions(-)
diff --git a/mlir/include/mlir/IR/Remarks.h b/mlir/include/mlir/IR/Remarks.h
index 3102542731b33..0a787f20c5704 100644
--- a/mlir/include/mlir/IR/Remarks.h
+++ b/mlir/include/mlir/IR/Remarks.h
@@ -89,9 +89,10 @@ class Remark {
public:
Remark(RemarkKind remarkKind, DiagnosticSeverity severity, Location loc,
RemarkOpts opts)
- : remarkKind(remarkKind), functionName(opts.functionName), loc(loc),
- categoryName(opts.categoryName), subCategoryName(opts.subCategoryName),
- remarkName(opts.remarkName) {
+ : remarkKind(remarkKind), functionName(opts.functionName.str()), loc(loc),
+ categoryName(opts.categoryName.str()),
+ subCategoryName(opts.subCategoryName.str()),
+ remarkName(opts.remarkName.str()) {
if (!categoryName.empty() && !subCategoryName.empty()) {
(llvm::Twine(categoryName) + ":" + subCategoryName)
.toStringRef(fullCategoryName);
@@ -183,21 +184,25 @@ class Remark {
/// Keeps the MLIR diagnostic kind, which is used to determine the
/// diagnostic kind in the LLVM remark streamer.
RemarkKind remarkKind;
- /// Name of the convering function like interface
- StringRef functionName;
+ /// Name of the covering function like interface.
+ /// Stored as std::string to ensure the Remark owns its data.
+ std::string functionName;
Location loc;
- /// Sub category passname e.g., "Unroll" or "UnrollAndJam"
- StringRef categoryName;
+ /// Category name e.g., "Unroll" or "UnrollAndJam".
+ /// Stored as std::string to ensure the Remark owns its data.
+ std::string categoryName;
- /// Sub category name "Loop Optimizer"
- StringRef subCategoryName;
+ /// Sub category name e.g., "Loop Optimizer".
+ /// Stored as std::string to ensure the Remark owns its data.
+ std::string subCategoryName;
/// Combined name for category and sub-category
SmallString<64> fullCategoryName;
- /// Remark identifier
- StringRef remarkName;
+ /// Remark identifier.
+ /// Stored as std::string to ensure the Remark owns its data.
+ std::string remarkName;
/// Args collected via the streaming interface.
SmallVector<Arg, 4> args;
diff --git a/mlir/unittests/IR/RemarkTest.cpp b/mlir/unittests/IR/RemarkTest.cpp
index dca86632071d4..df8e8c8cc066b 100644
--- a/mlir/unittests/IR/RemarkTest.cpp
+++ b/mlir/unittests/IR/RemarkTest.cpp
@@ -409,4 +409,83 @@ TEST(Remark, TestArgWithAttribute) {
EXPECT_FALSE(argWithoutAttr.getAttribute()); // Returns null Attribute
EXPECT_EQ(argWithoutAttr.val, "Value");
}
+
+// Test that Remark correctly owns its string data and doesn't have
+// use-after-free issues when the original strings go out of scope.
+// This is particularly important for RemarkEmittingPolicyFinal which
+// stores remarks and emits them later during finalize().
+TEST(Remark, TestRemarkOwnsStringData) {
+ testing::internal::CaptureStderr();
+
+ // These are the expected values we'll check for in the output.
+ // They must match what we create in the inner scope below.
+ const char *expectedCategory = "DynamicCategory";
+ const char *expectedName = "DynamicRemarkName";
+ const char *expectedFunction = "dynamicFunction";
+ const char *expectedMessage = "Dynamic message content";
+
+ {
+ MLIRContext context;
+ Location loc = FileLineColLoc::get(&context, "test.cpp", 42, 10);
+
+ // Setup with RemarkEmittingPolicyFinal - this stores remarks and emits
+ // them only when the engine is destroyed (during finalize).
+ // Note: The 'passed' filter must be set for remark::passed() to emit.
+ mlir::remark::RemarkCategories cats{
+ /*all=*/std::nullopt,
+ /*passed=*/expectedCategory, // Enable passed remarks for this category
+ /*missed=*/std::nullopt,
+ /*analysis=*/std::nullopt,
+ /*failed=*/std::nullopt};
+
+ std::unique_ptr<remark::RemarkEmittingPolicyFinal> policy =
+ std::make_unique<remark::RemarkEmittingPolicyFinal>();
+ LogicalResult isEnabled = remark::enableOptimizationRemarks(
+ context, std::make_unique<MyCustomStreamer>(), std::move(policy), cats,
+ /*printAsEmitRemarks=*/true);
+ ASSERT_TRUE(succeeded(isEnabled)) << "Failed to enable remark engine";
+
+ // Create dynamic strings in an inner scope that will go out of scope
+ // BEFORE the RemarkEngine is destroyed and finalize() is called.
+ {
+ std::string dynamicCategory(expectedCategory);
+ std::string dynamicName(expectedName);
+ std::string dynamicFunction(expectedFunction);
+ std::string dynamicSubCategory("DynamicSubCategory");
+ std::string dynamicMessage(expectedMessage);
+
+ // Emit a remark with all dynamic strings
+ remark::passed(loc, remark::RemarkOpts::name(dynamicName)
+ .category(dynamicCategory)
+ .subCategory(dynamicSubCategory)
+ .function(dynamicFunction))
+ << dynamicMessage;
+
+ // dynamicCategory, dynamicName, dynamicFunction, dynamicSubCategory,
+ // and dynamicMessage all go out of scope here!
+ }
+
+ // At this point, all the dynamic strings have been destroyed.
+ // The Remark stored in RemarkEmittingPolicyFinal must have its own
+ // copies of the string data, otherwise we'd have dangling pointers.
+
+ // Context destruction triggers RemarkEngine destruction, which calls
+ // finalize() on the policy, which then emits the stored remarks.
+ // If Remark doesn't own its strings, this would crash or produce garbage.
+ }
+
+ llvm::errs().flush();
+ std::string errOut = ::testing::internal::GetCapturedStderr();
+
+ // Verify the output contains our expected strings - this proves the
+ // Remark correctly copied and owns the string data.
+ EXPECT_NE(errOut.find(expectedCategory), std::string::npos)
+ << "Expected category not found in output. Got: " << errOut;
+ EXPECT_NE(errOut.find(expectedName), std::string::npos)
+ << "Expected name not found in output. Got: " << errOut;
+ EXPECT_NE(errOut.find(expectedFunction), std::string::npos)
+ << "Expected function not found in output. Got: " << errOut;
+ EXPECT_NE(errOut.find(expectedMessage), std::string::npos)
+ << "Expected message not found in output. Got: " << errOut;
+}
} // namespace
More information about the Mlir-commits
mailing list