Skip to content

Commit 3e72745

Browse files
authored
Feat: add tag-std and cargo-tag-std demo by driving rustc (#1)
* demo/rustc_driver: tag-std + tag_std=/home/zjp/rust/tag-std/demo/rustc_driver/target/debug/tag-std + cd ./tests/basic + /home/zjp/rust/tag-std/demo/rustc_driver/target/debug/tag-std src/lib.rs --crate-type=lib ["test"] "#[tag_std::unreachable]\n" ["MyStruct::get"] "#[tag_std::contract(!Null(self.ptr) && Align(self.ptr, u8) &&\nAllocated(self.ptr, u8, self.len, *) && Init(sel f.ptr, u8, self.len, *) &&\nValidInt(self.len*sizeof(u8), [0,isize::MAX]) && Alias(self.ptr, *))]\n" * demo/tag-std: Reachability analysis and cargo_tag_std wrapper rustc_driver/tests/basic $ tag-std src/main.rs --extern demo -L target/debug/ (body) "main" (body) "std::vec::Vec::<u8>::new" (body) "std::vec::Vec::<u8>::into_raw_parts" (body) "core::fmt::rt::Argument::<'_>::new_debug::<&mut [u8]>" (body) "<&mut [u8] as std::fmt::Debug>::fmt" (body) "<[u8] as std::fmt::Debug>::fmt" (body) "std::fmt::DebugList::<'_, '_>::entries::<&u8, std::slice::Iter<'_, u8>>" (body) "<std::slice::Iter<'_, u8> as std::iter::IntoIterator>::into_iter" (body) "<std::slice::Iter<'_, u8> as std::iter::Iterator>::next" (body) "core::num::<impl usize>::unchecked_sub::precondition_check" (body) "core::fmt::rt::<impl std::fmt::Arguments<'_>>::new_v1::<2, 1>" [src/main.rs:47:5] reachability.instances.len() = 18 [src/main.rs:47:5] reachability.cross_crates = false (error) demo::MyStruct::from not converted to be an item: Error("Item kind `Item` cannot be converted") (error) core::panicking::panic_nounwind not converted to be an item: Error("Item kind `Item` cannot be converted") (error) std::io::_print not converted to be an item: Error("Item kind `Item` cannot be converted") (error) std::fmt::DebugList::<'_, '_>::finish not converted to be an item: Error("Item kind `Item` cannot be converted") (error) demo::MyStruct::get not converted to be an item: Error("Item kind `Item` cannot be converted") (error) std::fmt::DebugList::<'_, '_>::entry not converted to be an item: Error("Item kind `Item` cannot be converted") (error) std::fmt::Formatter::<'_>::debug_list not converted to be an item: Error("Item kind `Item` cannot be converted") * fix: resort to internal APIs for instances that StableMir doesn't support + /home/zjp/rust/tag-std/demo/rustc_driver/target/debug/cargo_tag_std Compiling demo v0.1.0 (/home/zjp/rust/tag-std/demo/rustc_driver/tests/basic) [src/main.rs:53:5] reachability.instances.len() = 10 (stable mir) "test" ("src/lib.rs:8:1: 8:21") => "#[tag_std::unreachable]\n" (stable mir) "MyStruct::get" ("src/lib.rs:27:5: 27:42") => "#[tag_std::contract(!Null(self.ptr) && Align(self.ptr, u8) &&\nAllocated(self.ptr, u8, self.len, *) && Init(self.ptr, u8, self.len, *) &&\nValidInt(self.len*sizeof(u8), [0,isize::MAX]) && Alias(self.ptr, *))]\n" [src/main.rs:53:5] reachability.instances.len() = 18 (internal api) "demo::MyStruct::get" ("/home/zjp/rust/tag-std/demo/rustc_driver/tests/basic/src/lib.rs:27:5: 27:42") => "#[tag_std::contract(! Null(self.ptr) && Align(self.ptr, u8) &&\nAllocated(self.ptr, u8, self.len, *) && Init(self.ptr, u8, self.len, *) &&\nValidInt(self.len*sizeof (u8), [0,isize::MAX]) && Alias(self.ptr, *))]\n" Finished `dev` profile [unoptimized + debuginfo] target(s) in 1.75s * rename cargo_tag_std to cargo-tag-std; fix wrapper detection * print local crate name and type; and minor cleanups
1 parent 0b967ef commit 3e72745

File tree

9 files changed

+255
-0
lines changed

9 files changed

+255
-0
lines changed

demo/rustc_driver/Cargo.toml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
[package]
2+
name = "tag-std"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[[bin]]
7+
name = "cargo-tag-std"
8+
path = "src/bin/cargo_tag_std.rs"
9+
10+
[dependencies]
11+
12+
[package.metadata.rust-analyzer]
13+
rustc_private = true

demo/rustc_driver/run.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
set -ex
2+
3+
# Set up toolchain: works under current folder.
4+
export LD_LIBRARY_PATH=$(rustc --print sysroot)/lib
5+
6+
cargo build
7+
export TAG_STD=$PWD/target/debug/tag-std
8+
export CARGO_TAG_STD=$PWD/target/debug/cargo-tag-std
9+
10+
# Test basic demo
11+
cd ./tests/basic
12+
13+
cargo clean
14+
15+
# Analyze the lib and bin crates.
16+
# Same as `cargo tag-std` when tag-std and cargo-tag-std are installed.
17+
$CARGO_TAG_STD

demo/rustc_driver/rust-toolchain.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[toolchain]
2+
channel = "nightly-2025-05-08"
3+
components = ["llvm-tools", "rustc-dev", "rust-src", "rust-analyzer"]

demo/rustc_driver/rustfmt.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
edition = "2021"
2+
style_edition = "2024"
3+
use_small_heuristics = "Max"
4+
merge_derives = false
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
use std::{env::var, process::Command};
2+
3+
fn main() {
4+
// Search cargo-tag-std and tag-std CLI through environment variables,
5+
// or just use the name if absent.
6+
let cargo_tag_std = var("CARGO_TAG_STD").unwrap_or_else(|_| "cargo-tag-std".to_owned());
7+
let tag_std = var("TAG_STD").unwrap_or_else(|_| "tag-std".to_owned());
8+
9+
let mut args = std::env::args().collect::<Vec<_>>();
10+
11+
if args.len() == 2 && args[1].as_str() == "-vV" {
12+
// cargo invokes `rustc -vV` first
13+
run("rustc", &["-vV".to_owned()], &[]);
14+
} else if std::env::var("WRAPPER").as_deref() == Ok("1") {
15+
// then cargo invokes `rustc - --crate-name ___ --print=file-names`
16+
if args[1] == "-" {
17+
// `rustc -` is a substitute file name from stdin
18+
args[1] = "src/lib.rs".to_owned();
19+
}
20+
21+
run(&tag_std, &args[1..], &[]);
22+
} else {
23+
run("cargo", &["build"].map(String::from), &[("RUSTC", &cargo_tag_std), ("WRAPPER", "1")]);
24+
}
25+
}
26+
27+
fn run(cmd: &str, args: &[String], vars: &[(&str, &str)]) {
28+
let status = Command::new(cmd)
29+
.args(args)
30+
.envs(vars.iter().copied())
31+
.stdout(std::io::stdout())
32+
.stderr(std::io::stderr())
33+
.spawn()
34+
.unwrap()
35+
.wait()
36+
.unwrap();
37+
if !status.success() {
38+
eprintln!("[error] {cmd}: args={args:?} vars={vars:?}");
39+
}
40+
}

demo/rustc_driver/src/main.rs

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
#![feature(rustc_private)]
2+
3+
extern crate rustc_data_structures;
4+
extern crate rustc_driver;
5+
extern crate rustc_hir;
6+
extern crate rustc_hir_pretty;
7+
extern crate rustc_interface;
8+
extern crate rustc_middle;
9+
#[macro_use]
10+
extern crate rustc_smir;
11+
extern crate stable_mir;
12+
13+
use rustc_data_structures::fx::FxHashSet;
14+
use rustc_hir::Attribute;
15+
use rustc_middle::ty::TyCtxt;
16+
use stable_mir::{
17+
CrateDef, CrateItem, ItemKind,
18+
mir::{MirVisitor, mono::Instance, visit::Location},
19+
rustc_internal::internal,
20+
ty::Ty,
21+
};
22+
23+
fn main() {
24+
let rustc_args: Vec<_> = std::env::args().collect();
25+
_ = run_with_tcx!(&rustc_args, |tcx| {
26+
analyze(tcx);
27+
ControlFlow::<(), ()>::Continue(())
28+
});
29+
}
30+
31+
const REGISTER_TOOL_ATTR: &str = "#[tag_std";
32+
const TAG_STD: &str = "tag_std";
33+
34+
fn analyze(tcx: TyCtxt) {
35+
let mut reachability = Reachability::default();
36+
let local_items = stable_mir::all_local_items();
37+
let functions = local_items.iter().filter(|item| matches!(item.kind(), ItemKind::Fn));
38+
39+
for fun in functions {
40+
let instance = match Instance::try_from(*fun) {
41+
Ok(instance) => instance,
42+
Err(err) => {
43+
eprintln!("Failed to get the instance for {fun:?}:\n{err:?}");
44+
continue;
45+
}
46+
};
47+
reachability.add_instance(instance);
48+
}
49+
50+
let local_crate = stable_mir::local_crate();
51+
println!(
52+
"********* {name:?} {typ:?} has reached {len} instances *********",
53+
name = local_crate.name,
54+
typ = tcx.crate_types(),
55+
len = reachability.instances.len()
56+
);
57+
reachability.print_tag_std_attrs(tcx);
58+
}
59+
60+
#[derive(Debug, Default)]
61+
struct Reachability {
62+
/// Collect monomorphized instances.
63+
instances: FxHashSet<Instance>,
64+
}
65+
66+
impl Reachability {
67+
fn add_instance(&mut self, instance: Instance) {
68+
if self.instances.insert(instance) {
69+
// recurse if this is the first time of insertion
70+
if let Some(body) = instance.body() {
71+
self.visit_body(&body);
72+
}
73+
}
74+
}
75+
76+
fn print_tag_std_attrs(&self, tcx: TyCtxt) {
77+
for instance in &self.instances {
78+
// Only user defined instances can be converted.
79+
match CrateItem::try_from(*instance) {
80+
Ok(item) => print_tag_std_attrs(instance, item),
81+
Err(_) => {
82+
// Resort to internal API for unsupported StableMir conversions.
83+
print_tag_std_attrs_through_internal_apis(tcx, instance);
84+
}
85+
}
86+
}
87+
}
88+
}
89+
90+
fn print_tag_std_attrs_through_internal_apis(tcx: TyCtxt<'_>, instance: &Instance) {
91+
let def_id = internal(tcx, instance.def.def_id());
92+
let tool_attrs = tcx.get_all_attrs(def_id).filter(|attr| {
93+
if let Attribute::Unparsed(tool_attr) = attr {
94+
if tool_attr.path.segments[0].as_str() == TAG_STD {
95+
return true;
96+
}
97+
}
98+
false
99+
});
100+
for attr in tool_attrs {
101+
println!(
102+
"(internal api) {fn_name:?} ({span:?}) => {attr:?}\n",
103+
fn_name = instance.name(),
104+
span = instance.def.span().diagnostic(),
105+
attr = rustc_hir_pretty::attribute_to_string(&tcx, attr)
106+
);
107+
}
108+
}
109+
110+
fn print_tag_std_attrs(instance: &Instance, item: CrateItem) {
111+
let tool_attrs = item
112+
.all_tool_attrs()
113+
.into_iter()
114+
.filter(|attr| attr.as_str().starts_with(REGISTER_TOOL_ATTR));
115+
for tag_attr in tool_attrs {
116+
println!(
117+
"(stable mir) {fn_name:?} ({span:?}) => {attr:?}\n",
118+
fn_name = instance.name(),
119+
span = instance.def.span().diagnostic(),
120+
attr = tag_attr.as_str()
121+
);
122+
}
123+
}
124+
125+
impl MirVisitor for Reachability {
126+
fn visit_ty(&mut self, ty: &Ty, _: Location) {
127+
if let Some((fn_def, args)) = ty.kind().fn_def() {
128+
// Add an instance.
129+
let instance = Instance::resolve(fn_def, args).unwrap();
130+
self.add_instance(instance);
131+
}
132+
}
133+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[package]
2+
name = "demo"
3+
version = "0.1.0"
4+
edition = "2024"
5+
6+
[dependencies]
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#![feature(register_tool)]
2+
#![register_tool(tag_std)]
3+
4+
use std::slice;
5+
6+
#[tag_std::unreachable]
7+
pub unsafe fn test() {
8+
println!("unreachable!");
9+
}
10+
11+
pub struct MyStruct {
12+
ptr: *mut u8,
13+
len: usize,
14+
}
15+
impl MyStruct {
16+
pub fn from(p: *mut u8, l: usize) -> MyStruct {
17+
MyStruct { ptr: p, len: l }
18+
}
19+
///contract(!Null(self.ptr); Align(self.ptr, u8); Allocated(self.ptr, u8, self.len, *); Init(self.ptr, u8, self.len, *); ValidInt(self.len*sizeof(u8), [0,isize::MAX]); Alias(self.ptr, *);
20+
#[tag_std::contract(
21+
!Null(self.ptr) && Align(self.ptr, u8) &&
22+
Allocated(self.ptr, u8, self.len, *) && Init(self.ptr, u8, self.len, *) &&
23+
ValidInt(self.len*sizeof(u8), [0,isize::MAX]) && Alias(self.ptr, *)
24+
)]
25+
#[allow(clippy::mut_from_ref)]
26+
pub unsafe fn get(&self) -> &mut [u8] {
27+
unsafe { slice::from_raw_parts_mut(self.ptr, self.len) }
28+
}
29+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#![feature(vec_into_raw_parts)]
2+
#![allow(unused_variables)]
3+
4+
use demo::MyStruct;
5+
6+
fn main() {
7+
let (p, l, _c) = Vec::new().into_raw_parts();
8+
let a = MyStruct::from(p, l);
9+
println!("{:?}", unsafe { a.get() });
10+
}

0 commit comments

Comments
 (0)