Repository navigation
[fml] fml::Status keeps a std::string_view, so a status built from a runtime string dangles #194151
Description
Steps to reproduce
fml::Status (engine/src/flutter/fml/status.h) stores its message as a std::string_view:
class Status final {
...
private:
fml::StatusCode code_;
std::string_view message_;
};The constructor takes a std::string_view, so passing a std::string compiles without a warning, and the status then points into that string's buffer.
- Build a status from a
std::string. - Change or destroy the string.
- Read
status.message().
There is one such caller on main today, in impeller/renderer/backend/vulkan/command_queue_vk.cc (CommandQueueVK::Submit):
auto submit_callback = [&context, &vk_buffers](vk::Fence submit_fence) {
...
if (status != vk::Result::eSuccess) {
std::string submit_error =
"Failed to submit command: " + vk::to_string(status);
VALIDATION_LOG << submit_error;
return fml::Status(fml::StatusCode::kCancelled, submit_error);
}
return fml::Status();
};FenceWaiterVK::AddFence returns this status, and CommandQueueVK::Submit hands it to its own caller after submit_error is gone.
Expected results
message() returns the message the status was built with, for as long as the status lives.
Actual results
message() returns whatever the string's buffer holds now: "secon" after the string was changed to "second", or freed memory after it was destroyed. When a Vulkan queue submit fails, anyone who reads the returned status.message() reads freed heap memory: garbage in a log, a crash under ASan. Every other caller in the tree passes a string literal, so nothing goes wrong until someone formats a message at runtime.
Suggested fix
Store the message as a std::string. message() keeps returning std::string_view, so no caller has to change. It costs one copy of the message when a non-OK status is created; OK statuses have an empty message and don't allocate. I'd rather fix the type than the one caller, because the next caller that formats a message would fall into the same trap.
Code sample
Minimal reproduction
std::string message = "first";
fml::Status status(fml::StatusCode::kInternal, message);
message.assign("second");
// status.message() is now "secon".
fml::Status later;
{
std::string long_message = "a message long enough to live on the heap ...";
later = fml::Status(fml::StatusCode::kCancelled, long_message);
}
later.message(); // use after freeFlutter Doctor output
Found in an engine built from source: macOS arm64, host_debug_unopt_arm64, fml_unittests; fml/status.h and command_queue_vk.cc are the same on current main.
Doctor output
[✓] Flutter (Channel stable, 3.47.0, on macOS 27.2 26B5091g darwin-arm64)
• Framework revision 4cf2416426, 2026-08-11
• Engine revision 5f77625673
• Dart version 3.13.0
[!] Xcode - develop for iOS and macOS (Xcode 27.0)
Activity
suryaKommana2662 commentedon Oct 12, 2026
@dzolotov Thanks for the report and for linking the proposed fix #194161 . I'm routing this issue to
team-enginefor further investigation.