Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 213c7eb

Browse files
authored
[MLIR] Fix use-after-free in Remark by owning string data (#179889)
Change Remark's StringRef members to std::string to ensure remarks own their data, preventing dangling pointers when used with RemarkEmittingPolicyFinal.
1 parent 1214c96 commit 213c7eb

2 files changed

Lines changed: 95 additions & 11 deletions

File tree

mlir/include/mlir/IR/Remarks.h

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,10 @@ class Remark {
8989
public:
9090
Remark(RemarkKind remarkKind, DiagnosticSeverity severity, Location loc,
9191
RemarkOpts opts)
92-
: remarkKind(remarkKind), functionName(opts.functionName), loc(loc),
93-
categoryName(opts.categoryName), subCategoryName(opts.subCategoryName),
94-
remarkName(opts.remarkName) {
92+
: remarkKind(remarkKind), functionName(opts.functionName.str()), loc(loc),
93+
categoryName(opts.categoryName.str()),
94+
subCategoryName(opts.subCategoryName.str()),
95+
remarkName(opts.remarkName.str()) {
9596
if (!categoryName.empty() && !subCategoryName.empty()) {
9697
(llvm::Twine(categoryName) + ":" + subCategoryName)
9798
.toStringRef(fullCategoryName);
@@ -183,21 +184,25 @@ class Remark {
183184
/// Keeps the MLIR diagnostic kind, which is used to determine the
184185
/// diagnostic kind in the LLVM remark streamer.
185186
RemarkKind remarkKind;
186-
/// Name of the convering function like interface
187-
StringRef functionName;
187+
/// Name of the covering function like interface.
188+
/// Stored as std::string to ensure the Remark owns its data.
189+
std::string functionName;
188190

189191
Location loc;
190-
/// Sub category passname e.g., "Unroll" or "UnrollAndJam"
191-
StringRef categoryName;
192+
/// Category name e.g., "Unroll" or "UnrollAndJam".
193+
/// Stored as std::string to ensure the Remark owns its data.
194+
std::string categoryName;
192195

193-
/// Sub category name "Loop Optimizer"
194-
StringRef subCategoryName;
196+
/// Sub category name e.g., "Loop Optimizer".
197+
/// Stored as std::string to ensure the Remark owns its data.
198+
std::string subCategoryName;
195199

196200
/// Combined name for category and sub-category
197201
SmallString<64> fullCategoryName;
198202

199-
/// Remark identifier
200-
StringRef remarkName;
203+
/// Remark identifier.
204+
/// Stored as std::string to ensure the Remark owns its data.
205+
std::string remarkName;
201206

202207
/// Args collected via the streaming interface.
203208
SmallVector<Arg, 4> args;

mlir/unittests/IR/RemarkTest.cpp

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,4 +409,83 @@ TEST(Remark, TestArgWithAttribute) {
409409
EXPECT_FALSE(argWithoutAttr.getAttribute()); // Returns null Attribute
410410
EXPECT_EQ(argWithoutAttr.val, "Value");
411411
}
412+
413+
// Test that Remark correctly owns its string data and doesn't have
414+
// use-after-free issues when the original strings go out of scope.
415+
// This is particularly important for RemarkEmittingPolicyFinal which
416+
// stores remarks and emits them later during finalize().
417+
TEST(Remark, TestRemarkOwnsStringData) {
418+
testing::internal::CaptureStderr();
419+
420+
// These are the expected values we'll check for in the output.
421+
// They must match what we create in the inner scope below.
422+
const char *expectedCategory = "DynamicCategory";
423+
const char *expectedName = "DynamicRemarkName";
424+
const char *expectedFunction = "dynamicFunction";
425+
const char *expectedMessage = "Dynamic message content";
426+
427+
{
428+
MLIRContext context;
429+
Location loc = FileLineColLoc::get(&context, "test.cpp", 42, 10);
430+
431+
// Setup with RemarkEmittingPolicyFinal - this stores remarks and emits
432+
// them only when the engine is destroyed (during finalize).
433+
// Note: The 'passed' filter must be set for remark::passed() to emit.
434+
mlir::remark::RemarkCategories cats{
435+
/*all=*/std::nullopt,
436+
/*passed=*/expectedCategory, // Enable passed remarks for this category
437+
/*missed=*/std::nullopt,
438+
/*analysis=*/std::nullopt,
439+
/*failed=*/std::nullopt};
440+
441+
std::unique_ptr<remark::RemarkEmittingPolicyFinal> policy =
442+
std::make_unique<remark::RemarkEmittingPolicyFinal>();
443+
LogicalResult isEnabled = remark::enableOptimizationRemarks(
444+
context, std::make_unique<MyCustomStreamer>(), std::move(policy), cats,
445+
/*printAsEmitRemarks=*/true);
446+
ASSERT_TRUE(succeeded(isEnabled)) << "Failed to enable remark engine";
447+
448+
// Create dynamic strings in an inner scope that will go out of scope
449+
// BEFORE the RemarkEngine is destroyed and finalize() is called.
450+
{
451+
std::string dynamicCategory(expectedCategory);
452+
std::string dynamicName(expectedName);
453+
std::string dynamicFunction(expectedFunction);
454+
std::string dynamicSubCategory("DynamicSubCategory");
455+
std::string dynamicMessage(expectedMessage);
456+
457+
// Emit a remark with all dynamic strings
458+
remark::passed(loc, remark::RemarkOpts::name(dynamicName)
459+
.category(dynamicCategory)
460+
.subCategory(dynamicSubCategory)
461+
.function(dynamicFunction))
462+
<< dynamicMessage;
463+
464+
// dynamicCategory, dynamicName, dynamicFunction, dynamicSubCategory,
465+
// and dynamicMessage all go out of scope here!
466+
}
467+
468+
// At this point, all the dynamic strings have been destroyed.
469+
// The Remark stored in RemarkEmittingPolicyFinal must have its own
470+
// copies of the string data, otherwise we'd have dangling pointers.
471+
472+
// Context destruction triggers RemarkEngine destruction, which calls
473+
// finalize() on the policy, which then emits the stored remarks.
474+
// If Remark doesn't own its strings, this would crash or produce garbage.
475+
}
476+
477+
llvm::errs().flush();
478+
std::string errOut = ::testing::internal::GetCapturedStderr();
479+
480+
// Verify the output contains our expected strings - this proves the
481+
// Remark correctly copied and owns the string data.
482+
EXPECT_NE(errOut.find(expectedCategory), std::string::npos)
483+
<< "Expected category not found in output. Got: " << errOut;
484+
EXPECT_NE(errOut.find(expectedName), std::string::npos)
485+
<< "Expected name not found in output. Got: " << errOut;
486+
EXPECT_NE(errOut.find(expectedFunction), std::string::npos)
487+
<< "Expected function not found in output. Got: " << errOut;
488+
EXPECT_NE(errOut.find(expectedMessage), std::string::npos)
489+
<< "Expected message not found in output. Got: " << errOut;
490+
}
412491
} // namespace

0 commit comments

Comments
 (0)