//! Guards the lockstep rules: the core must not use anything whose result //! can differ between machines or between runs. //! //! Clippy checks the same rules on resolved paths (see `core/clippy.toml`), //! which also catches aliases and grouped imports. This text search adds //! what Clippy cannot express, such as floating point types and global //! mutable state, and it runs even when Clippy does not. // Tests may read files. The rules of core/clippy.toml apply to core/src. #![allow(clippy::disallowed_methods)] use std::fs; use std::path::{Path, PathBuf}; /// Text that must not appear anywhere in the source of the core, and why. const FORBIDDEN: &[(&str, &str)] = &[ ("f32", "floating point results can differ between machines"), ("f64", "floating point results can differ between machines"), ( "HashMap", "iteration order differs between runs; use BTreeMap or Vec", ), ( "HashSet", "iteration order differs between runs; use BTreeSet or Vec", ), ("std::time", "the core must not read the clock"), ("Instant", "the core must not read the clock"), ("SystemTime", "the core must not read the clock"), ("std::thread", "the core must stay single-threaded"), ("std::env", "the core must not depend on the environment"), ( "std::fs", "the core must not touch files; the shell does that", ), ( "std::net", "the core must not open connections; the shell does that", ), ( "std::io", "the core must not read or write streams; the shell does that", ), ("std::process", "the core must not start or end processes"), ( "std::{", "grouped std imports hide forbidden modules from this search; import each path on its own line", ), ("static mut", "global mutable state can differ between runs"), ( "thread_local", "global mutable state can differ between runs", ), ("Atomic", "global mutable state can differ between runs"), ("Mutex", "global mutable state can differ between runs"), ("RwLock", "global mutable state can differ between runs"), ("OnceLock", "global mutable state can differ between runs"), ("LazyLock", "global mutable state can differ between runs"), ("unsafe", "the core forbids unsafe code"), ]; /// Every forbidden term in `text`, as (line number, term, reason). fn violations(text: &str) -> Vec<(usize, &'static str, &'static str)> { let mut found = Vec::new(); for (index, line) in text.lines().enumerate() { for &(needle, reason) in FORBIDDEN { if line.contains(needle) { found.push((index + 1, needle, reason)); } } } found } fn rust_files(dir: &Path, out: &mut Vec) { for entry in fs::read_dir(dir).expect("source directory is readable") { let path = entry.expect("directory entry is readable").path(); if path.is_dir() { rust_files(&path, out); } else if path.extension().is_some_and(|ext| ext == "rs") { out.push(path); } } } #[test] fn core_source_uses_no_forbidden_apis() { let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src"); let mut files = Vec::new(); rust_files(&src, &mut files); files.sort(); assert!( !files.is_empty(), "no source files found in {}", src.display() ); let mut problems = Vec::new(); for file in &files { let text = fs::read_to_string(file).expect("source file is readable"); for (line, needle, reason) in violations(&text) { problems.push(format!("{}:{line}: `{needle}`: {reason}", file.display())); } } assert!( problems.is_empty(), "forbidden APIs in the core:\n{}", problems.join("\n") ); } #[test] fn every_forbidden_term_is_detected() { for &(needle, _) in FORBIDDEN { let text = format!("pub fn fine() {{}}\nlet x = {needle};\n"); let found = violations(&text); assert!( found .iter() .any(|&(line, term, _)| line == 2 && term == needle), "`{needle}` was not detected: {found:?}" ); } } #[test] fn clean_code_has_no_violations() { let text = "pub fn add(a: u32, b: u32) -> u32 {\n a.wrapping_add(b)\n}\n"; assert!(violations(text).is_empty()); }