Audit fixes: unsafe scope, unwrap cleanup, goto.rs consolidation, hex_edit tests, Cargo path fix
CRITICAL fixes (F1-F3 from audit):
- tlc_pty_login.rs: #![deny(unsafe_code)] with scoped #[allow] + SAFETY doc
- handlers.rs:55: unsafe .is_some()+unwrap() → if let Some(menubar)
- editor/mod.rs:515: .unwrap() → .expect("system clock before Unix epoch")
MEDIUM fixes:
- editor/goto.rs: 4 duplicate #[allow(result_large_err)] → 1 module-level #![allow]
- viewer/hex_edit.rs: +7 tests (hex_digit parser, nibble styles, HEX_CHARS table)
- viewer/mod.rs: pub(crate) mod tests for cross-module test helper access
INFRA:
- Cargo.toml: fixed redox_syscall path (../../../../→../../../../../) for
single canonical source path across all targets
1440 tests passing, zero warnings.
This commit is contained in:
@@ -76,7 +76,7 @@ suppaftp = { version = "6", optional = true }
|
||||
tar = { version = "0.4", optional = true }
|
||||
zip = { version = "2", default-features = false, optional = true }
|
||||
|
||||
redox_syscall = { path = "../../../../local/sources/syscall" }
|
||||
redox_syscall = { path = "../../../../../local/sources/syscall" }
|
||||
|
||||
# Syntax highlighting
|
||||
syntect = { version = "5", default-features = false, features = ["default-onig", "regex-onig"], optional = true }
|
||||
@@ -165,3 +165,4 @@ opt-level = 1
|
||||
|
||||
[patch.crates-io]
|
||||
libredox = { path = "../../../../../local/sources/libredox" }
|
||||
redox_syscall = { path = "../../../../../local/sources/syscall" }
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#![allow(unsafe_code)]
|
||||
#![deny(unsafe_code)]
|
||||
|
||||
use std::fs::OpenOptions;
|
||||
use std::os::fd::AsRawFd;
|
||||
@@ -18,6 +18,10 @@ fn main() -> anyhow::Result<()> {
|
||||
.write(true)
|
||||
.open(&tty_path)?;
|
||||
|
||||
// SAFETY: login_tty(2) has no safe Rust equivalent. The tty fd was
|
||||
// just opened by us and is guaranteed valid. On error, the return
|
||||
// code is checked and the last OS error is surfaced.
|
||||
#[allow(unsafe_code)]
|
||||
let rc = unsafe { libc::login_tty(tty.as_raw_fd()) };
|
||||
if rc != 0 {
|
||||
return Err(std::io::Error::last_os_error().into());
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
//! for clamping the returned offset to `[0, buf.len()]` and moving
|
||||
//! the cursor.
|
||||
//!
|
||||
//! Several functions return `Result<_, String>` for user-facing error
|
||||
//! messages; String is a large error type but this is intentional —
|
||||
//! these errors are displayed directly to the user.
|
||||
#![allow(clippy::result_large_err)]
|
||||
//!
|
||||
//! ## Address grammar
|
||||
//!
|
||||
//! [`parse_address`] accepts three input forms:
|
||||
@@ -37,7 +42,6 @@ use crate::editor::buffer::Buffer;
|
||||
/// Returns an error if `s` is empty, contains non-digit characters
|
||||
/// other than the separators `:` and `%`, or has malformed structure
|
||||
/// (e.g. `:` without a column, `%` not at the end).
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn parse_address(s: &str, total_lines: u32) -> Result<(u32, u32), String> {
|
||||
let s = s.trim();
|
||||
if s.is_empty() {
|
||||
@@ -98,7 +102,6 @@ pub fn parse_address(s: &str, total_lines: u32) -> Result<(u32, u32), String> {
|
||||
///
|
||||
/// Returns an error if `line == 0` (since 1 is the smallest valid
|
||||
/// 1-based line) or if the line is past the end of the buffer.
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn line_to_offset(buf: &Buffer, line: u32) -> Result<usize, String> {
|
||||
if line == 0 {
|
||||
return Err("line numbers are 1-based; 0 is invalid".to_string());
|
||||
@@ -124,7 +127,6 @@ pub fn line_to_offset(buf: &Buffer, line: u32) -> Result<usize, String> {
|
||||
///
|
||||
/// Returns an error if `line` is 0, `col` is 0, `line` is past the
|
||||
/// end of the buffer, or `col` is past the end of the line.
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn col_to_offset(buf: &Buffer, line: u32, col: u32) -> Result<usize, String> {
|
||||
let line_off = line_to_offset(buf, line)?;
|
||||
if col == 0 {
|
||||
@@ -153,7 +155,6 @@ pub fn col_to_offset(buf: &Buffer, line: u32, col: u32) -> Result<usize, String>
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if `pct > 100`.
|
||||
#[allow(clippy::result_large_err)]
|
||||
pub fn percent_to_offset(buf: &Buffer, pct: u8) -> Result<usize, String> {
|
||||
if pct > 100 {
|
||||
return Err(format!("percentage out of range: {pct}"));
|
||||
|
||||
@@ -52,9 +52,9 @@ impl Editor {
|
||||
}
|
||||
// When the F9 menu bar is open, route the key through it.
|
||||
// Esc / F9 close; arrows / Enter / hotkeys dispatch.
|
||||
if self.menubar.is_some() {
|
||||
if let Some(menubar) = self.menubar.as_mut() {
|
||||
use crate::editor::menubar::EditorMenuOutcome;
|
||||
let outcome = self.menubar.as_mut().unwrap().handle_key(key);
|
||||
let outcome = menubar.handle_key(key);
|
||||
return match outcome {
|
||||
EditorMenuOutcome::Handled | EditorMenuOutcome::Select(_, _) => {
|
||||
EditorResult::Running
|
||||
|
||||
@@ -512,7 +512,7 @@ impl Editor {
|
||||
"tlc-sort-{}",
|
||||
std::time::SystemTime::now()
|
||||
.duration_since(std::time::UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.expect("system clock before Unix epoch")
|
||||
.as_nanos()
|
||||
));
|
||||
let _ = std::fs::create_dir_all(&tmp_dir);
|
||||
|
||||
@@ -176,4 +176,69 @@ fn hex_digit(key: Key) -> Option<u8> {
|
||||
'A'..='F' => Some(c as u8 - b'A' + 10),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::key::Modifiers;
|
||||
|
||||
fn plain_key(c: char) -> Key {
|
||||
Key { code: c as u32, mods: Modifiers::empty() }
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_digit_parses_0_to_f() {
|
||||
for (ch, expected) in [
|
||||
('0', 0), ('1', 1), ('2', 2), ('3', 3), ('4', 4),
|
||||
('5', 5), ('6', 6), ('7', 7), ('8', 8), ('9', 9),
|
||||
('a', 10), ('b', 11), ('c', 12), ('d', 13), ('e', 14), ('f', 15),
|
||||
('A', 10), ('B', 11), ('C', 12), ('D', 13), ('E', 14), ('F', 15),
|
||||
] {
|
||||
assert_eq!(hex_digit(plain_key(ch)), Some(expected), "failed for '{ch}'");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_digit_rejects_non_hex() {
|
||||
assert_eq!(hex_digit(plain_key('g')), None);
|
||||
assert_eq!(hex_digit(plain_key('z')), None);
|
||||
assert_eq!(hex_digit(plain_key(' ')), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_digit_rejects_modified_keys() {
|
||||
let ctrl_f = Key::ctrl('f');
|
||||
assert_eq!(hex_digit(ctrl_f), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_nibble_oob_returns_question() {
|
||||
let p = crate::viewer::tests::make_text("hex.bin", &[0x41]);
|
||||
let v = crate::viewer::Viewer::open(&p).unwrap();
|
||||
assert_eq!(hex_nibble_char(&v, 99, true), '?');
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn active_nibble_style_is_bold_reversed() {
|
||||
let theme = &*crate::terminal::color::DEFAULT_THEME;
|
||||
let style = active_nibble_style(theme);
|
||||
assert!(style.add_modifier.contains(Modifier::BOLD));
|
||||
assert!(style.add_modifier.contains(Modifier::REVERSED));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cursor_off_style_has_no_bold() {
|
||||
let theme = &*crate::terminal::color::DEFAULT_THEME;
|
||||
let style = cursor_off_style(theme);
|
||||
assert!(!style.add_modifier.contains(Modifier::BOLD));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_chars_table_is_16_entries() {
|
||||
assert_eq!(HEX_CHARS.len(), 16);
|
||||
assert_eq!(HEX_CHARS[0], '0');
|
||||
assert_eq!(HEX_CHARS[10], 'a');
|
||||
assert_eq!(HEX_CHARS[15], 'f');
|
||||
}
|
||||
}
|
||||
@@ -1539,12 +1539,12 @@ fn format_size(bytes: u64) -> String {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub(crate) mod tests {
|
||||
use super::*;
|
||||
use ratatui::backend::TestBackend;
|
||||
use ratatui::Terminal;
|
||||
|
||||
fn make_text(name: &str, data: &[u8]) -> PathBuf {
|
||||
pub(crate) fn make_text(name: &str, data: &[u8]) -> PathBuf {
|
||||
let dir = std::env::temp_dir().join("tlc-viewer-mod-test");
|
||||
let _ = std::fs::create_dir_all(&dir);
|
||||
let p = dir.join(name);
|
||||
|
||||
Reference in New Issue
Block a user