8
8
// option. This file may not be copied, modified, or distributed
9
9
// except according to those terms.
10
10
11
- // FIXME #3921. This is unsafe because linenoise uses global mutable
12
- // state without mutexes.
13
-
14
11
use std:: c_str:: ToCStr ;
15
12
use 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 ;
18
15
19
16
#[ cfg( stage0) ]
20
17
pub mod rustrt {
@@ -28,6 +25,9 @@ pub mod rustrt {
28
25
fn linenoiseHistoryLoad ( file : * c_char ) -> c_int ;
29
26
fn linenoiseSetCompletionCallback ( callback : * u8 ) ;
30
27
fn linenoiseAddCompletion ( completions : * ( ) , line : * c_char ) ;
28
+
29
+ fn rust_take_linenoise_lock ( ) ;
30
+ fn rust_drop_linenoise_lock ( ) ;
31
31
}
32
32
}
33
33
@@ -42,65 +42,107 @@ pub mod rustrt {
42
42
externfn ! ( fn linenoiseHistoryLoad( file: * c_char) -> c_int)
43
43
externfn ! ( fn linenoiseSetCompletionCallback( callback: extern "C" fn ( * i8 , * ( ) ) ) )
44
44
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
+ }
45
60
}
46
61
47
62
/// Add a line to history
48
- pub unsafe fn add_history ( line : & str ) -> bool {
63
+ pub fn add_history ( line : & str ) -> bool {
49
64
do line. with_c_str |buf| {
50
- rustrt:: linenoiseHistoryAdd ( buf) == 1 as c_int
65
+ ( locked ! ( rustrt:: linenoiseHistoryAdd( buf) ) ) == 1 as c_int
51
66
}
52
67
}
53
68
54
69
/// 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
57
72
}
58
73
59
74
/// Save line history to a file
60
- pub unsafe fn save_history ( file : & str ) -> bool {
75
+ pub fn save_history ( file : & str ) -> bool {
61
76
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
63
79
}
64
80
}
65
81
66
82
/// Load line history from a file
67
- pub unsafe fn load_history ( file : & str ) -> bool {
83
+ pub fn load_history ( file : & str ) -> bool {
68
84
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
70
87
}
71
88
}
72
89
73
90
/// 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 > {
75
92
do prompt. with_c_str |buf| {
76
- let line = rustrt:: linenoise ( buf) ;
93
+ let line = locked ! ( rustrt:: linenoise( buf) ) ;
77
94
78
95
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
+ }
80
107
}
81
108
}
82
109
83
110
pub type CompletionCb = @fn ( ~str , @fn ( ~str ) ) ;
84
111
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
+ }
99
141
}
100
142
}
101
143
}
102
144
}
103
145
}
104
146
105
- rustrt:: linenoiseSetCompletionCallback ( callback) ;
147
+ locked ! ( rustrt:: linenoiseSetCompletionCallback( callback) ) ;
106
148
}
0 commit comments