Skip to content

Commit 7662d19

Browse files
authored
Merge pull request #33 from Ekleog/no-linker-shenanigans
do not rely on linker shenanigans to work with a single binary
2 parents f0b8743 + be12086 commit 7662d19

File tree

7 files changed

+135
-40
lines changed

7 files changed

+135
-40
lines changed

Cargo.lock

Lines changed: 11 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
[workspace]
2-
members = ["cli", "core", "machine", "nat", "router", "."]
2+
members = ["cli", "core", "machine", "macros", "nat", "router", "."]
33

44
[package]
55
name = "netsim-embed"
@@ -11,7 +11,7 @@ license = "MIT"
1111
repository = "https://github.com/ipfs-rust/netsim-embed"
1212

1313
[features]
14-
ipc = ["dep:libtest-mimic", "dep:anyhow", "dep:ipc-channel", "dep:dlopen"]
14+
ipc = ["dep:libtest-mimic", "dep:anyhow", "dep:ipc-channel", "dep:netsim-embed-macros", "dep:serde"]
1515

1616
[dependencies]
1717
anyhow = { version = "1.0.40", optional = true }
@@ -25,8 +25,10 @@ libtest-mimic = { version = "0.6.0", optional = true }
2525
log = "0.4.14"
2626
netsim-embed-core = { version = "0.4.2", path = "core" }
2727
netsim-embed-machine = { version = "0.6.3", path = "machine" }
28+
netsim-embed-macros = { version = "0.1.0", path = "macros", optional = true }
2829
netsim-embed-nat = { version = "0.4.1", path = "nat" }
2930
netsim-embed-router = { version = "0.4.5", path = "router" }
31+
serde = { version = "1.0", optional = true }
3032

3133
[dev-dependencies]
3234
anyhow = "1.0.40"

build.rs

Lines changed: 0 additions & 3 deletions
This file was deleted.

macros/Cargo.toml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
[package]
2+
name = "netsim-embed-macros"
3+
version = "0.1.0"
4+
edition = "2021"
5+
6+
[lib]
7+
proc-macro = true
8+
9+
[dependencies]
10+
quote = "1.0"
11+
rand = "0.8.5"
12+
syn = "1.0"

macros/src/lib.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use proc_macro::TokenStream;
2+
3+
#[proc_macro_attribute]
4+
pub fn machine(_attrs: TokenStream, fun: TokenStream) -> TokenStream {
5+
let f = syn::parse_macro_input!(fun as syn::ItemFn);
6+
7+
assert!(
8+
f.attrs.is_empty(),
9+
"netsim_embed::machine cannot have any attribute"
10+
);
11+
assert!(
12+
f.sig.constness.is_none(),
13+
"netsim_embed::machine cannot be const"
14+
);
15+
assert!(
16+
f.sig.asyncness.is_none(),
17+
"netsim_embed::machine cannot be async"
18+
);
19+
assert!(
20+
f.sig.unsafety.is_none(),
21+
"netsim_embed::machine cannot be unsafe"
22+
);
23+
assert!(
24+
f.sig.abi.is_none(),
25+
"netsim_embed::machine cannot have an abi defined"
26+
);
27+
assert!(
28+
f.sig.generics.params.is_empty(),
29+
"netsim_embed::machine cannot be generic"
30+
);
31+
assert!(
32+
f.sig.inputs.len() == 1,
33+
"netsim_embed::machine must take exactly one argument"
34+
);
35+
assert!(
36+
f.sig.variadic.is_none(),
37+
"netsim_embed::machine cannot be variadic"
38+
);
39+
assert!(
40+
matches!(f.sig.output, syn::ReturnType::Default),
41+
"netsim_embed::machine must not declare a return type"
42+
);
43+
44+
let input = match &f.sig.inputs.first().unwrap() {
45+
syn::FnArg::Typed(input) => input,
46+
_ => panic!("netsim_embed::machine must be a freestanding function"),
47+
};
48+
assert!(
49+
input.attrs.is_empty(),
50+
"netsim_embed::machine's only argument must not have any attributes attached"
51+
);
52+
53+
let f_vis = f.vis;
54+
let f_ident = f.sig.ident;
55+
let input_ty = &input.ty;
56+
let id: u128 = rand::random();
57+
let input_pat = &input.pat;
58+
let f_block = f.block;
59+
60+
TokenStream::from(quote::quote! {
61+
#[allow(non_camel_case_types)]
62+
#f_vis struct #f_ident ;
63+
64+
impl netsim_embed::MachineFn for #f_ident {
65+
type Arg = #input_ty ;
66+
67+
fn id() -> u128 {
68+
#id
69+
}
70+
71+
fn call(#input_pat: #input_ty) #f_block
72+
}
73+
})
74+
}

src/lib.rs

Lines changed: 26 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -72,19 +72,23 @@ where
7272
}
7373

7474
#[cfg(feature = "ipc")]
75-
pub async fn spawn<T: 'static + Send>(
76-
&mut self,
77-
function: fn(ipc_channel::ipc::IpcReceiver<T>),
78-
) -> (MachineId, ipc_channel::ipc::IpcSender<T>) {
79-
let name = get_fn_name(function);
80-
let (server, server_name) = ipc_channel::ipc::IpcOneShotServer::new().unwrap();
75+
pub async fn spawn<M: MachineFn>(&mut self, _machine: M, arg: M::Arg) -> MachineId {
76+
use ipc_channel::ipc;
77+
let id = M::id();
78+
let (server, server_name) = ipc::IpcOneShotServer::<ipc::IpcSender<M::Arg>>::new().unwrap();
8179
let mut command = Command::new(std::env::current_exe().unwrap());
82-
command.args(["--netsim-embed-internal-call", &name, &server_name]);
80+
command.args([
81+
"--netsim-embed-internal-call",
82+
&format!("{}", id),
83+
&server_name,
84+
]);
8385
let machine = self.spawn_machine(command, None).await;
8486
let (_, ipc) = async_global_executor::spawn_blocking(|| server.accept())
8587
.await
8688
.unwrap();
87-
(machine, ipc)
89+
ipc.send(arg)
90+
.expect("Failed sending argument to child process");
91+
machine
8892
}
8993

9094
pub async fn spawn_machine(
@@ -204,16 +208,6 @@ where
204208
}
205209
}
206210

207-
#[cfg(feature = "ipc")]
208-
pub fn get_fn_name<T>(function: fn(ipc_channel::ipc::IpcReceiver<T>)) -> String {
209-
let info = dlopen::raw::AddressInfoObtainer::new()
210-
.obtain(function as *const ())
211-
.expect("look up existing function pointer");
212-
info.overlapping_symbol
213-
.expect("cannot find function symbol: have you used #[no_mangle] for machine definitions and emitted `cargo:rustc-link-arg-tests=-rdynamic` from build.rs?")
214-
.name
215-
}
216-
217211
#[derive(Debug)]
218212
pub struct Network {
219213
id: NetworkId,
@@ -257,6 +251,16 @@ pub struct NatConfig {
257251
pub forward_ports: Vec<(Protocol, u16, SocketAddrV4)>,
258252
}
259253

254+
#[cfg(feature = "ipc")]
255+
pub trait MachineFn {
256+
type Arg: 'static + Send + serde::Serialize;
257+
fn id() -> u128;
258+
fn call(arg: Self::Arg);
259+
}
260+
261+
#[cfg(feature = "ipc")]
262+
pub use netsim_embed_macros::machine;
263+
260264
#[allow(clippy::needless_doctest_main)]
261265
/// Dispatch spawned machine invocations to their declared functions.
262266
///
@@ -272,18 +276,19 @@ pub struct NatConfig {
272276
#[cfg(feature = "ipc")]
273277
#[macro_export]
274278
macro_rules! declare_machines {
275-
( $($fn:path),* ) => {{
279+
( $($machine:path),* ) => {{
276280
let mut args = std::env::args();
277281
args.next();
278282
if args.next().map(|v| v == "--netsim-embed-internal-call").unwrap_or(false) {
279283
let function = args.next().unwrap();
280284
let server_name = args.next().unwrap();
285+
let function: u128 = function.parse().expect("Got a non-integer function to call");
281286
$(
282-
if function == $crate::get_fn_name($fn) {
287+
if function == <$machine as $crate::MachineFn>::id() {
283288
let (sender, receiver) = $crate::test_util::ipc::channel().unwrap();
284289
let server_sender = $crate::test_util::ipc::IpcSender::connect(server_name).unwrap();
285290
server_sender.send(sender).unwrap();
286-
$fn(receiver);
291+
<$machine as $crate::MachineFn>::call(receiver.recv().expect("Failed receiving argument from main process"));
287292
std::process::exit(0);
288293
}
289294
)*

tests/smoke_test.rs

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,12 @@
11
use ipc_channel::ipc::{IpcReceiver, IpcSender};
22

3-
#[no_mangle]
4-
pub fn send_one(r: IpcReceiver<IpcSender<usize>>) {
5-
let sender = r.recv().unwrap();
3+
#[netsim_embed::machine]
4+
fn send_one(sender: IpcSender<usize>) {
65
sender.send(1).unwrap();
76
}
87

9-
#[no_mangle]
10-
pub fn add(r: IpcReceiver<(IpcReceiver<usize>, IpcReceiver<usize>, IpcSender<usize>)>) {
11-
let (left, right, sender) = r.recv().unwrap();
8+
#[netsim_embed::machine]
9+
fn add((left, right, sender): (IpcReceiver<usize>, IpcReceiver<usize>, IpcSender<usize>)) {
1210
sender
1311
.send(left.recv().unwrap() + right.recv().unwrap())
1412
.unwrap();
@@ -17,8 +15,7 @@ pub fn add(r: IpcReceiver<(IpcReceiver<usize>, IpcReceiver<usize>, IpcSender<usi
1715
fn can_send_one() {
1816
let mut s = netsim_embed::Netsim::<String, String>::new();
1917
let (sender, receiver) = ipc_channel::ipc::channel().unwrap();
20-
let (_, mach_sender) = async_std::task::block_on(s.spawn(send_one));
21-
mach_sender.send(sender).unwrap();
18+
let _ = async_std::task::block_on(s.spawn(send_one, sender));
2219
assert_eq!(1, receiver.recv().unwrap());
2320
}
2421

@@ -27,16 +24,13 @@ fn one_plus_one_makes_two() {
2724
let mut s = netsim_embed::Netsim::<String, String>::new();
2825

2926
let (sender1, receiver1) = ipc_channel::ipc::channel::<usize>().unwrap();
30-
let (_, mach_sender1) = s.spawn(send_one).await;
31-
mach_sender1.send(sender1).unwrap();
27+
let _ = s.spawn(send_one, sender1).await;
3228

3329
let (sender2, receiver2) = ipc_channel::ipc::channel::<usize>().unwrap();
34-
let (_, mach_sender2) = s.spawn(send_one).await;
35-
mach_sender2.send(sender2).unwrap();
30+
let _ = s.spawn(send_one, sender2).await;
3631

3732
let (sender3, receiver3) = ipc_channel::ipc::channel::<usize>().unwrap();
38-
let (_, mach_sender3) = s.spawn(add).await;
39-
mach_sender3.send((receiver1, receiver2, sender3)).unwrap();
33+
let _ = s.spawn(add, (receiver1, receiver2, sender3)).await;
4034

4135
assert_eq!(2, receiver3.recv().unwrap());
4236
})

0 commit comments

Comments
 (0)