[vm/sendports] Introduce an api that can be safely used to rebuild SendPort.

Existing api is not aware of origin_id, which leads to problems when a port gets closed, origin_id can't be properly restored.

Fixes https://github.com/dart-lang/sdk/issues/55605
TEST=vm/dart/sendport_api_test

Change-Id: Ia8a5978a968f6ea643d7921c64146cebf17d2e0e
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/365120
Reviewed-by: Ryan Macnak <rmacnak@google.com>
Commit-Queue: Alexander Aprelev <aam@google.com>
diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h
index 040887b..1f2c0c4 100644
--- a/runtime/include/dart_api.h
+++ b/runtime/include/dart_api.h
@@ -1522,6 +1522,10 @@
  * A port is used to send or receive inter-isolate messages
  */
 typedef int64_t Dart_Port;
+typedef struct {
+  int64_t port_id;
+  int64_t origin_id;
+} Dart_PortEx;
 
 /**
  * ILLEGAL_PORT is a port number guaranteed never to be associated with a valid
@@ -1773,6 +1777,10 @@
 /**
  * Returns a new SendPort with the provided port id.
  *
+ * If there is a possibility of a port closing since port_id was acquired
+ * for a SendPort, one should use Dart_NewSendPortEx and
+ * Dart_SendPortGetIdEx.
+ *
  * \param port_id The destination port.
  *
  * \return A new SendPort if no errors occurs. Otherwise returns
@@ -1781,6 +1789,16 @@
 DART_EXPORT Dart_Handle Dart_NewSendPort(Dart_Port port_id);
 
 /**
+ * Returns a new SendPort with the provided port id and origin id.
+ *
+ * \param portex_id The destination composte port id.
+ *
+ * \return A new SendPort if no errors occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_NewSendPortEx(Dart_PortEx portex_id);
+
+/**
  * Gets the SendPort id for the provided SendPort.
  * \param port A SendPort object whose id is desired.
  * \param port_id Returns the id of the SendPort.
@@ -1790,6 +1808,15 @@
 DART_EXPORT Dart_Handle Dart_SendPortGetId(Dart_Handle port,
                                            Dart_Port* port_id);
 
+/**
+ * Gets the SendPort and Origin ids for the provided SendPort.
+ * \param port A SendPort object whose id is desired.
+ * \param portex_id Returns composite id of the SendPort.
+ * \return Success if no error occurs. Otherwise returns
+ *   an error handle.
+ */
+DART_EXPORT Dart_Handle Dart_SendPortGetIdEx(Dart_Handle port,
+                                             Dart_PortEx* portex_id);
 /*
  * ======
  * Scopes
diff --git a/runtime/include/dart_api_dl.h b/runtime/include/dart_api_dl.h
index 5088b433..2b4c8d4 100644
--- a/runtime/include/dart_api_dl.h
+++ b/runtime/include/dart_api_dl.h
@@ -38,6 +38,10 @@
 // are typechecked nominally in C/C++, so they are not copied, instead a
 // comment is added to their definition.
 typedef int64_t Dart_Port_DL;
+typedef struct {
+  int64_t port_id;
+  int64_t origin_id;
+} Dart_PortEx_DL;
 
 typedef void (*Dart_NativeMessageHandler_DL)(Dart_Port_DL dest_port_id,
                                              Dart_CObject* message);
@@ -94,8 +98,11 @@
   /* Dart_Port */                                                              \
   F(Dart_Post, bool, (Dart_Port_DL port_id, Dart_Handle object))               \
   F(Dart_NewSendPort, Dart_Handle, (Dart_Port_DL port_id))                     \
+  F(Dart_NewSendPortEx, Dart_Handle, (Dart_PortEx_DL portex_id))               \
   F(Dart_SendPortGetId, Dart_Handle,                                           \
     (Dart_Handle port, Dart_Port_DL * port_id))                                \
+  F(Dart_SendPortGetIdEx, Dart_Handle,                                         \
+    (Dart_Handle port, Dart_PortEx_DL * portex_id))                            \
   /* Scopes */                                                                 \
   F(Dart_EnterScope, void, (void))                                             \
   F(Dart_ExitScope, void, (void))                                              \
diff --git a/runtime/include/dart_version.h b/runtime/include/dart_version.h
index cb343c0..5ca0b68 100644
--- a/runtime/include/dart_version.h
+++ b/runtime/include/dart_version.h
@@ -11,6 +11,6 @@
 // On backwards compatible changes the minor version is increased.
 // The versioning covers the symbols exposed in dart_api_dl.h
 #define DART_API_DL_MAJOR_VERSION 2
-#define DART_API_DL_MINOR_VERSION 4
+#define DART_API_DL_MINOR_VERSION 5
 
 #endif /* RUNTIME_INCLUDE_DART_VERSION_H_ */ /* NOLINT */
diff --git a/runtime/tests/vm/dart/exported_symbols_test.dart b/runtime/tests/vm/dart/exported_symbols_test.dart
index 708a80e..c16130e 100644
--- a/runtime/tests/vm/dart/exported_symbols_test.dart
+++ b/runtime/tests/vm/dart/exported_symbols_test.dart
@@ -248,6 +248,7 @@
     "Dart_NewNativePort",
     "Dart_NewPersistentHandle",
     "Dart_NewSendPort",
+    "Dart_NewSendPortEx",
     "Dart_NewStringFromCString",
     "Dart_NewStringFromUTF16",
     "Dart_NewStringFromUTF32",
@@ -280,6 +281,7 @@
     "Dart_RunLoopAsync",
     "Dart_ScopeAllocate",
     "Dart_SendPortGetId",
+    "Dart_SendPortGetIdEx",
     "Dart_ServiceSendDataEvent",
     "Dart_SetBooleanReturnValue",
     "Dart_SetCurrentUserTag",
diff --git a/runtime/tests/vm/dart/sendport_api_test.dart b/runtime/tests/vm/dart/sendport_api_test.dart
new file mode 100644
index 0000000..2df8005
--- /dev/null
+++ b/runtime/tests/vm/dart/sendport_api_test.dart
@@ -0,0 +1,61 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'dart:isolate';
+import 'dart:ffi';
+
+import 'package:async_helper/async_helper.dart' show asyncEnd, asyncStart;
+import 'package:expect/expect.dart';
+import 'package:ffi/ffi.dart';
+
+@Native<Handle Function(Int64 port)>(symbol: 'Dart_NewSendPort')
+external SendPort newSendPort(int obj);
+
+@Native<Handle Function(Handle, Pointer<Int64>)>(symbol: 'Dart_SendPortGetId')
+external Object sendPortGetId(Object sendPort, Pointer<Int64> outId);
+
+final class PortEx extends Struct {
+  @Int64()
+  external int portId;
+  @Int64()
+  external int originId;
+}
+
+@Native<Handle Function(PortEx portex)>(symbol: 'Dart_NewSendPortEx')
+external SendPort newSendPortEx(PortEx portEx);
+
+@Native<Handle Function(Handle, Pointer<PortEx>)>(
+    symbol: 'Dart_SendPortGetIdEx')
+external Object sendPortGetIdEx(Object sendPort, Pointer<PortEx> outPortExId);
+
+class A {}
+
+main() async {
+  asyncStart();
+
+  final rp = ReceivePort();
+  rp.close();
+
+  {
+    Pointer<Int64> portId = calloc();
+    sendPortGetId(rp.sendPort, portId);
+    final sendPortThroughAPI = newSendPort(portId.value);
+    sendPortThroughAPI.send('A');
+    Expect.throwsArgumentError(() {
+      sendPortThroughAPI.send(A());
+    });
+    calloc.free(portId);
+  }
+
+  {
+    Pointer<PortEx> portExId = calloc();
+    sendPortGetIdEx(rp.sendPort, portExId);
+    final sendPortThroughAPI = newSendPortEx(portExId[0]);
+    sendPortThroughAPI.send('A');
+    sendPortThroughAPI.send(A());
+    calloc.free(portExId);
+  }
+
+  asyncEnd();
+}
diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc
index 001bb82..b8b7f25 100644
--- a/runtime/vm/dart_api_impl.cc
+++ b/runtime/vm/dart_api_impl.cc
@@ -2157,6 +2157,17 @@
   return Api::NewHandle(T, SendPort::New(port_id, origin_id));
 }
 
+DART_EXPORT Dart_Handle Dart_NewSendPortEx(Dart_PortEx portex_id) {
+  DARTSCOPE(Thread::Current());
+  CHECK_CALLBACK_STATE(T);
+  if (portex_id.port_id == ILLEGAL_PORT) {
+    return Api::NewError("%s: illegal port_id %" Pd64 ".", CURRENT_FUNC,
+                         portex_id.port_id);
+  }
+  return Api::NewHandle(T,
+                        SendPort::New(portex_id.port_id, portex_id.origin_id));
+}
+
 DART_EXPORT Dart_Handle Dart_SendPortGetId(Dart_Handle port,
                                            Dart_Port* port_id) {
   DARTSCOPE(Thread::Current());
@@ -2173,6 +2184,23 @@
   return Api::Success();
 }
 
+DART_EXPORT Dart_Handle Dart_SendPortGetIdEx(Dart_Handle port,
+                                             Dart_PortEx* portex_id) {
+  DARTSCOPE(Thread::Current());
+  CHECK_CALLBACK_STATE(T);
+  API_TIMELINE_DURATION(T);
+  const SendPort& send_port = Api::UnwrapSendPortHandle(Z, port);
+  if (send_port.IsNull()) {
+    RETURN_TYPE_ERROR(Z, port, SendPort);
+  }
+  if (portex_id == nullptr) {
+    RETURN_NULL_ERROR(port_id);
+  }
+  portex_id->port_id = send_port.Id();
+  portex_id->origin_id = send_port.origin_id();
+  return Api::Success();
+}
+
 DART_EXPORT Dart_Port Dart_GetMainPortId() {
   Isolate* isolate = Isolate::Current();
   CHECK_ISOLATE(isolate);