use alloc::boxed::Box;
#[cfg(target_has_atomic = "ptr")]
pub(crate) use alloc::sync::Arc;
#[cfg(not(target_has_atomic = "ptr"))]
pub(crate) use portable_atomic_util::Arc;
use crate::store::LeafStore;
pub type SharedStore = Arc<dyn LeafStore>;
pub fn shared_store<S: LeafStore + 'static>(store: S) -> SharedStore {
let boxed: Box<dyn LeafStore> = Box::new(store);
Arc::from(boxed)
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::store::{MemoryStore, KEY_TYPE_IDENTITY};
use crate::LeafDevice;
use std::string::String;
use std::vec::Vec;
use std::{format, fs, path::PathBuf};
#[test]
fn a_store_built_by_the_constructor_is_the_one_the_device_writes() {
let store = shared_store(MemoryStore::new());
let reader = Arc::clone(&store);
let _device = LeafDevice::open(store, "com.example.lock").expect("device opens");
let identity = reader
.load(KEY_TYPE_IDENTITY, "signature_public")
.expect("load succeeds");
assert!(
identity.is_some(),
"provisioning must write through the handle the constructor built"
);
}
fn tokens(src: &str) -> Vec<String> {
let chars: Vec<char> = src.chars().collect();
let ident = |c: char| c == '_' || c.is_alphanumeric();
let mut out = Vec::new();
let mut i = 0;
while i < chars.len() {
let c = chars[i];
let next = chars.get(i + 1).copied();
if c.is_whitespace() {
i += 1;
} else if c == '/' && next == Some('/') {
while i < chars.len() && chars[i] != '\n' {
i += 1;
}
} else if c == '/' && next == Some('*') {
let mut depth = 0usize;
while i < chars.len() {
if chars[i] == '/' && chars.get(i + 1) == Some(&'*') {
depth += 1;
i += 2;
} else if chars[i] == '*' && chars.get(i + 1) == Some(&'/') {
depth -= 1;
i += 2;
if depth == 0 {
break;
}
} else {
i += 1;
}
}
} else if c == '"' {
i += 1;
while i < chars.len() && chars[i] != '"' {
i += if chars[i] == '\\' { 2 } else { 1 };
}
i += 1;
} else if c == '\'' {
if next == Some('\\') {
i += 3;
while i < chars.len() && chars[i] != '\'' {
i += 1;
}
i += 1;
} else if chars.get(i + 2) == Some(&'\'') {
i += 3;
} else {
i += 1;
while i < chars.len() && ident(chars[i]) {
i += 1;
}
}
} else if ident(c) {
let begin = i;
while i < chars.len() && ident(chars[i]) {
i += 1;
}
let word: String = chars[begin..i].iter().collect();
let raw_prefix = word == "r" || word == "br" || word == "cr";
let hashes = chars[i..].iter().take_while(|&&h| h == '#').count();
if raw_prefix && chars.get(i + hashes) == Some(&'"') {
i += hashes + 1;
while i < chars.len() {
if chars[i] == '"'
&& chars[i + 1..].iter().take_while(|&&h| h == '#').count() >= hashes
{
i += 1 + hashes;
break;
}
i += 1;
}
} else if word == "b" && matches!(chars.get(i), Some('"') | Some('\'')) {
} else if word == "r" && hashes == 1 && chars.get(i + 1).is_some_and(|&n| ident(n))
{
i += 1;
} else {
out.push(word);
}
} else if c == ':' && next == Some(':') {
out.push("::".into());
i += 2;
} else {
out.push(c.to_string());
i += 1;
}
}
out
}
fn use_tree(
tokens: &[String],
at: &mut usize,
prefix: Vec<String>,
out: &mut Vec<(Vec<String>, Option<String>)>,
) {
let mut path = prefix;
while let Some(token) = tokens.get(*at) {
match token.as_str() {
"::" => *at += 1,
"{" => {
*at += 1;
while let Some(inner) = tokens.get(*at) {
match inner.as_str() {
"}" => {
*at += 1;
break;
}
"," => *at += 1,
";" => return,
_ => {
let before = *at;
use_tree(tokens, at, path.clone(), out);
if *at == before {
*at += 1;
}
}
}
}
return;
}
"*" => {
*at += 1;
path.push("*".into());
out.push((path, None));
return;
}
word if word.starts_with(|c: char| c == '_' || c.is_alphabetic()) => {
*at += 1;
if word != "self" {
path.push(word.into());
}
if tokens.get(*at).is_some_and(|t| t == "::") {
continue;
}
let rename = (tokens.get(*at).is_some_and(|t| t == "as"))
.then(|| tokens.get(*at + 1).cloned())
.flatten();
if rename.is_some() {
*at += 2;
}
out.push((path, rename));
return;
}
_ => return,
}
}
}
fn counted_pointer_paths(src: &str) -> Vec<String> {
let tokens = tokens(src);
let mut imports = Vec::new();
let mut aliases = vec![String::from("alloc")];
for (index, token) in tokens.iter().enumerate() {
if token == "use" {
use_tree(&tokens, &mut (index + 1), Vec::new(), &mut imports);
}
let rest = &tokens[index..];
if rest.len() >= 5
&& rest[0] == "extern"
&& rest[1] == "crate"
&& rest[2] == "alloc"
&& rest[3] == "as"
{
aliases.push(rest[4].clone());
}
}
for (path, _) in imports.iter_mut() {
let relative = path
.iter()
.take_while(|segment| matches!(segment.as_str(), "crate" | "self" | "super"))
.count();
path.drain(..relative);
}
loop {
let found: Vec<String> = imports
.iter()
.filter(|(path, rename)| {
path.len() == 1 && aliases.contains(&path[0]) && rename.is_some()
})
.filter_map(|(_, rename)| rename.clone())
.filter(|name| !aliases.contains(name))
.collect();
if found.is_empty() {
break;
}
aliases.extend(found);
}
let through_sync = |root: &str, second: &str| {
aliases.iter().any(|alias| alias == root) && (second == "sync" || second == "*")
};
let mut offenders: Vec<String> = imports
.iter()
.filter(|(path, _)| path.len() >= 2 && through_sync(&path[0], &path[1]))
.map(|(path, _)| format!("use {}", path.join("::")))
.collect();
offenders.extend(
aliases
.iter()
.skip(1)
.filter(|alias| alias.as_str() != "_")
.map(|alias| format!("alloc renamed to {alias}")),
);
offenders.extend(
tokens
.windows(3)
.filter(|w| w[1] == "::" && w[2] == "sync" && through_sync(&w[0], &w[2]))
.map(|w| format!("{}::sync", w[0])),
);
offenders
}
fn rust_files(dir: &std::path::Path, found: &mut Vec<PathBuf>) {
let entries = fs::read_dir(dir)
.unwrap_or_else(|error| panic!("{} is unreadable: {error}", dir.display()));
for entry in entries {
let path = entry
.unwrap_or_else(|error| {
panic!("an entry in {} is unreadable: {error}", dir.display())
})
.path();
if path.is_dir() {
rust_files(&path, found);
} else if path.extension().is_some_and(|ext| ext == "rs") {
found.push(path);
}
}
}
#[test]
fn the_guard_sees_every_route_to_the_counted_pointer() {
for code in [
"use alloc::sync::Arc;",
"use alloc::sync;",
"use ::alloc::sync::Weak;",
"use alloc::{string::String, sync::Arc};",
"use alloc::{sync::{Arc, Weak}, vec::Vec};",
"use alloc::{string::String, sync as s};",
"use alloc::{string::String, sync::*};",
"use alloc::*;",
"use alloc::{\n string::String,\n sync::Arc,\n};",
"pub(crate) use alloc::sync::Arc;",
"let store: alloc::sync::Arc<dyn LeafStore>;",
"extern crate alloc as heap;\nuse heap::sync::Arc;",
"use alloc as heap;\nfn f(_: heap::sync::Arc<u8>) {}",
"use alloc::{self as heap};\nuse heap::sync::Arc;",
"let quote = '\"'; use alloc::sync::Arc;",
"fn f<'a>(x: &'a str) -> &'a str { x }\nuse alloc::sync::Arc;",
"const S: &[u8] = b\"x\"; use alloc::sync::Arc;",
"const R: &str = r#\"a \" b\"#; use alloc::sync::Arc;",
"const C: &CStr = cr#\"a \" b\"#; use alloc::sync::Arc;",
"f('\\'','\"'); use alloc::sync::Arc; g('\"');",
"use crate::alloc::{string::String, sync::Arc};",
"use super::alloc::{sync::Arc};",
"use crate::alloc::*;",
"let store: crate::alloc::sync::Arc<dyn LeafStore>;",
"use crate::alloc as heap;",
"extern crate alloc as heap;",
"pub(crate) use alloc as mem;",
"use alloc::r#sync::Arc;",
"const B: &[u8] = br#\"a \" b\"#; use alloc::sync::Arc;",
] {
assert!(!counted_pointer_paths(code).is_empty(), "missed: {code}");
}
for code in [
"use std::sync::Mutex;",
"use std::sync::{Mutex, MutexGuard};",
"use std::{sync::Mutex, vec::Vec};",
"use core::sync::atomic::{AtomicU32, Ordering};",
"use crate::shared::Arc;",
"use alloc::{string::String, vec::Vec};",
"let n = 1; // alloc::sync is gone here",
"/* use alloc::sync::Arc; /* nested */ still a comment */",
"const S: &str = \"use alloc::sync::Arc;\";",
"const R: &str = r#\"use alloc::sync::Arc;\"#;",
"let sync = true; call(a, sync); file.sync_all();",
"mod sync { pub struct Arc; } use self::sync::Arc;",
"extern crate alloc; extern crate alloc as _;",
"let c = '\\''; let d = '\\\\'; let e = b'\\\\';",
"const S: &str = \"\\\" use alloc::sync::Arc; \\\"\";",
"/* a /* b */ use alloc::sync::Arc; */",
] {
assert_eq!(
counted_pointer_paths(code),
Vec::<String>::new(),
"false alarm: {code}"
);
}
}
#[test]
fn only_one_file_names_the_counted_pointer() {
let src = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src");
let exempt = src.join("shared.rs");
let mut files = Vec::new();
rust_files(&src, &mut files);
assert!(
files.contains(&exempt),
"the guard did not find {}",
exempt.display()
);
let offenders: Vec<String> = files
.iter()
.filter(|path| **path != exempt)
.flat_map(|path| {
let text = fs::read_to_string(path)
.unwrap_or_else(|error| panic!("{} is unreadable: {error}", path.display()));
counted_pointer_paths(&text)
.into_iter()
.map(move |found| format!("{}: {found}", path.display()))
})
.collect();
assert!(
offenders.is_empty(),
"these reach alloc::sync directly, which compiles on every host and \
breaks every target without compare-and-swap; take `Arc` from \
`crate::shared` instead: {offenders:?}"
);
}
}