[vm/ffi] Add `DynamicLibrary.close()`

This adds the `close()` method to `DynamicLibrary`, which uses
`dlclose()` on Unix and `FreeLibrary` on Windows to close a dynamic FFI
library.

TEST=tests/ffi/dylib_close_test.dart
Closes https://github.com/dart-lang/sdk/issues/40159

Cq-Include-Trybots: luci.dart.try:vm-ffi-android-debug-arm-try,vm-ffi-android-debug-arm64c-try,vm-ffi-qemu-linux-release-riscv64-try,vm-ffi-qemu-linux-release-arm-try,dart2wasm-linux-d8-try,dart2wasm-linux-chrome-try,vm-reload-rollback-linux-debug-x64-try,vm-reload-linux-debug-x64-try,vm-win-debug-x64c-try,vm-win-debug-x64-try,vm-aot-android-release-arm_x64-try,vm-mac-debug-arm64-try,vm-mac-debug-x64-try,vm-asan-linux-release-x64-try,vm-aot-msan-linux-release-x64-try,vm-aot-android-release-arm64c-try,vm-aot-android-release-arm_x64-try,vm-aot-linux-debug-x64-try,vm-aot-linux-debug-x64c-try,vm-aot-mac-release-x64-try,vm-aot-mac-release-arm64-try,vm-aot-win-debug-x64c-try,vm-aot-win-release-x64-try
CoreLibraryReviewExempt: `dart:ffi` VM only API.
Change-Id: I73af98677b481902fe548bdcee56964a0195faf0
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/307680
Reviewed-by: Daco Harkes <dacoharkes@google.com>
Commit-Queue: Daco Harkes <dacoharkes@google.com>
diff --git a/runtime/lib/ffi_dynamic_library.cc b/runtime/lib/ffi_dynamic_library.cc
index 0df6a41..09e159f 100644
--- a/runtime/lib/ffi_dynamic_library.cc
+++ b/runtime/lib/ffi_dynamic_library.cc
@@ -55,6 +55,9 @@
 DEFINE_NATIVE_ENTRY(Ffi_dl_getHandle, 0, 1) {
   SimulatorUnsupported();
 }
+DEFINE_NATIVE_ENTRY(Ffi_dl_close, 0, 1) {
+  SimulatorUnsupported();
+}
 DEFINE_NATIVE_ENTRY(Ffi_dl_providesSymbol, 0, 2) {
   SimulatorUnsupported();
 }
@@ -167,20 +170,46 @@
     free(error);
     Exceptions::ThrowArgumentError(msg);
   }
-  return DynamicLibrary::New(handle);
+  return DynamicLibrary::New(handle, true);
 }
 
 DEFINE_NATIVE_ENTRY(Ffi_dl_processLibrary, 0, 0) {
 #if defined(DART_HOST_OS_LINUX) || defined(DART_HOST_OS_MACOS) ||              \
     defined(DART_HOST_OS_ANDROID) || defined(DART_HOST_OS_FUCHSIA)
-  return DynamicLibrary::New(RTLD_DEFAULT);
+  return DynamicLibrary::New(RTLD_DEFAULT, false);
 #else
-  return DynamicLibrary::New(kWindowsDynamicLibraryProcessPtr);
+  return DynamicLibrary::New(kWindowsDynamicLibraryProcessPtr, false);
 #endif
 }
 
 DEFINE_NATIVE_ENTRY(Ffi_dl_executableLibrary, 0, 0) {
-  return DynamicLibrary::New(LoadDynamicLibrary(nullptr));
+  return DynamicLibrary::New(LoadDynamicLibrary(nullptr), false);
+}
+
+DEFINE_NATIVE_ENTRY(Ffi_dl_close, 0, 1) {
+  GET_NON_NULL_NATIVE_ARGUMENT(DynamicLibrary, dlib, arguments->NativeArgAt(0));
+  if (dlib.IsClosed()) {
+    // Already closed, nothing to do
+  } else if (!dlib.CanBeClosed()) {
+    const String& msg = String::Handle(
+        String::New("DynamicLibrary.process() and DynamicLibrary.executable() "
+                    "can't be closed."));
+    Exceptions::ThrowStateError(msg);
+  } else {
+    void* handle = dlib.GetHandle();
+    char* error = nullptr;
+    Utils::UnloadDynamicLibrary(handle, &error);
+
+    if (error == nullptr) {
+      dlib.SetClosed(true);
+    } else {
+      const String& msg = String::Handle(String::New(error));
+      free(error);
+      Exceptions::ThrowStateError(msg);
+    }
+  }
+
+  return Object::null();
 }
 
 DEFINE_NATIVE_ENTRY(Ffi_dl_lookup, 1, 2) {
@@ -188,6 +217,12 @@
   GET_NON_NULL_NATIVE_ARGUMENT(String, argSymbolName,
                                arguments->NativeArgAt(1));
 
+  if (dlib.IsClosed()) {
+    const String& msg =
+        String::Handle(String::New("Cannot lookup symbols in closed library."));
+    Exceptions::ThrowStateError(msg);
+  }
+
   void* handle = dlib.GetHandle();
 
   char* error = nullptr;
diff --git a/runtime/vm/bootstrap_natives.h b/runtime/vm/bootstrap_natives.h
index bfe2979..0cabfa0 100644
--- a/runtime/vm/bootstrap_natives.h
+++ b/runtime/vm/bootstrap_natives.h
@@ -365,6 +365,7 @@
   V(Ffi_asFunctionInternal, 2)                                                 \
   V(Ffi_pointerFromFunction, 1)                                                \
   V(Ffi_dl_open, 1)                                                            \
+  V(Ffi_dl_close, 1)                                                           \
   V(Ffi_dl_lookup, 2)                                                          \
   V(Ffi_dl_getHandle, 1)                                                       \
   V(Ffi_dl_providesSymbol, 2)                                                  \
diff --git a/runtime/vm/compiler/runtime_offsets_extracted.h b/runtime/vm/compiler/runtime_offsets_extracted.h
index 4810d21..705d180 100644
--- a/runtime/vm/compiler/runtime_offsets_extracted.h
+++ b/runtime/vm/compiler/runtime_offsets_extracted.h
@@ -632,7 +632,7 @@
     CompressedStackMaps_PayloadHeaderSize = 0x4;
 static constexpr dart::compiler::target::word Context_header_size = 0xc;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
-static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0x8;
+static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0xc;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -1345,7 +1345,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x18;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -2047,7 +2047,7 @@
     CompressedStackMaps_PayloadHeaderSize = 0x4;
 static constexpr dart::compiler::target::word Context_header_size = 0xc;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
-static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0x8;
+static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0xc;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -2762,7 +2762,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x18;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -3471,7 +3471,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x10;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -4182,7 +4182,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x10;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -4886,7 +4886,7 @@
     CompressedStackMaps_PayloadHeaderSize = 0x4;
 static constexpr dart::compiler::target::word Context_header_size = 0xc;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
-static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0x8;
+static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0xc;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -5600,7 +5600,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x18;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -6297,7 +6297,7 @@
     CompressedStackMaps_PayloadHeaderSize = 0x4;
 static constexpr dart::compiler::target::word Context_header_size = 0xc;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
-static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0x8;
+static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0xc;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -7002,7 +7002,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x18;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -7696,7 +7696,7 @@
     CompressedStackMaps_PayloadHeaderSize = 0x4;
 static constexpr dart::compiler::target::word Context_header_size = 0xc;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
-static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0x8;
+static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0xc;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -8403,7 +8403,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x18;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -9104,7 +9104,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x10;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -9807,7 +9807,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x10;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -10503,7 +10503,7 @@
     CompressedStackMaps_PayloadHeaderSize = 0x4;
 static constexpr dart::compiler::target::word Context_header_size = 0xc;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
-static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0x8;
+static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize = 0xc;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -11209,7 +11209,7 @@
 static constexpr dart::compiler::target::word Context_header_size = 0x18;
 static constexpr dart::compiler::target::word Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -11977,7 +11977,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0xc;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x8;
+    0xc;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -12760,7 +12760,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x18;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -13548,7 +13548,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x18;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -14333,7 +14333,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x10;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -15120,7 +15120,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x10;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -15904,7 +15904,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0xc;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x8;
+    0xc;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -16688,7 +16688,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x18;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -17464,7 +17464,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0xc;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x8;
+    0xc;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -18238,7 +18238,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x18;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -19017,7 +19017,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x18;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -19793,7 +19793,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x10;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -20571,7 +20571,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x10;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
@@ -21346,7 +21346,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0xc;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x8;
+    0xc;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x14;
 static constexpr dart::compiler::target::word
@@ -22121,7 +22121,7 @@
 static constexpr dart::compiler::target::word AOT_Context_header_size = 0x18;
 static constexpr dart::compiler::target::word AOT_Double_InstanceSize = 0x10;
 static constexpr dart::compiler::target::word AOT_DynamicLibrary_InstanceSize =
-    0x10;
+    0x18;
 static constexpr dart::compiler::target::word
     AOT_ExternalOneByteString_InstanceSize = 0x20;
 static constexpr dart::compiler::target::word
diff --git a/runtime/vm/exceptions.cc b/runtime/vm/exceptions.cc
index b5d3173..56565d0 100644
--- a/runtime/vm/exceptions.cc
+++ b/runtime/vm/exceptions.cc
@@ -1059,6 +1059,12 @@
   Exceptions::ThrowByType(Exceptions::kArgument, args);
 }
 
+void Exceptions::ThrowStateError(const Instance& arg) {
+  const Array& args = Array::Handle(Array::New(1));
+  args.SetAt(0, arg);
+  Exceptions::ThrowByType(Exceptions::kState, args);
+}
+
 void Exceptions::ThrowRangeError(const char* argument_name,
                                  const Integer& argument_value,
                                  intptr_t expected_from,
@@ -1126,6 +1132,10 @@
       class_name = &Symbols::ArgumentError();
       constructor_name = &Symbols::DotValue();
       break;
+    case kState:
+      library = Library::CoreLibrary();
+      class_name = &Symbols::StateError();
+      break;
     case kIntegerDivisionByZeroException:
       library = Library::CoreLibrary();
       class_name = &Symbols::IntegerDivisionByZeroException();
diff --git a/runtime/vm/exceptions.h b/runtime/vm/exceptions.h
index 1573440..6983c0c 100644
--- a/runtime/vm/exceptions.h
+++ b/runtime/vm/exceptions.h
@@ -55,6 +55,7 @@
     kRangeMsg,
     kArgument,
     kArgumentValue,
+    kState,
     kIntegerDivisionByZeroException,
     kNoSuchMethod,
     kFormat,
@@ -78,6 +79,7 @@
   DART_NORETURN static void ThrowOOM();
   DART_NORETURN static void ThrowStackOverflow();
   DART_NORETURN static void ThrowArgumentError(const Instance& arg);
+  DART_NORETURN static void ThrowStateError(const Instance& arg);
   DART_NORETURN static void ThrowRangeError(const char* argument_name,
                                             const Integer& argument_value,
                                             intptr_t expected_from,
diff --git a/runtime/vm/object.cc b/runtime/vm/object.cc
index ea0f243b..2bd5bbd 100644
--- a/runtime/vm/object.cc
+++ b/runtime/vm/object.cc
@@ -26397,13 +26397,17 @@
                      NativeAddress());
 }
 
-DynamicLibraryPtr DynamicLibrary::New(void* handle, Heap::Space space) {
+DynamicLibraryPtr DynamicLibrary::New(void* handle,
+                                      bool canBeClosed,
+                                      Heap::Space space) {
   DynamicLibrary& result = DynamicLibrary::Handle();
   result ^=
       Object::Allocate(kDynamicLibraryCid, DynamicLibrary::InstanceSize(),
                        space, DynamicLibrary::ContainsCompressedPointers());
   NoSafepointScope no_safepoint;
   result.SetHandle(handle);
+  result.SetClosed(false);
+  result.SetCanBeClosed(canBeClosed);
   return result.ptr();
 }
 
diff --git a/runtime/vm/object.h b/runtime/vm/object.h
index 58590ff..5bd082d 100644
--- a/runtime/vm/object.h
+++ b/runtime/vm/object.h
@@ -11793,7 +11793,9 @@
 
 class DynamicLibrary : public Instance {
  public:
-  static DynamicLibraryPtr New(void* handle, Heap::Space space = Heap::kNew);
+  static DynamicLibraryPtr New(void* handle,
+                               bool canBeClosed,
+                               Heap::Space space = Heap::kNew);
 
   static intptr_t InstanceSize() {
     return RoundedAllocationSize(sizeof(UntaggedDynamicLibrary));
@@ -11814,6 +11816,25 @@
     StoreNonPointer(&untag()->handle_, value);
   }
 
+  bool CanBeClosed() const {
+    ASSERT(!IsNull());
+    return untag()->canBeClosed_;
+  }
+
+  void SetCanBeClosed(bool value) const {
+    ASSERT(!IsNull());
+    StoreNonPointer(&untag()->canBeClosed_, value);
+  }
+
+  bool IsClosed() const {
+    ASSERT(!IsNull());
+    return untag()->isClosed_;
+  }
+
+  void SetClosed(bool value) const {
+    StoreNonPointer(&untag()->isClosed_, value);
+  }
+
  private:
   FINAL_HEAP_OBJECT_IMPLEMENTATION(DynamicLibrary, Instance);
 
diff --git a/runtime/vm/raw_object.h b/runtime/vm/raw_object.h
index f0ca60d..cac0992 100644
--- a/runtime/vm/raw_object.h
+++ b/runtime/vm/raw_object.h
@@ -3301,6 +3301,8 @@
   RAW_HEAP_OBJECT_IMPLEMENTATION(DynamicLibrary);
   VISIT_NOTHING();
   void* handle_;
+  bool isClosed_;
+  bool canBeClosed_;
 
   friend class DynamicLibrary;
 };
diff --git a/runtime/vm/raw_object_fields.cc b/runtime/vm/raw_object_fields.cc
index 51fb5d6..b68748c 100644
--- a/runtime/vm/raw_object_fields.cc
+++ b/runtime/vm/raw_object_fields.cc
@@ -231,6 +231,8 @@
   F(Pointer, data_)                                                            \
   F(Pointer, type_arguments_)                                                  \
   F(DynamicLibrary, handle_)                                                   \
+  F(DynamicLibrary, isClosed_)                                                 \
+  F(DynamicLibrary, canBeClosed_)                                              \
   F(FfiTrampolineData, c_signature_)                                           \
   F(FfiTrampolineData, callback_target_)                                       \
   F(FfiTrampolineData, callback_exceptional_return_)                           \
diff --git a/runtime/vm/symbols.h b/runtime/vm/symbols.h
index 86013ad..548af80 100644
--- a/runtime/vm/symbols.h
+++ b/runtime/vm/symbols.h
@@ -24,6 +24,7 @@
   V(ApiError, "ApiError")                                                      \
   V(ArgDescVar, ":arg_desc")                                                   \
   V(ArgumentError, "ArgumentError")                                            \
+  V(StateError, "StateError")                                                  \
   V(AssertionError, "_AssertionError")                                         \
   V(AssignIndexToken, "[]=")                                                   \
   V(AsyncStarMoveNextHelper, "_asyncStarMoveNextHelper")                       \
diff --git a/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart b/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart
index a79a1c9..cee78e2 100644
--- a/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart
+++ b/sdk/lib/_internal/vm/lib/ffi_dynamic_library_patch.dart
@@ -40,6 +40,10 @@
   external int getHandle();
 
   @patch
+  @pragma("vm:external-name", "Ffi_dl_close")
+  external void close();
+
+  @patch
   bool operator ==(Object other) {
     if (other is! DynamicLibrary) return false;
     DynamicLibrary otherLib = other;
diff --git a/sdk/lib/core/errors.dart b/sdk/lib/core/errors.dart
index 368891c..fa8147f 100644
--- a/sdk/lib/core/errors.dart
+++ b/sdk/lib/core/errors.dart
@@ -571,6 +571,7 @@
 /// actions. The message should be descriptive.
 class StateError extends Error {
   final String message;
+  @pragma("vm:entry-point")
   StateError(this.message);
   String toString() => "Bad state: $message";
 }
diff --git a/sdk/lib/ffi/dynamic_library.dart b/sdk/lib/ffi/dynamic_library.dart
index 2d919dd..da7a786 100644
--- a/sdk/lib/ffi/dynamic_library.dart
+++ b/sdk/lib/ffi/dynamic_library.dart
@@ -48,6 +48,19 @@
   @Since('2.14')
   external bool providesSymbol(String symbolName);
 
+  /// Closes this dynamic library.
+  ///
+  /// After calling [close], this library object can no longer be used for
+  /// lookups. Further, this information is forwarded to the operating system,
+  /// which may unload the library if there are no remaining references to it
+  /// in the current process.
+  ///
+  /// Depending on whether another reference to this library has been opened,
+  /// pointers and functions previously returned by [lookup] and
+  /// [DynamicLibraryExtension.lookupFunction] may become invalid as well.
+  @Since('3.1')
+  external void close();
+
   /// Dynamic libraries are equal if they load the same library.
   external bool operator ==(Object other);
 
diff --git a/tests/ffi/dylib_close_test.dart b/tests/ffi/dylib_close_test.dart
new file mode 100644
index 0000000..13ddd63
--- /dev/null
+++ b/tests/ffi/dylib_close_test.dart
@@ -0,0 +1,35 @@
+// Copyright (c) 2023, 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.
+//
+// SharedObjects=ffi_test_functions
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:expect/expect.dart';
+
+import 'dylib_utils.dart';
+
+void main() {
+  testClose();
+  testUnclosable();
+}
+
+void testClose() {
+  final lib = dlopenPlatformSpecific("ffi_test_functions");
+  lib.lookup('ReturnMaxUint8');
+
+  lib.close();
+  Expect.throwsStateError(
+      () => lib.lookup('ReturnMaxUint8'), 'Illegal lookup in closed library');
+  lib.close(); // Duplicate close should not crash.
+}
+
+void testUnclosable() {
+  final proc = DynamicLibrary.process();
+  final exec = DynamicLibrary.executable();
+
+  Expect.throwsStateError(proc.close);
+  Expect.throwsStateError(exec.close);
+}
diff --git a/tests/ffi_2/dylib_close_test.dart b/tests/ffi_2/dylib_close_test.dart
new file mode 100644
index 0000000..8814825
--- /dev/null
+++ b/tests/ffi_2/dylib_close_test.dart
@@ -0,0 +1,37 @@
+// Copyright (c) 2023, 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.
+//
+// SharedObjects=ffi_test_functions
+
+// @dart=2.9
+
+import 'dart:ffi';
+import 'dart:io';
+
+import 'package:expect/expect.dart';
+
+import 'dylib_utils.dart';
+
+void main() {
+  testClose();
+  testUnclosable();
+}
+
+void testClose() {
+  final lib = dlopenPlatformSpecific("ffi_test_functions");
+  lib.lookup('ReturnMaxUint8');
+
+  lib.close();
+  Expect.throwsStateError(
+      () => lib.lookup('ReturnMaxUint8'), 'Illegal lookup in closed library');
+  lib.close(); // Duplicate close should not crash.
+}
+
+void testUnclosable() {
+  final proc = DynamicLibrary.process();
+  final exec = DynamicLibrary.executable();
+
+  Expect.throwsStateError(proc.close);
+  Expect.throwsStateError(exec.close);
+}