diff --git a/crates/paralegal-flow/tests/cross-crate.rs b/crates/paralegal-flow/tests/cross-crate.rs
new file mode 100644
index 0000000000..bea931513f
--- /dev/null
+++ b/crates/paralegal-flow/tests/cross-crate.rs
@@ -0,0 +1,76 @@
+#![feature(rustc_private)]
+#[macro_use]
+extern crate lazy_static;
+
+use paralegal_flow::test_utils::*;
+use paralegal_spdg::Identifier;
+
+const CRATE_DIR: &str = "tests/cross-crate";
+
+lazy_static! {
+ static ref TEST_CRATE_ANALYZED: bool = {
+ paralegal_flow_command(CRATE_DIR)
+ .status()
+ .unwrap()
+ .success()
+ };
+}
+
+macro_rules! define_test {
+ ($name:ident: $ctrl:ident -> $block:block) => {
+ define_test!($name: $ctrl, $name -> $block);
+ };
+ ($name:ident: $ctrl:ident, $ctrl_name:ident -> $block:block) => {
+ paralegal_flow::define_flow_test_template!(TEST_CRATE_ANALYZED, CRATE_DIR, $name: $ctrl, $ctrl_name -> $block);
+ };
+}
+
+define_test!(basic : graph -> {
+ let src_fn = graph.function("src");
+ let src = graph.call_site(&src_fn);
+ let not_src_fn = graph.function("not_src");
+ let not_src = graph.call_site(¬_src_fn);
+ let target_fn = graph.function("target");
+ let target = graph.call_site(&target_fn);
+ assert!(src.output().flows_to_data(&target.input()));
+ assert!(!not_src.output().flows_to_data(&target.input()));
+});
+
+define_test!(basic_marker: graph -> {
+
+ let marker = Identifier::new_intern("mark");
+ assert!(dbg!(&graph.spdg().markers).iter().any(|(_, markers)| markers.contains(&marker)))
+});
+
+define_test!(assigns_marker: graph -> {
+ let sources = graph.marked(Identifier::new_intern("source"));
+ let mark = graph.marked(Identifier::new_intern("mark"));
+ let target = graph.marked(Identifier::new_intern("target"));
+ assert!(!sources.is_empty());
+ assert!(!mark.is_empty());
+ assert!(!target.is_empty());
+ assert!(sources.flows_to_data(&mark));
+ assert!(mark.flows_to_data(&target));
+});
+
+define_test!(basic_generic : graph -> {
+ let src_fn = graph.function("src");
+ let src = graph.call_site(&src_fn);
+ let not_src_fn = graph.function("not_src");
+ let not_src = graph.call_site(¬_src_fn);
+ let target_fn = graph.function("target");
+ let target = graph.call_site(&target_fn);
+ assert!(src.output().flows_to_data(&target.input()));
+ assert!(!not_src.output().flows_to_data(&target.input()));
+});
+
+define_test!(assigns_marker_generic: graph -> {
+ let sources = graph.marked(Identifier::new_intern("source"));
+ let mark = graph.marked(Identifier::new_intern("mark"));
+ let target = graph.marked(Identifier::new_intern("target"));
+ assert!(!sources.is_empty());
+ assert!(!mark.is_empty());
+ assert!(!target.is_empty());
+ assert!(sources.flows_to_data(&mark));
+ assert!(mark.flows_to_data(&target));
+});
diff --git a/crates/paralegal-flow/tests/cross-crate/Cargo.lock b/crates/paralegal-flow/tests/cross-crate/Cargo.lock
new file mode 100644
index 0000000000..6843ae0e3b
--- /dev/null
+++ b/crates/paralegal-flow/tests/cross-crate/Cargo.lock
@@ -0,0 +1,57 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "dependency"
+version = "0.0.1"
+dependencies = [
+ "paralegal",
+]
+
+[[package]]
+name = "entry"
+version = "0.0.1"
+dependencies = [
+ "dependency",
+ "paralegal",
+]
+
+[[package]]
+name = "paralegal"
+version = "0.1.0"
+dependencies = [
+ "cfg-if",
+ "proc-macro2",
+ "quote",
+]
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.36"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.12"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b"
diff --git a/crates/paralegal-flow/tests/cross-crate/Cargo.toml b/crates/paralegal-flow/tests/cross-crate/Cargo.toml
new file mode 100644
index 0000000000..6e5676e925
--- /dev/null
+++ b/crates/paralegal-flow/tests/cross-crate/Cargo.toml
@@ -0,0 +1,2 @@
+[workspace]
+members = ["dependency", "entry"]
diff --git a/crates/paralegal-flow/tests/cross-crate/dependency/Cargo.toml b/crates/paralegal-flow/tests/cross-crate/dependency/Cargo.toml
new file mode 100644
index 0000000000..9cd274b7d0
--- /dev/null
+++ b/crates/paralegal-flow/tests/cross-crate/dependency/Cargo.toml
@@ -0,0 +1,6 @@
+[package]
+name = "dependency"
+version = "0.0.1"
+
+[dependencies]
+paralegal = { path = "../../../../paralegal" }
diff --git a/crates/paralegal-flow/tests/cross-crate/dependency/src/lib.rs b/crates/paralegal-flow/tests/cross-crate/dependency/src/lib.rs
new file mode 100644
index 0000000000..194dd768be
--- /dev/null
+++ b/crates/paralegal-flow/tests/cross-crate/dependency/src/lib.rs
@@ -0,0 +1,30 @@
+pub fn find_me(a: usize, _b: usize) -> usize {
+ a
+}
+
+#[paralegal::marker(mark, return)]
+pub fn source() -> usize {
+ 0
+}
+
+#[paralegal::marker(mark, return)]
+fn taint_it(_: A) -> A {
+ unimplemented!()
+}
+
+pub fn assign_marker(a: usize) -> usize {
+ taint_it(a)
+}
+
+pub fn find_me_generic(a: A, _b: A) -> A {
+ a
+}
+
+#[paralegal::marker(mark, return)]
+pub fn generic_source() -> A {
+ unimplemented!()
+}
+
+pub fn assign_marker_generic(a: A) -> A {
+ taint_it(a)
+}
diff --git a/crates/paralegal-flow/tests/cross-crate/entry/Cargo.toml b/crates/paralegal-flow/tests/cross-crate/entry/Cargo.toml
new file mode 100644
index 0000000000..52bdc7a83f
--- /dev/null
+++ b/crates/paralegal-flow/tests/cross-crate/entry/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "entry"
+version = "0.0.1"
+
+[dependencies]
+dependency = { path = "../dependency" }
+paralegal = { path = "../../../../paralegal" }
diff --git a/crates/paralegal-flow/tests/cross-crate/entry/src/main.rs b/crates/paralegal-flow/tests/cross-crate/entry/src/main.rs
new file mode 100644
index 0000000000..66f47a24c4
--- /dev/null
+++ b/crates/paralegal-flow/tests/cross-crate/entry/src/main.rs
@@ -0,0 +1,43 @@
+extern crate dependency;
+
+use dependency::{assign_marker, assign_marker_generic, find_me, find_me_generic, source};
+
+#[paralegal::marker(source, return)]
+fn src() -> usize {
+ 0
+}
+
+#[paralegal::marker(not_source)]
+fn not_src() -> usize {
+ 1
+}
+
+#[paralegal::marker(target, arguments = [0])]
+fn target(u: usize) {}
+
+#[paralegal::analyze]
+fn basic() {
+ target(find_me(src(), not_src()))
+}
+
+#[paralegal::analyze]
+fn basic_marker() {
+ target(source());
+}
+
+#[paralegal::analyze]
+fn assigns_marker() {
+ target(assign_marker(src()));
+}
+
+#[paralegal::analyze]
+fn basic_generic() {
+ target(find_me_generic(src(), not_src()))
+}
+
+#[paralegal::analyze]
+fn assigns_marker_generic() {
+ target(assign_marker_generic(src()));
+}
+
+fn main() {}