diff options
author | Erik Hennig <online@erik-hennig.me> | 2024-08-01 23:16:49 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-08-01 21:16:49 +0000 |
commit | 5963695664a6c0112bded05c9af9bbfa2224d411 (patch) | |
tree | d7cd468c01b5aa61146c2a675f359d1ebfb3f7f3 /components/script | |
parent | 501950c2e33d421ad1a38fa095fa7aae610fd3aa (diff) | |
download | servo-5963695664a6c0112bded05c9af9bbfa2224d411.tar.gz servo-5963695664a6c0112bded05c9af9bbfa2224d411.zip |
fix: Memory leak from CreateProxyWindowHandler (#32773)
* fix: Memory leak from CreateProxyWindowHandler
Signed-off-by: ede1998 <online@erik-hennig.me>
* fix: memory leak in WindowProxy
Signed-off-by: ede1998 <online@erik-hennig.me>
* fix: Memory leak in WindowProxyHandler through static
Signed-off-by: ede1998 <online@erik-hennig.me>
---------
Signed-off-by: ede1998 <online@erik-hennig.me>
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/dom/bindings/trace.rs | 2 | ||||
-rw-r--r-- | components/script/dom/bindings/utils.rs | 19 | ||||
-rw-r--r-- | components/script/dom/window.rs | 8 | ||||
-rw-r--r-- | components/script/dom/windowproxy.rs | 112 |
4 files changed, 101 insertions, 40 deletions
diff --git a/components/script/dom/bindings/trace.rs b/components/script/dom/bindings/trace.rs index 34019fa789d..cc5462f9dc1 100644 --- a/components/script/dom/bindings/trace.rs +++ b/components/script/dom/bindings/trace.rs @@ -61,11 +61,11 @@ use crate::dom::bindings::refcounted::{Trusted, TrustedPromise}; use crate::dom::bindings::reflector::{DomObject, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::{DOMString, USVString}; -use crate::dom::bindings::utils::WindowProxyHandler; use crate::dom::gpubuffer::GPUBufferState; use crate::dom::gpucanvascontext::WebGPUContextId; use crate::dom::htmlimageelement::SourceSet; use crate::dom::htmlmediaelement::HTMLMediaElementFetchContext; +use crate::dom::windowproxy::WindowProxyHandler; use crate::script_runtime::{ContextForRequestInterrupt, StreamConsumer}; use crate::script_thread::IncompleteParserContexts; use crate::task::TaskBox; diff --git a/components/script/dom/bindings/utils.rs b/components/script/dom/bindings/utils.rs index 64886971f5f..522b6f46686 100644 --- a/components/script/dom/bindings/utils.rs +++ b/components/script/dom/bindings/utils.rs @@ -31,7 +31,7 @@ use js::rust::{ MutableHandleValue, ToString, }; use js::JS_CALLEE; -use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; +use malloc_size_of::MallocSizeOfOps; use crate::dom::bindings::codegen::PrototypeList::{MAX_PROTO_CHAIN_LENGTH, PROTO_OR_IFACE_LENGTH}; use crate::dom::bindings::codegen::{InterfaceObjectMap, PrototypeList}; @@ -42,31 +42,22 @@ use crate::dom::bindings::error::throw_invalid_this; use crate::dom::bindings::inheritance::TopTypeId; use crate::dom::bindings::str::DOMString; use crate::dom::bindings::trace::trace_object; -use crate::dom::windowproxy; +use crate::dom::windowproxy::WindowProxyHandler; use crate::script_runtime::JSContext as SafeJSContext; -/// Proxy handler for a WindowProxy. -pub struct WindowProxyHandler(pub *const libc::c_void); - -impl MallocSizeOf for WindowProxyHandler { - fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { - // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs. - 0 - } -} - #[derive(JSTraceable, MallocSizeOf)] /// Static data associated with a global object. pub struct GlobalStaticData { + #[ignore_malloc_size_of = "WindowProxyHandler does not properly implement it anyway"] /// The WindowProxy proxy handler for this global. - pub windowproxy_handler: WindowProxyHandler, + pub windowproxy_handler: &'static WindowProxyHandler, } impl GlobalStaticData { /// Creates a new GlobalStaticData. pub fn new() -> GlobalStaticData { GlobalStaticData { - windowproxy_handler: windowproxy::new_window_proxy_handler(), + windowproxy_handler: WindowProxyHandler::proxy_handler(), } } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index e38e0aa54e8..1448adb8c5a 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -104,7 +104,7 @@ use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::structuredclone; use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox}; -use crate::dom::bindings::utils::{GlobalStaticData, WindowProxyHandler}; +use crate::dom::bindings::utils::GlobalStaticData; use crate::dom::bindings::weakref::DOMTracker; use crate::dom::bluetooth::BluetoothExtraPermissionData; use crate::dom::crypto::Crypto; @@ -133,7 +133,7 @@ use crate::dom::selection::Selection; use crate::dom::storage::Storage; use crate::dom::testrunner::TestRunner; use crate::dom::webglrenderingcontext::WebGLCommandSender; -use crate::dom::windowproxy::WindowProxy; +use crate::dom::windowproxy::{WindowProxy, WindowProxyHandler}; use crate::dom::worklet::Worklet; use crate::dom::workletglobalscope::WorkletGlobalScopeType; use crate::layout_image::fetch_image_for_layout; @@ -2321,8 +2321,8 @@ impl Window { self.Document().url() } - pub fn windowproxy_handler(&self) -> WindowProxyHandler { - WindowProxyHandler(self.dom_static.windowproxy_handler.0) + pub fn windowproxy_handler(&self) -> &'static WindowProxyHandler { + self.dom_static.windowproxy_handler } pub fn get_pending_reflow_count(&self) -> u32 { diff --git a/components/script/dom/windowproxy.rs b/components/script/dom/windowproxy.rs index 92ecd52968d..0715108d757 100644 --- a/components/script/dom/windowproxy.rs +++ b/components/script/dom/windowproxy.rs @@ -12,8 +12,8 @@ use html5ever::local_name; use indexmap::map::IndexMap; use ipc_channel::ipc; use js::glue::{ - CreateWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot, ProxyTraps, - SetProxyReservedSlot, + CreateWrapperProxyHandler, DeleteWrapperProxyHandler, GetProxyPrivate, GetProxyReservedSlot, + ProxyTraps, SetProxyReservedSlot, }; use js::jsapi::{ GCContext, Handle as RawHandle, HandleId as RawHandleId, HandleObject as RawHandleObject, @@ -28,6 +28,7 @@ use js::jsval::{JSVal, NullValue, PrivateValue, UndefinedValue}; use js::rust::wrappers::{JS_TransplantObject, NewWindowProxy, SetWindowProxy}; use js::rust::{get_object_class, Handle, MutableHandle}; use js::JSCLASS_IS_GLOBAL; +use malloc_size_of::{MallocSizeOf, MallocSizeOfOps}; use net_traits::request::Referrer; use script_traits::{ AuxiliaryBrowsingContextLoadInfo, HistoryEntryReplacement, LoadData, LoadOrigin, NewLayoutInfo, @@ -46,7 +47,7 @@ use crate::dom::bindings::reflector::{DomObject, Reflector}; use crate::dom::bindings::root::{Dom, DomRoot}; use crate::dom::bindings::str::{DOMString, USVString}; use crate::dom::bindings::trace::JSTraceable; -use crate::dom::bindings::utils::{get_array_index_from_id, AsVoidPtr, WindowProxyHandler}; +use crate::dom::bindings::utils::{get_array_index_from_id, AsVoidPtr}; use crate::dom::dissimilaroriginwindow::DissimilarOriginWindow; use crate::dom::document::Document; use crate::dom::element::Element; @@ -126,7 +127,7 @@ pub struct WindowProxy { } impl WindowProxy { - pub fn new_inherited( + fn new_inherited( browsing_context_id: BrowsingContextId, top_level_browsing_context_id: TopLevelBrowsingContextId, currently_active: Option<PipelineId>, @@ -168,8 +169,7 @@ impl WindowProxy { creator: CreatorBrowsingContextInfo, ) -> DomRoot<WindowProxy> { unsafe { - let WindowProxyHandler(handler) = window.windowproxy_handler(); - assert!(!handler.is_null()); + let handler = window.windowproxy_handler(); let cx = GlobalScope::get_cx(); let window_jsobject = window.reflector().get_jsobject(); @@ -181,7 +181,7 @@ impl WindowProxy { let _ac = JSAutoRealm::new(*cx, window_jsobject.get()); // Create a new window proxy. - rooted!(in(*cx) let js_proxy = NewWindowProxy(*cx, window_jsobject, handler)); + rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject)); assert!(!js_proxy.is_null()); // Create a new browsing context. @@ -228,8 +228,7 @@ impl WindowProxy { creator: CreatorBrowsingContextInfo, ) -> DomRoot<WindowProxy> { unsafe { - let handler = CreateWrapperProxyHandler(&XORIGIN_PROXY_HANDLER); - assert!(!handler.is_null()); + let handler = WindowProxyHandler::x_origin_proxy_handler(); let cx = GlobalScope::get_cx(); @@ -255,7 +254,7 @@ impl WindowProxy { let _ac = JSAutoRealm::new(*cx, window_jsobject.get()); // Create a new window proxy. - rooted!(in(*cx) let js_proxy = NewWindowProxy(*cx, window_jsobject, handler)); + rooted!(in(*cx) let js_proxy = handler.new_window_proxy(&cx, window_jsobject)); assert!(!js_proxy.is_null()); // The window proxy owns the browsing context. @@ -622,11 +621,9 @@ impl WindowProxy { /// Change the Window that this WindowProxy resolves to. // TODO: support setting the window proxy to a dummy value, // to handle the case when the active document is in another script thread. - fn set_window(&self, window: &GlobalScope, traps: &ProxyTraps) { + fn set_window(&self, window: &GlobalScope, handler: &WindowProxyHandler) { unsafe { debug!("Setting window of {:p}.", self); - let handler = CreateWrapperProxyHandler(traps); - assert!(!handler.is_null()); let cx = GlobalScope::get_cx(); let window_jsobject = window.reflector().get_jsobject(); @@ -641,12 +638,12 @@ impl WindowProxy { // The old window proxy no longer owns this browsing context. SetProxyReservedSlot(old_js_proxy.get(), 0, &PrivateValue(ptr::null_mut())); - // Brain transpant the window proxy. Brain transplantation is + // Brain transplant the window proxy. Brain transplantation is // usually done to move a window proxy between compartments, but // that's not what we are doing here. We need to do this just // because we want to replace the wrapper's `ProxyTraps`, but we // don't want to update its identity. - rooted!(in(*cx) let new_js_proxy = NewWindowProxy(*cx, window_jsobject, handler)); + rooted!(in(*cx) let new_js_proxy = handler.new_window_proxy(&cx, window_jsobject)); debug!( "Transplanting proxy from {:p} to {:p}.", old_js_proxy.get(), @@ -681,7 +678,7 @@ impl WindowProxy { ); } } - self.set_window(globalscope, &PROXY_HANDLER); + self.set_window(globalscope, WindowProxyHandler::proxy_handler()); self.currently_active.set(Some(globalscope.pipeline_id())); } @@ -691,7 +688,10 @@ impl WindowProxy { } let globalscope = self.global(); let window = DissimilarOriginWindow::new(&globalscope, self); - self.set_window(window.upcast(), &XORIGIN_PROXY_HANDLER); + self.set_window( + window.upcast(), + WindowProxyHandler::x_origin_proxy_handler(), + ); self.currently_active.set(None); } @@ -1039,7 +1039,7 @@ unsafe extern "C" fn get_prototype_if_ordinary( true } -static PROXY_HANDLER: ProxyTraps = ProxyTraps { +static PROXY_TRAPS: ProxyTraps = ProxyTraps { // TODO: These traps should change their behavior depending on // `IsPlatformObjectSameOrigin(this.[[Window]])` enter: None, @@ -1074,9 +1074,79 @@ static PROXY_HANDLER: ProxyTraps = ProxyTraps { isConstructor: None, }; +/// Proxy handler for a WindowProxy. +/// Has ownership of the inner pointer and deallocates it when it is no longer needed. +pub struct WindowProxyHandler(*const libc::c_void); + +impl MallocSizeOf for WindowProxyHandler { + fn size_of(&self, _ops: &mut MallocSizeOfOps) -> usize { + // FIXME(#6907) this is a pointer to memory allocated by `new` in NewProxyHandler in rust-mozjs. + 0 + } +} + +// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const. +#[allow(unsafe_code)] +unsafe impl Send for WindowProxyHandler {} +// Safety: Send and Sync is guaranteed since the underlying pointer and all its associated methods in C++ are const. +#[allow(unsafe_code)] +unsafe impl Sync for WindowProxyHandler {} + +#[allow(unsafe_code)] +impl WindowProxyHandler { + fn new(traps: &ProxyTraps) -> Self { + // Safety: Foreign function generated by bindgen. Pointer is freed in drop to prevent memory leak. + let ptr = unsafe { CreateWrapperProxyHandler(traps) }; + assert!(!ptr.is_null()); + Self(ptr) + } + + /// Returns a single, shared WindowProxyHandler that contains XORIGIN_PROXY_TRAPS. + pub fn x_origin_proxy_handler() -> &'static Self { + use std::sync::OnceLock; + /// We are sharing a single instance for the entire programs here due to lifetime issues. + /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when + /// it is safe to free it. + /// Sharing a single instance should be fine because all methods on this pointer in C++ + /// are const and don't modify its internal state. + static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new(); + SINGLETON.get_or_init(|| Self::new(&XORIGIN_PROXY_TRAPS)) + } + + /// Returns a single, shared WindowProxyHandler that contains normal PROXY_TRAPS. + pub fn proxy_handler() -> &'static Self { + use std::sync::OnceLock; + /// We are sharing a single instance for the entire programs here due to lifetime issues. + /// The pointer in self.0 is known to C++ and visited by the GC. Hence, we don't know when + /// it is safe to free it. + /// Sharing a single instance should be fine because all methods on this pointer in C++ + /// are const and don't modify its internal state. + static SINGLETON: OnceLock<WindowProxyHandler> = OnceLock::new(); + SINGLETON.get_or_init(|| Self::new(&PROXY_TRAPS)) + } + + /// Creates a new WindowProxy object on the C++ side and returns the pointer to it. + /// The pointer should be owned by the GC. + pub fn new_window_proxy( + &self, + cx: &crate::script_runtime::JSContext, + window_jsobject: js::gc::HandleObject, + ) -> *mut JSObject { + let obj = unsafe { NewWindowProxy(**cx, window_jsobject, self.0) }; + assert!(!obj.is_null()); + obj + } +} + #[allow(unsafe_code)] -pub fn new_window_proxy_handler() -> WindowProxyHandler { - unsafe { WindowProxyHandler(CreateWrapperProxyHandler(&PROXY_HANDLER)) } +impl Drop for WindowProxyHandler { + fn drop(&mut self) { + // Safety: Pointer is allocated by corresponding C++ function, owned by this + // struct and not accessible from outside. + unsafe { + DeleteWrapperProxyHandler(self.0); + } + } } // The proxy traps for cross-origin windows. @@ -1189,7 +1259,7 @@ unsafe extern "C" fn preventExtensions_xorigin( throw_security_error(cx, InRealm::Already(&in_realm_proof)) } -static XORIGIN_PROXY_HANDLER: ProxyTraps = ProxyTraps { +static XORIGIN_PROXY_TRAPS: ProxyTraps = ProxyTraps { enter: None, getOwnPropertyDescriptor: Some(getOwnPropertyDescriptor_xorigin), defineProperty: Some(defineProperty_xorigin), |