88// option. This file may not be copied, modified, or distributed
99// except according to those terms.
1010
11- // FIXME #3921. This is unsafe because linenoise uses global mutable
12- // state without mutexes.
13-
1411use std:: c_str:: ToCStr ;
1512use std:: libc:: { c_char, c_int} ;
16- use std:: local_data;
17- use std:: str ;
13+ use std:: { local_data, str , rt } ;
14+ use std:: unstable :: finally :: Finally ;
1815
1916#[ cfg( stage0) ]
2017pub mod rustrt {
@@ -28,6 +25,9 @@ pub mod rustrt {
2825 fn linenoiseHistoryLoad ( file : * c_char ) -> c_int ;
2926 fn linenoiseSetCompletionCallback ( callback : * u8 ) ;
3027 fn linenoiseAddCompletion ( completions : * ( ) , line : * c_char ) ;
28+
29+ fn rust_take_linenoise_lock ( ) ;
30+ fn rust_drop_linenoise_lock ( ) ;
3131 }
3232}
3333
@@ -42,65 +42,107 @@ pub mod rustrt {
4242 externfn ! ( fn linenoiseHistoryLoad( file: * c_char) -> c_int)
4343 externfn ! ( fn linenoiseSetCompletionCallback( callback: extern "C" fn ( * i8 , * ( ) ) ) )
4444 externfn ! ( fn linenoiseAddCompletion( completions: * ( ) , line: * c_char) )
45+
46+ externfn ! ( fn rust_take_linenoise_lock( ) )
47+ externfn ! ( fn rust_drop_linenoise_lock( ) )
48+ }
49+
50+ macro_rules! locked {
51+ ( $expr: expr) => {
52+ unsafe {
53+ // FIXME #9105: can't use a static mutex in pure Rust yet.
54+ rustrt:: rust_take_linenoise_lock( ) ;
55+ let x = $expr;
56+ rustrt:: rust_drop_linenoise_lock( ) ;
57+ x
58+ }
59+ }
4560}
4661
4762/// Add a line to history
48- pub unsafe fn add_history ( line : & str ) -> bool {
63+ pub fn add_history ( line : & str ) -> bool {
4964 do line. with_c_str |buf| {
50- rustrt:: linenoiseHistoryAdd ( buf) == 1 as c_int
65+ ( locked ! ( rustrt:: linenoiseHistoryAdd( buf) ) ) == 1 as c_int
5166 }
5267}
5368
5469/// Set the maximum amount of lines stored
55- pub unsafe fn set_history_max_len ( len : int ) -> bool {
56- rustrt:: linenoiseHistorySetMaxLen ( len as c_int ) == 1 as c_int
70+ pub fn set_history_max_len ( len : int ) -> bool {
71+ ( locked ! ( rustrt:: linenoiseHistorySetMaxLen( len as c_int) ) ) == 1 as c_int
5772}
5873
5974/// Save line history to a file
60- pub unsafe fn save_history ( file : & str ) -> bool {
75+ pub fn save_history ( file : & str ) -> bool {
6176 do file. with_c_str |buf| {
62- rustrt:: linenoiseHistorySave ( buf) == 1 as c_int
77+ // 0 on success, -1 on failure
78+ ( locked ! ( rustrt:: linenoiseHistorySave( buf) ) ) == 0 as c_int
6379 }
6480}
6581
6682/// Load line history from a file
67- pub unsafe fn load_history ( file : & str ) -> bool {
83+ pub fn load_history ( file : & str ) -> bool {
6884 do file. with_c_str |buf| {
69- rustrt:: linenoiseHistoryLoad ( buf) == 1 as c_int
85+ // 0 on success, -1 on failure
86+ ( locked ! ( rustrt:: linenoiseHistoryLoad( buf) ) ) == 0 as c_int
7087 }
7188}
7289
7390/// Print out a prompt and then wait for input and return it
74- pub unsafe fn read ( prompt : & str ) -> Option < ~str > {
91+ pub fn read ( prompt : & str ) -> Option < ~str > {
7592 do prompt. with_c_str |buf| {
76- let line = rustrt:: linenoise ( buf) ;
93+ let line = locked ! ( rustrt:: linenoise( buf) ) ;
7794
7895 if line. is_null ( ) { None }
79- else { Some ( str:: raw:: from_c_str ( line) ) }
96+ else {
97+ unsafe {
98+ do ( || {
99+ Some ( str:: raw:: from_c_str ( line) )
100+ } ) . finally {
101+ // linenoise's return value is from strdup, so we
102+ // better not leak it.
103+ rt:: global_heap:: exchange_free ( line) ;
104+ }
105+ }
106+ }
80107 }
81108}
82109
83110pub type CompletionCb = @fn ( ~str , @fn ( ~str ) ) ;
84111
85- static complete_key: local_data:: Key < @CompletionCb > = & local_data:: Key ;
86-
87- /// Bind to the main completion callback
88- pub unsafe fn complete ( cb : CompletionCb ) {
89- local_data:: set ( complete_key, @cb) ;
90-
91- extern fn callback ( line : * c_char , completions : * ( ) ) {
92- do local_data:: get ( complete_key) |cb| {
93- let cb = * * cb. unwrap ( ) ;
94-
95- unsafe {
96- do cb ( str:: raw:: from_c_str ( line) ) |suggestion| {
97- do suggestion. with_c_str |buf| {
98- rustrt:: linenoiseAddCompletion ( completions, buf) ;
112+ static complete_key: local_data:: Key < CompletionCb > = & local_data:: Key ;
113+
114+ /// Bind to the main completion callback in the current task.
115+ ///
116+ /// The completion callback should not call any `extra::rl` functions
117+ /// other than the closure that it receives as its second
118+ /// argument. Calling such a function will deadlock on the mutex used
119+ /// to ensure that the calls are thread-safe.
120+ pub fn complete ( cb : CompletionCb ) {
121+ local_data:: set ( complete_key, cb) ;
122+
123+ extern fn callback ( c_line : * c_char , completions : * ( ) ) {
124+ do local_data:: get ( complete_key) |opt_cb| {
125+ // only fetch completions if a completion handler has been
126+ // registered in the current task.
127+ match opt_cb {
128+ None => { } ,
129+ Some ( cb) => {
130+ let line = unsafe { str:: raw:: from_c_str ( c_line) } ;
131+ do ( * cb) ( line) |suggestion| {
132+ do suggestion. with_c_str |buf| {
133+ // This isn't locked, because `callback` gets
134+ // called inside `rustrt::linenoise`, which
135+ // *is* already inside the mutex, so
136+ // re-locking would be a deadlock.
137+ unsafe {
138+ rustrt:: linenoiseAddCompletion ( completions, buf) ;
139+ }
140+ }
99141 }
100142 }
101143 }
102144 }
103145 }
104146
105- rustrt:: linenoiseSetCompletionCallback ( callback) ;
147+ locked ! ( rustrt:: linenoiseSetCompletionCallback( callback) ) ;
106148}
0 commit comments