Skip to content

Commit 80133c4

Browse files
authored
Fix safety issues with some static variables (fish-shell#10329)
Add safe Send/Sync wrapper for main thread data
2 parents 3c7b2af + 1ac1775 commit 80133c4

File tree

5 files changed

+66
-46
lines changed

5 files changed

+66
-46
lines changed

src/output.rs

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::color::RgbColor;
33
use crate::common::{self, wcs2string_appending};
44
use crate::curses::{self, tparm1, Term};
55
use crate::env::EnvVar;
6+
use crate::threads::MainThread;
67
use crate::wchar::prelude::*;
78
use bitflags::bitflags;
89
use std::cell::RefCell;
@@ -444,14 +445,10 @@ impl Outputter {
444445

445446
/// Access the outputter for stdout.
446447
/// This should only be used from the main thread.
447-
pub fn stdoutput() -> &'static mut RefCell<Outputter> {
448-
crate::threads::assert_is_main_thread();
449-
static mut STDOUTPUT: RefCell<Outputter> =
450-
RefCell::new(Outputter::new_from_fd(libc::STDOUT_FILENO));
451-
// Safety: this is only called from the main thread.
452-
// XXX: creating and using multiple (read or write!) references to the same mutable static
453-
// is undefined behavior!
454-
unsafe { &mut STDOUTPUT }
448+
pub fn stdoutput() -> &'static RefCell<Outputter> {
449+
static STDOUTPUT: MainThread<RefCell<Outputter>> =
450+
MainThread::new(RefCell::new(Outputter::new_from_fd(libc::STDOUT_FILENO)));
451+
STDOUTPUT.get()
455452
}
456453
}
457454

src/parser.rs

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ use crate::parse_execution::{EndExecutionReason, ParseExecutionContext};
2727
use crate::parse_tree::{parse_source, ParsedSourceRef};
2828
use crate::proc::{job_reap, JobGroupRef, JobList, JobRef, ProcStatus};
2929
use crate::signal::{signal_check_cancel, signal_clear_cancel, Signal};
30-
use crate::threads::assert_is_main_thread;
30+
use crate::threads::{assert_is_main_thread, MainThread};
3131
use crate::util::get_time;
3232
use crate::wait_handle::WaitHandleStore;
3333
use crate::wchar::{wstr, WString, L};
@@ -414,19 +414,11 @@ impl Parser {
414414
false
415415
}
416416

417-
/// Get the "principal" parser, whatever that is.
417+
/// Get the "principal" parser, whatever that is. Can only be called by the main thread.
418418
pub fn principal_parser() -> &'static Parser {
419-
// XXX: We use `static mut` as a hack to work around the fact that Parser doesn't implement
420-
// Sync! Even though we are wrapping it in Lazy<> and it compiles without an error, that
421-
// doesn't mean this is safe to access across threads!
422-
static mut PRINCIPAL: Lazy<ParserRef> =
423-
Lazy::new(|| Parser::new(EnvStack::principal().clone(), true));
424-
// XXX: Creating and using multiple (read or write!) references to the same mutable static
425-
// is undefined behavior!
426-
unsafe {
427-
PRINCIPAL.assert_can_execute();
428-
&PRINCIPAL
429-
}
419+
static PRINCIPAL: Lazy<MainThread<ParserRef>> =
420+
Lazy::new(|| MainThread::new(Parser::new(EnvStack::principal().clone(), true)));
421+
PRINCIPAL.get()
430422
}
431423

432424
/// Assert that this parser is allowed to execute on the current thread.

src/reader.rs

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1906,7 +1906,7 @@ impl ReaderData {
19061906
perror("tcsetattr"); // return to previous mode
19071907
}
19081908
Outputter::stdoutput()
1909-
.get_mut()
1909+
.borrow_mut()
19101910
.set_color(RgbColor::RESET, RgbColor::RESET);
19111911
}
19121912
rls.finished.then(|| zelf.command_line.text().to_owned())
@@ -2946,8 +2946,9 @@ impl ReaderData {
29462946
el.end_edit_group();
29472947
}
29482948
rl::DisableMouseTracking => {
2949-
let outp = Outputter::stdoutput().get_mut();
2950-
outp.write_wstr(L!("\x1B[?1000l"));
2949+
Outputter::stdoutput()
2950+
.borrow_mut()
2951+
.write_wstr(L!("\x1B[?1000l"));
29512952
}
29522953
rl::ClearScreenAndRepaint => {
29532954
self.parser().libdata_mut().pods.is_repaint = true;
@@ -2958,8 +2959,7 @@ impl ReaderData {
29582959
// and *then* reexecute the prompt and overdraw it.
29592960
// This removes the flicker,
29602961
// while keeping the prompt up-to-date.
2961-
let outp = Outputter::stdoutput().get_mut();
2962-
outp.write_wstr(&clear);
2962+
Outputter::stdoutput().borrow_mut().write_wstr(&clear);
29632963
self.screen.reset_line(/*repaint_prompt=*/ true);
29642964
self.layout_and_repaint(L!("readline"));
29652965
}
@@ -3492,7 +3492,7 @@ fn reader_interactive_init(parser: &Parser) {
34923492
/// Destroy data for interactive use.
34933493
fn reader_interactive_destroy() {
34943494
Outputter::stdoutput()
3495-
.get_mut()
3495+
.borrow_mut()
34963496
.set_color(RgbColor::RESET, RgbColor::RESET);
34973497
}
34983498

@@ -3575,7 +3575,7 @@ pub fn reader_write_title(
35753575
}
35763576

35773577
Outputter::stdoutput()
3578-
.get_mut()
3578+
.borrow_mut()
35793579
.set_color(RgbColor::RESET, RgbColor::RESET);
35803580
if reset_cursor_position && !lst.is_empty() {
35813581
// Put the cursor back at the beginning of the line (issue #2453).
@@ -4587,9 +4587,10 @@ fn reader_run_command(parser: &Parser, cmd: &wstr) -> EvalRes {
45874587
.set_one(L!("_"), EnvMode::GLOBAL, ft.to_owned());
45884588
}
45894589

4590-
let outp = Outputter::stdoutput().get_mut();
45914590
reader_write_title(cmd, parser, true);
4592-
outp.set_color(RgbColor::NORMAL, RgbColor::NORMAL);
4591+
Outputter::stdoutput()
4592+
.borrow_mut()
4593+
.set_color(RgbColor::NORMAL, RgbColor::NORMAL);
45934594
term_donate(false);
45944595

45954596
let time_before = Instant::now();

src/screen.rs

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
//! of text around to handle text insertion.
99
1010
use crate::pager::{PageRendering, Pager};
11+
use std::cell::RefCell;
1112
use std::collections::LinkedList;
1213
use std::ffi::{CStr, CString};
1314
use std::io::Write;
@@ -176,7 +177,7 @@ pub struct Screen {
176177
pub autosuggestion_is_truncated: bool,
177178

178179
/// Receiver for our output.
179-
outp: &'static mut Outputter,
180+
outp: &'static RefCell<Outputter>,
180181

181182
/// The internal representation of the desired screen contents.
182183
desired: ScreenData,
@@ -208,7 +209,7 @@ pub struct Screen {
208209
impl Screen {
209210
pub fn new() -> Self {
210211
Self {
211-
outp: Outputter::stdoutput().get_mut(),
212+
outp: Outputter::stdoutput(),
212213
autosuggestion_is_truncated: Default::default(),
213214
desired: Default::default(),
214215
actual: Default::default(),
@@ -637,9 +638,9 @@ impl Screen {
637638
// Either issue a cr to go back to the beginning of this line, or a nl to go to the
638639
// beginning of the next one, depending on what we think is more efficient.
639640
if new_y <= zelf.actual.cursor.y {
640-
zelf.outp.push(b'\r');
641+
zelf.outp.borrow_mut().push(b'\r');
641642
} else {
642-
zelf.outp.push(b'\n');
643+
zelf.outp.borrow_mut().push(b'\n');
643644
zelf.actual.cursor.y += 1;
644645
}
645646
// Either way we're not in the first column.
@@ -674,13 +675,13 @@ impl Screen {
674675
};
675676

676677
for _ in 0..y_steps.abs_diff(0) {
677-
zelf.outp.tputs_if_some(&s);
678+
zelf.outp.borrow_mut().tputs_if_some(&s);
678679
}
679680

680681
let mut x_steps =
681682
isize::try_from(new_x).unwrap() - isize::try_from(zelf.actual.cursor.x).unwrap();
682683
if x_steps != 0 && new_x == 0 {
683-
zelf.outp.push(b'\r');
684+
zelf.outp.borrow_mut().push(b'\r');
684685
x_steps = 0;
685686
}
686687

@@ -700,10 +701,10 @@ impl Screen {
700701
multi_str.as_ref().unwrap(),
701702
i32::try_from(x_steps.abs_diff(0)).unwrap(),
702703
);
703-
zelf.outp.tputs_if_some(&multi_param);
704+
zelf.outp.borrow_mut().tputs_if_some(&multi_param);
704705
} else {
705706
for _ in 0..x_steps.abs_diff(0) {
706-
zelf.outp.tputs_if_some(&s);
707+
zelf.outp.borrow_mut().tputs_if_some(&s);
707708
}
708709
}
709710

@@ -715,7 +716,7 @@ impl Screen {
715716
fn write_char(&mut self, c: char, width: isize) {
716717
let mut zelf = self.scoped_buffer();
717718
zelf.actual.cursor.x = zelf.actual.cursor.x.wrapping_add(width as usize);
718-
zelf.outp.writech(c);
719+
zelf.outp.borrow_mut().writech(c);
719720
if Some(zelf.actual.cursor.x) == zelf.actual.screen_width && allow_soft_wrap() {
720721
zelf.soft_wrap_location = Some(Cursor {
721722
x: 0,
@@ -732,16 +733,16 @@ impl Screen {
732733

733734
/// Send the specified string through tputs and append the output to the screen's outputter.
734735
fn write_mbs(&mut self, s: &CStr) {
735-
self.outp.tputs(s)
736+
self.outp.borrow_mut().tputs(s);
736737
}
737738

738739
fn write_mbs_if_some(&mut self, s: &Option<impl AsRef<CStr>>) -> bool {
739-
self.outp.tputs_if_some(s)
740+
self.outp.borrow_mut().tputs_if_some(s)
740741
}
741742

742743
/// Convert a wide string to a multibyte string and append it to the buffer.
743744
fn write_str(&mut self, s: &wstr) {
744-
self.outp.write_wstr(s)
745+
self.outp.borrow_mut().write_wstr(s);
745746
}
746747

747748
/// Update the cursor as if soft wrapping had been performed.
@@ -766,9 +767,9 @@ impl Screen {
766767
}
767768

768769
fn scoped_buffer(&mut self) -> impl ScopeGuarding<Target = &mut Screen> {
769-
self.outp.begin_buffering();
770+
self.outp.borrow_mut().begin_buffering();
770771
ScopeGuard::new(self, |zelf| {
771-
zelf.outp.end_buffering();
772+
zelf.outp.borrow_mut().end_buffering();
772773
})
773774
}
774775

@@ -779,7 +780,7 @@ impl Screen {
779780
let mut set_color = |zelf: &mut Self, c| {
780781
let fg = color_resolver.resolve_spec(&c, false, vars);
781782
let bg = color_resolver.resolve_spec(&c, true, vars);
782-
zelf.outp.set_color(fg, bg);
783+
zelf.outp.borrow_mut().set_color(fg, bg);
783784
};
784785

785786
let mut cached_layouts = LAYOUT_CACHE_SHARED.lock().unwrap();
@@ -835,6 +836,7 @@ impl Screen {
835836
for line_break in left_prompt_layout.line_breaks {
836837
zelf.write_str(&left_prompt[start..line_break]);
837838
zelf.outp
839+
.borrow_mut()
838840
.tputs_if_some(&term.and_then(|term| term.clr_eol.as_ref()));
839841
start = line_break;
840842
}
@@ -1068,7 +1070,7 @@ impl Screen {
10681070
/// Issues an immediate clr_eos.
10691071
pub fn screen_force_clear_to_end() {
10701072
Outputter::stdoutput()
1071-
.get_mut()
1073+
.borrow_mut()
10721074
.tputs_if_some(&term().unwrap().clr_eos);
10731075
}
10741076

src/threads.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use crate::flog::{FloggableDebug, FLOG};
55
use crate::reader::ReaderData;
66
use once_cell::race::OnceBox;
7+
use std::marker::PhantomData;
78
use std::num::NonZeroU64;
89
use std::sync::atomic::{AtomicBool, Ordering};
910
use std::sync::{Arc, Mutex};
@@ -358,6 +359,33 @@ impl ThreadPool {
358359
}
359360
}
360361

362+
/// A `Sync` and `Send` wrapper for non-`Sync`/`Send` types.
363+
/// Only allows access from the main thread.
364+
pub struct MainThread<T> {
365+
data: T,
366+
// Make type !Send and !Sync by default
367+
_marker: PhantomData<*const ()>,
368+
}
369+
370+
// Manually implement Send and Sync for MainThread<T> to ensure it can be shared across threads
371+
// as long as T is 'static.
372+
unsafe impl<T: 'static> Send for MainThread<T> {}
373+
unsafe impl<T: 'static> Sync for MainThread<T> {}
374+
375+
impl<T> MainThread<T> {
376+
pub const fn new(value: T) -> Self {
377+
Self {
378+
data: value,
379+
_marker: PhantomData,
380+
}
381+
}
382+
383+
pub fn get(&self) -> &T {
384+
assert_is_main_thread();
385+
&self.data
386+
}
387+
}
388+
361389
pub struct WorkerThread {
362390
/// The data shared with the [`ThreadPool`].
363391
shared: Arc<ThreadPoolShared>,

0 commit comments

Comments
 (0)