[gardening] Fix memory leak of Dart::FeaturesString() buffer

The CL in [0] introduced a new usage of Dart::FeaturesString() but
missed to free the resulting buffer (it's not a compile-time constant
but rather built via TextBuffer).

Closes https://github.com/dart-lang/sdk/issues/47460

[0] https://dart-review.googlesource.com/c/sdk/+/215945

TEST=Fixes asan failures, e.g. vm/cc/DartAPI_InvokeVMServiceMethod

Change-Id: I954bd4a0af5959f1c38d4b70978bef6c6baed407
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/216802
Commit-Queue: Martin Kustermann <kustermann@google.com>
Reviewed-by: Ryan Macnak <rmacnak@google.com>
diff --git a/runtime/vm/app_snapshot.cc b/runtime/vm/app_snapshot.cc
index 3cc94ca..a27d27c 100644
--- a/runtime/vm/app_snapshot.cc
+++ b/runtime/vm/app_snapshot.cc
@@ -6842,13 +6842,13 @@
   const intptr_t version_len = strlen(expected_version);
   WriteBytes(reinterpret_cast<const uint8_t*>(expected_version), version_len);
 
-  const char* expected_features =
+  char* expected_features =
       Dart::FeaturesString(IsolateGroup::Current(), is_vm_snapshot, kind_);
   ASSERT(expected_features != NULL);
   const intptr_t features_len = strlen(expected_features);
   WriteBytes(reinterpret_cast<const uint8_t*>(expected_features),
              features_len + 1);
-  free(const_cast<char*>(expected_features));
+  free(expected_features);
 }
 
 #if !defined(DART_PRECOMPILED_RUNTIME)
diff --git a/runtime/vm/dart.cc b/runtime/vm/dart.cc
index b041eb8..ced30ca 100644
--- a/runtime/vm/dart.cc
+++ b/runtime/vm/dart.cc
@@ -1064,9 +1064,9 @@
   return Error::null();
 }
 
-const char* Dart::FeaturesString(IsolateGroup* isolate_group,
-                                 bool is_vm_isolate,
-                                 Snapshot::Kind kind) {
+char* Dart::FeaturesString(IsolateGroup* isolate_group,
+                           bool is_vm_isolate,
+                           Snapshot::Kind kind) {
   TextBuffer buffer(64);
 
 // Different fields are included for DEBUG/RELEASE/PRODUCT.
diff --git a/runtime/vm/dart.h b/runtime/vm/dart.h
index c22f1f0..9d0cdf1 100644
--- a/runtime/vm/dart.h
+++ b/runtime/vm/dart.h
@@ -111,9 +111,10 @@
   static uword AllocateReadOnlyHandle();
   static bool IsReadOnlyHandle(uword address);
 
-  static const char* FeaturesString(IsolateGroup* isolate_group,
-                                    bool is_vm_snapshot,
-                                    Snapshot::Kind kind);
+  // The returned string has to be free()ed.
+  static char* FeaturesString(IsolateGroup* isolate_group,
+                              bool is_vm_snapshot,
+                              Snapshot::Kind kind);
   static Snapshot::Kind vm_snapshot_kind() { return vm_snapshot_kind_; }
 
   static Dart_ThreadExitCallback thread_exit_callback() {
diff --git a/runtime/vm/service.cc b/runtime/vm/service.cc
index fd3b9ee..a0a5b9c 100644
--- a/runtime/vm/service.cc
+++ b/runtime/vm/service.cc
@@ -4889,7 +4889,9 @@
 #else
   Snapshot::Kind kind = Snapshot::kFullJIT;
 #endif
-  jsobj.AddProperty("_features", Dart::FeaturesString(nullptr, true, kind));
+  char* features_string = Dart::FeaturesString(nullptr, true, kind);
+  jsobj.AddProperty("_features", features_string);
+  free(features_string);
   jsobj.AddProperty("_profilerMode", FLAG_profile_vm ? "VM" : "Dart");
   jsobj.AddProperty64("pid", OS::ProcessId());
   jsobj.AddPropertyTimeMillis(