@@ -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