#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct HostImport {
pub module: &'static str,
pub name: &'static str,
}
impl HostImport {
const fn new(module: &'static str, name: &'static str) -> Self {
Self { module, name }
}
}
pub const DECLARED_HOST_IMPORTS: &[HostImport] = &[
HostImport::new("freenet_contract_io", "__frnt__fill_buffer"),
HostImport::new(
"freenet_delegate_contracts",
"__frnt__delegate__get_contract_state",
),
HostImport::new(
"freenet_delegate_contracts",
"__frnt__delegate__get_contract_state_len",
),
HostImport::new("freenet_delegate_ctx", "__frnt__delegate__ctx_len"),
HostImport::new("freenet_delegate_ctx", "__frnt__delegate__ctx_read"),
HostImport::new("freenet_delegate_ctx", "__frnt__delegate__ctx_write"),
HostImport::new(
"freenet_delegate_management",
"__frnt__delegate__create_delegate",
),
HostImport::new("freenet_delegate_secrets", "__frnt__delegate__get_secret"),
HostImport::new(
"freenet_delegate_secrets",
"__frnt__delegate__get_secret_len",
),
HostImport::new("freenet_delegate_secrets", "__frnt__delegate__has_secret"),
HostImport::new("freenet_delegate_secrets", "__frnt__delegate__list_secrets"),
HostImport::new(
"freenet_delegate_secrets",
"__frnt__delegate__list_secrets_len",
),
HostImport::new(
"freenet_delegate_secrets",
"__frnt__delegate__remove_secret",
),
HostImport::new("freenet_delegate_secrets", "__frnt__delegate__set_secret"),
HostImport::new("freenet_log", "__frnt__logger__info"),
HostImport::new("freenet_rand", "__frnt__rand__rand_bytes"),
HostImport::new("freenet_time", "__frnt__time__utc_now"),
];
#[cfg(test)]
mod host_import_manifest_tests {
use super::{HostImport, DECLARED_HOST_IMPORTS};
const SCANNED: &[(&str, &str)] = &[
("delegate_host.rs", include_str!("delegate_host.rs")),
("host_imports.rs", include_str!("host_imports.rs")),
("log.rs", include_str!("log.rs")),
("rand.rs", include_str!("rand.rs")),
("time.rs", include_str!("time.rs")),
("memory/buf.rs", include_str!("memory/buf.rs")),
];
fn strip_test_modules(src: &str) -> &str {
match src.find("#[cfg(test)]") {
Some(i) => &src[..i],
None => src,
}
}
const UNPARSED: &str = "<unparsed-extern-line>";
fn strip_visibility(t: &str) -> &str {
let Some(rest) = t.strip_prefix("pub") else {
return t;
};
let rest = match rest.chars().next() {
Some('(') => match rest.find(')') {
Some(i) => &rest[i + 1..],
None => return t,
},
Some(c) if c.is_whitespace() => rest,
_ => return t,
};
rest.trim_start()
}
fn ends_with_semicolon_ignoring_trailing_comment(t: &str) -> bool {
let core = match t.find("//") {
Some(i) => t[..i].trim_end(),
None => t,
};
core.ends_with(';')
}
fn parse_imports(src: &str) -> Vec<(String, String)> {
let mut out = Vec::new();
let mut pending_module: Option<String> = None;
let mut current_module: Option<String> = None;
let mut in_extern = false;
let mut in_signature = false;
for line in src.lines() {
let t = line.trim();
if in_extern {
if t == "}" || t.starts_with("} ") {
in_extern = false;
in_signature = false;
current_module = None;
continue;
}
if in_signature {
if ends_with_semicolon_ignoring_trailing_comment(t) {
in_signature = false;
}
continue;
}
if t.is_empty() || t.starts_with("//") || t.starts_with("#[") {
continue;
}
let decl = strip_visibility(t);
if let Some(rest) = decl.strip_prefix("fn ").or_else(|| {
decl.strip_prefix("unsafe fn ")
}) {
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
if name.is_empty() {
out.push((UNPARSED.to_string(), t.to_string()));
} else {
let module = current_module.clone().unwrap_or_else(|| "env".to_string());
out.push((module, name));
if !ends_with_semicolon_ignoring_trailing_comment(t) {
in_signature = true;
}
}
} else {
out.push((UNPARSED.to_string(), t.to_string()));
}
continue;
}
if t.starts_with("#[link(") && t.contains("wasm_import_module") {
if let Some(eq) = t.find('=') {
let after = &t[eq + 1..];
if let Some(open) = after.find('"') {
let rest = &after[open + 1..];
if let Some(close) = rest.find('"') {
pending_module = Some(rest[..close].to_string());
}
}
}
continue;
}
let opener = t
.strip_prefix("unsafe extern \"C\"")
.or_else(|| t.strip_prefix("extern \"C\""));
if let Some(rest) = opener {
let rest = rest.trim();
if rest.is_empty() || rest == "{" {
in_extern = true;
in_signature = false;
current_module = pending_module.take();
} else {
pending_module = None;
}
continue;
}
if !t.starts_with("#[") && !t.is_empty() {
pending_module = None;
}
}
out
}
fn declared_from_source() -> Vec<(String, String)> {
let mut found: Vec<(String, String)> = SCANNED
.iter()
.flat_map(|(_, src)| parse_imports(strip_test_modules(src)))
.collect();
found.sort();
found.dedup();
found
}
#[test]
fn the_declared_manifest_matches_the_extern_blocks() {
let from_source = declared_from_source();
let mut from_manifest: Vec<(String, String)> = DECLARED_HOST_IMPORTS
.iter()
.map(|i| (i.module.to_string(), i.name.to_string()))
.collect();
from_manifest.sort();
let missing: Vec<_> = from_source
.iter()
.filter(|i| !from_manifest.contains(i))
.collect();
let extra: Vec<_> = from_manifest
.iter()
.filter(|i| !from_source.contains(i))
.collect();
assert!(
missing.is_empty() && extra.is_empty(),
"host import manifest is out of step with the extern \"C\" blocks.\n\
Declared in source but absent from DECLARED_HOST_IMPORTS: {missing:?}\n\
Listed in DECLARED_HOST_IMPORTS but not declared in source: {extra:?}\n\
\n\
Adding an entry is only correct if freenet-core registers it. See \
the module docs."
);
}
#[test]
fn the_manifest_is_sorted_and_unique() {
let mut sorted = DECLARED_HOST_IMPORTS.to_vec();
sorted.sort();
assert_eq!(
DECLARED_HOST_IMPORTS,
sorted.as_slice(),
"DECLARED_HOST_IMPORTS must be sorted by (module, name)"
);
let mut seen = sorted.clone();
seen.dedup();
assert_eq!(
seen.len(),
DECLARED_HOST_IMPORTS.len(),
"DECLARED_HOST_IMPORTS contains duplicates"
);
}
#[test]
fn the_imports_removed_in_0_11_0_have_not_come_back() {
const REMOVED: &[&str] = &[
"__frnt__delegate__put_contract_state",
"__frnt__delegate__update_contract_state",
"__frnt__delegate__subscribe_contract",
"__frnt__delegate__subscribe_contract_checked",
"__frnt__delegate__list_subscriptions_len",
"__frnt__delegate__list_subscriptions",
"__frnt__delegate__schedule_wakeup",
];
let from_source = declared_from_source();
for name in REMOVED {
assert!(
!from_source.iter().any(|(_, n)| n == name),
"`{name}` was removed in 0.11.0 because no released freenet-core \
registers it; a delegate calling it fails to instantiate. \
Re-adding it needs the host side to exist first."
);
assert!(
!DECLARED_HOST_IMPORTS.iter().any(|i| i.name == *name),
"`{name}` is back in DECLARED_HOST_IMPORTS; see freenet-stdlib#133"
);
}
}
#[test]
fn prose_mentioning_an_import_is_not_read_as_a_declaration() {
let src = r#"
/// Calls `__frnt__delegate__ghost` under the hood, see fn __frnt__delegate__phantom
// fn __frnt__delegate__commented_out(a: i32) -> i32;
#[cfg(target_family = "wasm")]
#[link(wasm_import_module = "freenet_real")]
extern "C" {
/// Doc mentioning fn __frnt__delegate__not_this
fn __frnt__delegate__real(a: i32) -> i32;
}
fn __frnt__delegate__local_definition() -> i64 { 0 }
"#;
assert_eq!(
parse_imports(src),
vec![(
"freenet_real".to_string(),
"__frnt__delegate__real".to_string()
)],
"only a `fn` declaration line inside an extern block is an import"
);
}
#[test]
fn every_visibility_spelling_is_recognised() {
for vis in [
"",
"pub ",
"pub(crate) ",
"pub(super) ",
"pub(self) ",
"pub(in crate::memory) ",
] {
let src = format!(
"#[link(wasm_import_module = \"freenet_m\")]\nextern \"C\" {{\n {vis}fn __frnt__x() -> i32;\n}}\n"
);
assert_eq!(
parse_imports(&src),
vec![("freenet_m".to_string(), "__frnt__x".to_string())],
"visibility {vis:?} was not recognised"
);
}
}
#[test]
fn an_unreadable_declaration_is_reported_rather_than_skipped() {
let src = "#[link(wasm_import_module = \"freenet_m\")]\nextern \"C\" {\n static SOMETHING: i32;\n}\n";
let parsed = parse_imports(src);
assert_eq!(parsed.len(), 1);
assert_eq!(parsed[0].0, UNPARSED, "unreadable line must be flagged");
assert!(
!DECLARED_HOST_IMPORTS.iter().any(|i| i.module == UNPARSED),
"UNPARSED must never be a legitimate manifest module"
);
}
#[test]
fn a_multi_line_signature_is_one_import_and_its_arguments_are_not() {
let src = "#[link(wasm_import_module = \"freenet_m\")]\nextern \"C\" {\n fn __frnt__wide(\n a: i64,\n b: i32,\n ) -> i64;\n fn __frnt__narrow() -> i32;\n}\n";
assert_eq!(
parse_imports(src),
vec![
("freenet_m".to_string(), "__frnt__wide".to_string()),
("freenet_m".to_string(), "__frnt__narrow".to_string()),
]
);
}
#[test]
fn an_extern_c_function_definition_is_not_an_import_block() {
let src = "#[no_mangle]\nunsafe extern \"C\" fn __frnt__stub(_a: i64) -> u32 {\n 0\n}\n";
assert_eq!(parse_imports(src), vec![]);
let src = "#[no_mangle]\nextern \"C\" fn __frnt__stub2() -> u32 {\n 0\n}\n";
assert_eq!(parse_imports(src), vec![]);
let buf = strip_test_modules(include_str!("memory/buf.rs"));
assert_eq!(
parse_imports(buf),
vec![(
"freenet_contract_io".to_string(),
"__frnt__fill_buffer".to_string()
)],
"buf.rs declares one import and defines one stub of the same name"
);
}
#[test]
fn a_trailing_comment_after_the_closing_semicolon_does_not_swallow_the_next_declaration() {
let src = "#[link(wasm_import_module = \"freenet_m\")]\nextern \"C\" {\n fn __frnt__first() -> i32; // trailing comment\n fn __frnt__second() -> i32;\n}\n";
assert_eq!(
parse_imports(src),
vec![
("freenet_m".to_string(), "__frnt__first".to_string()),
("freenet_m".to_string(), "__frnt__second".to_string()),
],
"a trailing comment on the closing line must not hide the next import"
);
let src = "#[link(wasm_import_module = \"freenet_m\")]\nextern \"C\" {\n fn __frnt__wide(\n a: i64,\n ) -> i64; // trailing comment\n fn __frnt__narrow() -> i32;\n}\n";
assert_eq!(
parse_imports(src),
vec![
("freenet_m".to_string(), "__frnt__wide".to_string()),
("freenet_m".to_string(), "__frnt__narrow".to_string()),
],
"a trailing comment on a multi-line signature's closing line must not hide the next import"
);
}
#[test]
fn the_rust_2024_unsafe_extern_spelling_is_recognised() {
let src = "#[link(wasm_import_module = \"freenet_m\")]\nunsafe extern \"C\" {\n fn __frnt__x() -> i32;\n}\n";
assert_eq!(
parse_imports(src),
vec![("freenet_m".to_string(), "__frnt__x".to_string())]
);
}
#[test]
fn a_link_attribute_does_not_leak_past_intervening_code() {
let src = r#"
#[link(wasm_import_module = "freenet_first")]
extern "C" {
fn __frnt__one() -> i32;
}
pub fn something_in_between() {}
extern "C" {
fn __frnt__two() -> i32;
}
"#;
assert_eq!(
parse_imports(src),
vec![
("freenet_first".to_string(), "__frnt__one".to_string()),
("env".to_string(), "__frnt__two".to_string()),
]
);
}
#[test]
fn every_extern_c_block_is_in_a_scanned_file() {
let root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
if !root.is_dir() {
return;
}
let known: Vec<String> = SCANNED
.iter()
.map(|(p, _)| p.replace('/', std::path::MAIN_SEPARATOR_STR))
.collect();
let mut unscanned = Vec::new();
let mut stack = vec![root.clone()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).expect("read src/") {
let path = entry.expect("dir entry").path();
if path.is_dir() {
stack.push(path);
continue;
}
if path.extension().and_then(|e| e.to_str()) != Some("rs") {
continue;
}
let rel = path
.strip_prefix(&root)
.expect("under src/")
.to_string_lossy()
.to_string();
if known.contains(&rel) {
continue;
}
let src = std::fs::read_to_string(&path).expect("read source");
if !parse_imports(strip_test_modules(&src)).is_empty() {
unscanned.push(rel);
}
}
}
assert!(
unscanned.is_empty(),
"these files declare host imports but are not in SCANNED, so the \
manifest guard cannot see them: {unscanned:?}"
);
}
#[test]
fn the_manifest_is_public_api() {
let one: HostImport = DECLARED_HOST_IMPORTS[0];
assert!(one.module.starts_with("freenet_"));
assert!(one.name.starts_with("__frnt__"));
}
}