@@ -173,6 +173,72 @@ impl RuntimeFileSystem for OsFileSystem {
173173 }
174174}
175175
176+ /// [`MessageCloner`] is a wrapper around an `&Allocator` which allows it to be safely shared across threads,
177+ /// in order to clone [`Message`]s into it.
178+ ///
179+ /// `Allocator` is not thread safe (it is not `Sync`), so cannot be shared across threads.
180+ /// It would be undefined behavior to allocate into an `Allocator` from multiple threads simultaneously.
181+ ///
182+ /// `MessageCloner` ensures only one thread at a time can utilize the `Allocator`, by taking an
183+ /// exclusive `&mut Allocator` to start with, and synchronising access to the `Allocator` with a `Mutex`.
184+ ///
185+ /// This type is wrapped in a module so that other code cannot access the inner `UnsafeAllocatorRef`
186+ /// directly, and must go via the [`MessageCloner::clone_message`] method.
187+ mod message_cloner {
188+ use std:: sync:: Mutex ;
189+
190+ use oxc_allocator:: { Allocator , CloneIn } ;
191+
192+ use crate :: Message ;
193+
194+ /// Unsafe wrapper around an `&Allocator` which makes it `Send`.
195+ struct UnsafeAllocatorRef < ' a > ( & ' a Allocator ) ;
196+
197+ // SAFETY: It is sound to implement `Send` for `UnsafeAllocatorRef` because:
198+ // * The only way to construct an `UnsafeAllocatorRef` is via `MessageCloner::new`, which takes
199+ // an exclusive `&mut Allocator`, ensuring no other references to the same `Allocator` exist.
200+ // * The lifetime `'a` ensures that the reference to the `Allocator` cannot outlive the original
201+ // mutable borrow, preventing aliasing or concurrent mutation.
202+ // * All access to the `Allocator` via `UnsafeAllocatorRef` is synchronized by a `Mutex` inside
203+ // `MessageCloner`, so only one thread can access the allocator at a time.
204+ // * The module encapsulation prevents direct access to `UnsafeAllocatorRef`, so it cannot be
205+ // misused outside of the intended, synchronized context.
206+ //
207+ // Therefore, although `Allocator` is not `Sync`, it is safe to send `UnsafeAllocatorRef` between
208+ // threads as long as it is only accessed via the `Mutex` in `MessageCloner`.
209+ unsafe impl Send for UnsafeAllocatorRef < ' _ > { }
210+
211+ /// Wrapper around an [`Allocator`] which allows safely using it on multiple threads to
212+ /// clone [`Message`]s into.
213+ pub struct MessageCloner < ' a > ( Mutex < UnsafeAllocatorRef < ' a > > ) ;
214+
215+ impl < ' a > MessageCloner < ' a > {
216+ /// Wrap an [`Allocator`] in a [`MessageCloner`].
217+ ///
218+ /// This method takes a `&mut Allocator`, to ensure that no other references to the `Allocator`
219+ /// can exist, which guarantees no other threads can allocate with the `Allocator` while this
220+ /// `MessageCloner` exists.
221+ #[ inline]
222+ #[ expect( clippy:: needless_pass_by_ref_mut) ]
223+ pub fn new ( allocator : & ' a mut Allocator ) -> Self {
224+ Self ( Mutex :: new ( UnsafeAllocatorRef ( allocator) ) )
225+ }
226+
227+ /// Clone a [`Message`] into the [`Allocator`] held by this [`MessageCloner`].
228+ ///
229+ /// # Panics
230+ /// Panics if the underlying `Mutex` is poisoned.
231+ pub fn clone_message ( & self , message : & Message ) -> Message < ' a > {
232+ // Obtain an exclusive lock on the `Mutex` during `clone_in` operation,
233+ // to ensure no other thread can be simultaneously using the `Allocator`
234+ let guard = self . 0 . lock ( ) . unwrap ( ) ;
235+ let allocator = guard. 0 ;
236+ message. clone_in ( allocator)
237+ }
238+ }
239+ }
240+ use message_cloner:: MessageCloner ;
241+
176242impl Runtime {
177243 pub ( super ) fn new (
178244 linter : Linter ,
@@ -588,12 +654,12 @@ impl Runtime {
588654 #[ cfg( feature = "language_server" ) ]
589655 pub ( super ) fn run_source < ' a > (
590656 & mut self ,
591- allocator : & ' a oxc_allocator:: Allocator ,
657+ allocator : & ' a mut oxc_allocator:: Allocator ,
592658 ) -> Vec < MessageWithPosition < ' a > > {
593- use oxc_allocator:: CloneIn ;
594- use oxc_data_structures:: rope:: Rope ;
595659 use std:: sync:: Mutex ;
596660
661+ use oxc_data_structures:: rope:: Rope ;
662+
597663 use crate :: {
598664 FixWithPosition ,
599665 fixer:: { Fix , PossibleFixesWithPosition } ,
@@ -614,6 +680,9 @@ impl Runtime {
614680 }
615681 }
616682
683+ // Wrap allocator in `MessageCloner` so can clone `Message`s into it
684+ let message_cloner = MessageCloner :: new ( allocator) ;
685+
617686 let messages = Mutex :: new ( Vec :: < MessageWithPosition < ' a > > :: new ( ) ) ;
618687 let ( sender, _receiver) = mpsc:: channel ( ) ;
619688 rayon:: scope ( |scope| {
@@ -655,7 +724,7 @@ impl Runtime {
655724
656725 messages. lock ( ) . unwrap ( ) . extend ( section_messages. iter ( ) . map (
657726 |message| {
658- let message = message . clone_in ( allocator ) ;
727+ let message = message_cloner . clone_message ( message ) ;
659728
660729 let labels = & message. error . labels . clone ( ) . map ( |labels| {
661730 labels
@@ -742,13 +811,15 @@ impl Runtime {
742811 #[ cfg( test) ]
743812 pub ( super ) fn run_test_source < ' a > (
744813 & mut self ,
745- allocator : & ' a Allocator ,
814+ allocator : & ' a mut Allocator ,
746815 check_syntax_errors : bool ,
747816 tx_error : & DiagnosticSender ,
748817 ) -> Vec < Message < ' a > > {
749- use oxc_allocator:: CloneIn ;
750818 use std:: sync:: Mutex ;
751819
820+ // Wrap allocator in `MessageCloner` so can clone `Message`s into it
821+ let message_cloner = MessageCloner :: new ( allocator) ;
822+
752823 let messages = Mutex :: new ( Vec :: < Message < ' a > > :: new ( ) ) ;
753824 rayon:: scope ( |scope| {
754825 self . resolve_modules ( scope, check_syntax_errors, tx_error, |me, mut module| {
@@ -773,13 +844,13 @@ impl Runtime {
773844 . map ( |err| Message :: new ( err, PossibleFixes :: None ) )
774845 . collect ( ) ,
775846 }
776- . into_iter ( )
777- . map ( |mut message| {
847+ . iter_mut ( )
848+ . map ( |message| {
778849 if section. source . start != 0 {
779850 message. move_offset ( section. source . start )
780851 . move_fix_offset ( section. source . start ) ;
781852 }
782- message . clone_in ( allocator )
853+ message_cloner . clone_message ( message )
783854 } ) ,
784855 ) ;
785856 }
0 commit comments