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,38 +42,53 @@ 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+ ( locked ! ( rustrt:: linenoiseHistorySave( buf) ) ) == 1 as c_int
6378 }
6479}
6580
6681/// Load line history from a file
67- pub unsafe fn load_history ( file : & str ) -> bool {
82+ pub fn load_history ( file : & str ) -> bool {
6883 do file. with_c_str |buf| {
69- rustrt:: linenoiseHistoryLoad ( buf) == 1 as c_int
84+ ( locked ! ( rustrt:: linenoiseHistoryLoad( buf) ) ) == 1 as c_int
7085 }
7186}
7287
7388/// Print out a prompt and then wait for input and return it
74- pub unsafe fn read ( prompt : & str ) -> Option < ~str > {
89+ pub fn read ( prompt : & str ) -> Option < ~str > {
7590 do prompt. with_c_str |buf| {
76- let line = rustrt:: linenoise ( buf) ;
91+ let line = locked ! ( rustrt:: linenoise( buf) ) ;
7792
7893 if line. is_null ( ) { None }
7994 else { Some ( str:: raw:: from_c_str ( line) ) }
@@ -84,8 +99,13 @@ pub type CompletionCb = @fn(~str, @fn(~str));
8499
85100static complete_key: local_data:: Key < @CompletionCb > = & local_data:: Key ;
86101
87- /// Bind to the main completion callback
88- pub unsafe fn complete ( cb : CompletionCb ) {
102+ /// Bind to the main completion callback.
103+ ///
104+ /// The completion callback should not call any `extra::rl` functions
105+ /// other than the closure that it receives as its second
106+ /// argument. Calling such a function will deadlock on the mutex used
107+ /// to ensure that the calls are thread-safe.
108+ pub fn complete ( cb : CompletionCb ) {
89109 local_data:: set ( complete_key, @cb) ;
90110
91111 extern fn callback ( line : * c_char , completions : * ( ) ) {
@@ -95,12 +115,16 @@ pub unsafe fn complete(cb: CompletionCb) {
95115 unsafe {
96116 do cb ( str:: raw:: from_c_str ( line) ) |suggestion| {
97117 do suggestion. with_c_str |buf| {
118+ // This isn't locked, because `callback` gets
119+ // called inside `rustrt::linenoise`, which
120+ // *is* already inside the mutex, so
121+ // re-locking would be a deadlock.
98122 rustrt:: linenoiseAddCompletion ( completions, buf) ;
99123 }
100124 }
101125 }
102126 }
103127 }
104128
105- rustrt:: linenoiseSetCompletionCallback ( callback) ;
129+ locked ! ( rustrt:: linenoiseSetCompletionCallback( callback) ) ;
106130}
0 commit comments