//! 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"), ("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"), ("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()); }