@@ -173,6 +173,61 @@ 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: Not safe by itself. `MessageCloner` handles synchronization with a `Mutex`.
198+ unsafe impl Send for UnsafeAllocatorRef < ' _ > { }
199+
200+ /// Wrapper around an [`Allocator`] which allows safely using it on multiple threads to
201+ /// clone [`Message`]s into.
202+ pub struct MessageCloner < ' a > ( Mutex < UnsafeAllocatorRef < ' a > > ) ;
203+
204+ impl < ' a > MessageCloner < ' a > {
205+ /// Wrap an [`Allocator`] in a [`MessageCloner`].
206+ ///
207+ /// This method takes a `&mut Allocator`, to ensure that no other references to the `Allocator`
208+ /// can exist, which guarantees no other threads can allocate with the `Allocator` while this
209+ /// `MessageCloner` exists.
210+ #[ inline]
211+ #[ expect( clippy:: needless_pass_by_ref_mut) ]
212+ pub fn new ( allocator : & ' a mut Allocator ) -> Self {
213+ Self ( Mutex :: new ( UnsafeAllocatorRef ( allocator) ) )
214+ }
215+
216+ /// Clone a [`Message`] into the [`Allocator`] held by this [`MessageCloner`].
217+ ///
218+ /// # Panics
219+ /// Panics if the underlying `Mutex` is poisoned.
220+ pub fn clone_message ( & self , message : & Message ) -> Message < ' a > {
221+ // Obtain an exclusive lock on the `Mutex` during `clone_in` operation,
222+ // to ensure no other thread can be simultaneously using the `Allocator`
223+ let guard = self . 0 . lock ( ) . unwrap ( ) ;
224+ let allocator = guard. 0 ;
225+ message. clone_in ( allocator)
226+ }
227+ }
228+ }
229+ use message_cloner:: MessageCloner ;
230+
176231impl Runtime {
177232 pub ( super ) fn new (
178233 linter : Linter ,
@@ -588,12 +643,12 @@ impl Runtime {
588643 #[ cfg( feature = "language_server" ) ]
589644 pub ( super ) fn run_source < ' a > (
590645 & mut self ,
591- allocator : & ' a oxc_allocator:: Allocator ,
646+ allocator : & ' a mut oxc_allocator:: Allocator ,
592647 ) -> Vec < MessageWithPosition < ' a > > {
593- use oxc_allocator:: CloneIn ;
594- use oxc_data_structures:: rope:: Rope ;
595648 use std:: sync:: Mutex ;
596649
650+ use oxc_data_structures:: rope:: Rope ;
651+
597652 use crate :: {
598653 FixWithPosition ,
599654 fixer:: { Fix , PossibleFixesWithPosition } ,
@@ -614,6 +669,9 @@ impl Runtime {
614669 }
615670 }
616671
672+ // Wrap allocator in `MessageCloner` so can clone `Message`s into it
673+ let message_cloner = MessageCloner :: new ( allocator) ;
674+
617675 let messages = Mutex :: new ( Vec :: < MessageWithPosition < ' a > > :: new ( ) ) ;
618676 let ( sender, _receiver) = mpsc:: channel ( ) ;
619677 rayon:: scope ( |scope| {
@@ -655,7 +713,7 @@ impl Runtime {
655713
656714 messages. lock ( ) . unwrap ( ) . extend ( section_messages. iter ( ) . map (
657715 |message| {
658- let message = message . clone_in ( allocator ) ;
716+ let message = message_cloner . clone_message ( message ) ;
659717
660718 let labels = & message. error . labels . clone ( ) . map ( |labels| {
661719 labels
@@ -742,13 +800,15 @@ impl Runtime {
742800 #[ cfg( test) ]
743801 pub ( super ) fn run_test_source < ' a > (
744802 & mut self ,
745- allocator : & ' a Allocator ,
803+ allocator : & ' a mut Allocator ,
746804 check_syntax_errors : bool ,
747805 tx_error : & DiagnosticSender ,
748806 ) -> Vec < Message < ' a > > {
749- use oxc_allocator:: CloneIn ;
750807 use std:: sync:: Mutex ;
751808
809+ // Wrap allocator in `MessageCloner` so can clone `Message`s into it
810+ let message_cloner = MessageCloner :: new ( allocator) ;
811+
752812 let messages = Mutex :: new ( Vec :: < Message < ' a > > :: new ( ) ) ;
753813 rayon:: scope ( |scope| {
754814 self . resolve_modules ( scope, check_syntax_errors, tx_error, |me, mut module| {
@@ -773,13 +833,13 @@ impl Runtime {
773833 . map ( |err| Message :: new ( err, PossibleFixes :: None ) )
774834 . collect ( ) ,
775835 }
776- . into_iter ( )
777- . map ( |mut message| {
836+ . iter_mut ( )
837+ . map ( |message| {
778838 if section. source . start != 0 {
779839 message. move_offset ( section. source . start )
780840 . move_fix_offset ( section. source . start ) ;
781841 }
782- message . clone_in ( allocator )
842+ message_cloner . clone_message ( message )
783843 } ) ,
784844 ) ;
785845 }
0 commit comments