Skip to content

[fml] fml::Status keeps a std::string_view, so a status built from a runtime string dangles #194151

Description

@dzolotov

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.

  1. Build a status from a std::string.
  2. Change or destroy the string.
  3. 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 free

Flutter 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

  1. suryaKommana2662 commented on Oct 12, 2026

    @suryaKommana2662

    @dzolotov Thanks for the report and for linking the proposed fix #194161 . I'm routing this issue to team-engine for further investigation.

  2. added
    engineflutter/engine related. See also e: labels.
    and removed
    in triagePresently being triaged by the triage team
    on Oct 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    engineflutter/engine related. See also e: labels.team-engineOwned by Engine team

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions