Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 168 additions & 13 deletions crates/ruff_charon/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,8 @@ pub enum Callee {
Trait {
kind: String,
trait_id: Option<TraitDeclId>,
/// The trait method the call names.
method: TraitMethodId,
impl_fun: Option<FunDeclId>,
},
/// `FnOperand::Dynamic`.
Expand All @@ -123,6 +125,14 @@ pub struct CallSite {
pub site: Option<Site>,
}

/// A function item or pointer used as a value (not called), in a structured body.
#[derive(Debug, Clone)]
pub struct FnRef {
/// `None` when the reference is not a plain function id (a trait method).
pub target: Option<FunDeclId>,
pub site: Option<Site>,
}

/// What one function's body witnesses.
#[derive(Debug, Clone)]
pub struct FunFacts {
Expand All @@ -132,6 +142,8 @@ pub struct FunFacts {
pub site: Option<Site>,
/// `None`: the body is not structured LLBC (opaque, extern, unstructured).
pub calls: Option<Vec<CallSite>>,
/// Function items and pointers named as values; empty without a structured body.
pub refs: Vec<FnRef>,
/// Writes through a dereference rooted in an argument (caller memory; for
/// a closure, its captured state is argument 1).
pub deref_write_arg: usize,
Expand Down Expand Up @@ -178,6 +190,24 @@ fn has_deref(place: &Place) -> bool {
}
}

fn operand_fn_ptr(op: &Operand) -> Option<&FnPtr> {
let Operand::Const(c) = op else { return None };
match c.kind() {
ConstantExprKind::FnDef(p) | ConstantExprKind::FnPtr(p) => Some(p),
_ => None,
}
}

/// The operand positions of an rvalue that can hold a function value.
fn rvalue_operands(rv: &Rvalue) -> Vec<&Operand> {
match rv {
Rvalue::Use(op, _) | Rvalue::UnaryOp(_, op) => vec![op],
Rvalue::BinaryOp(_, a, b) => vec![a, b],
Rvalue::Aggregate(_, ops) => ops.iter().collect(),
_ => Vec::new(),
}
}

fn callee(krate: &TranslatedCrate, call: &Call) -> Callee {
match &call.func {
FnOperand::Regular(ptr) => match ptr.kind.as_ref() {
Expand All @@ -202,6 +232,7 @@ fn callee(krate: &TranslatedCrate, call: &Call) -> Callee {
Callee::Trait {
kind: variant(format!("{:?}", tr.kind)),
trait_id: Some(tr.trait_decl_ref.id),
method: *method,
impl_fun,
}
}
Expand All @@ -226,24 +257,47 @@ pub fn fun_facts(krate: &TranslatedCrate, crate_name: &str) -> Vec<FunFacts> {
name,
site: site(krate, f.item_meta.span),
calls: None,
refs: Vec::new(),
deref_write_arg: 0,
deref_write_local: 0,
asserts: 0,
};
if let Body::Structured(body) = &f.body {
let args = body.locals.arg_count;
let mut calls = Vec::new();
body.body.dyn_visit(|st: &Statement| match &st.kind {
StatementKind::Call { call, .. } => calls.push(CallSite {
callee: callee(krate, call),
site: site(krate, st.span),
}),
StatementKind::Assert { .. } => facts.asserts += 1,
StatementKind::Assign(place, _) if has_deref(place) => match root_local(place) {
Some(l) if l.index() <= args => facts.deref_write_arg += 1,
_ => facts.deref_write_local += 1,
},
_ => {}
body.body.dyn_visit(|st: &Statement| {
// Operands that are function values, never the callee itself
// (`FnOperand` is not an `Operand`).
let operands: Vec<&Operand> = match &st.kind {
StatementKind::Assign(_, rv) => rvalue_operands(rv),
StatementKind::Call { call, .. } => call.args.iter().collect(),
_ => Vec::new(),
};
for op in operands {
if let Some(ptr) = operand_fn_ptr(op) {
facts.refs.push(FnRef {
target: match ptr.kind.as_ref() {
FnPtrKind::Fun(id) => Some(*id),
FnPtrKind::Trait(..) => None,
},
site: site(krate, st.span),
});
}
}
match &st.kind {
StatementKind::Call { call, .. } => calls.push(CallSite {
callee: callee(krate, call),
site: site(krate, st.span),
}),
StatementKind::Assert { .. } => facts.asserts += 1,
StatementKind::Assign(place, _) if has_deref(place) => {
match root_local(place) {
Some(l) if l.index() <= args => facts.deref_write_arg += 1,
_ => facts.deref_write_local += 1,
}
}
_ => {}
}
});
facts.calls = Some(calls);
}
Expand Down Expand Up @@ -302,6 +356,9 @@ pub struct Relation {
pub subject: String,
pub object: String,
pub site: Option<Site>,
/// What a Kythe `ref/call` points at when it is not `object`: the plain
/// trait name for `MAY_DISPATCH`, which Kythe reads as the declaration.
pub kythe_target: Option<String>,
}

/// Something Charon gave that no relation represents, with the reason.
Expand Down Expand Up @@ -342,7 +399,63 @@ pub fn relations(
subject: f.qualified.clone(),
object: f.qualified.clone(),
site: f.site.clone(),
kythe_target: None,
});
let decl = krate.fun_decls.get(f.id);
let mut marker = |predicate: &'static str, object: String| {
receipt.harvested += 1;
rows.push(Relation {
predicate,
subject: f.qualified.clone(),
object,
site: f.site.clone(),
kythe_target: None,
});
};
if let Some(d) = decl {
if d.item_meta.attr_info.public {
marker("PUBLIC", f.qualified.clone());
}
if d.item_meta
.name
.name
.iter()
.any(|e| !matches!(e, PathElem::Ident(..) | PathElem::Impl(_)))
{
marker("SYNTHETIC", f.qualified.clone());
}
}
if let Some(d) = decl
&& let FunSource::TraitImpl {
trait_ref, item_id, ..
}
| FunSource::TraitDefault {
trait_ref, item_id, ..
} = &d.src
{
let named = krate.trait_decls.get(trait_ref.id).and_then(|t| {
let m = t.methods.get(*item_id)?;
Some(format!(
"{}::{}",
qualified_name(krate, &t.item_meta.name),
m.skip_binder.name
))
});
receipt.harvested += 1;
match named {
Some(object) => rows.push(Relation {
predicate: "IMPLEMENTS",
subject: f.qualified.clone(),
object,
site: f.site.clone(),
kythe_target: None,
}),
None => residual.push(Residual {
reason: "TRAIT_METHOD_NOT_FOUND",
subject: f.qualified.clone(),
}),
}
}
let Some(calls) = f.calls else {
// The body is an item of its own: harvested here, represented by
// nothing, so it is the residual. The definition above stays
Expand All @@ -356,6 +469,7 @@ pub fn relations(
};
for c in calls {
receipt.harvested += 1;
let mut kythe_target = None;
let (predicate, object) = match &c.callee {
Callee::Fun(id) => match krate.fun_decls.get(*id) {
Some(t) => ("CALLS_RESOLVED", qualified_name(krate, &t.item_meta.name)),
Expand All @@ -373,6 +487,7 @@ pub fn relations(
Callee::Trait {
kind,
trait_id,
method,
impl_fun,
} => {
if kind == "TraitImpl" {
Expand All @@ -398,7 +513,12 @@ pub fn relations(
.and_then(|t| krate.item_names.get(&ItemId::TraitDecl(t)))
.map(|n| qualified_name(krate, n))
.unwrap_or_else(|| "?".to_owned());
("MAY_DISPATCH", format!("{tr} via {kind}"))
let name = trait_id
.and_then(|t| krate.trait_decls.get(t))
.and_then(|t| t.methods.get(*method))
.map_or_else(|| "?".to_owned(), |m| m.skip_binder.name.to_string());
kythe_target = Some(tr.clone());
("MAY_DISPATCH", format!("{tr}::{name} via {kind}"))
}
}
Callee::Dynamic => ("UNRESOLVED_CALL", "dynamic".to_owned()),
Expand All @@ -408,8 +528,32 @@ pub fn relations(
subject: f.qualified.clone(),
object,
site: c.site,
kythe_target,
});
}
for r in f.refs {
receipt.harvested += 1;
let named = r.target.and_then(|id| match krate.fun_decls.get(id) {
Some(t) => Some(qualified_name(krate, &t.item_meta.name)),
None => krate
.item_names
.get(&ItemId::Fun(id))
.map(|n| qualified_name(krate, n)),
});
match named {
Some(object) => rows.push(Relation {
predicate: "REFERENCES",
subject: f.qualified.clone(),
object,
site: r.site,
kythe_target: None,
}),
None => residual.push(Residual {
reason: "REFERENCE_NOT_NAMED",
subject: f.qualified.clone(),
}),
}
}
}
receipt.represented = rows.len();
receipt.residual = residual.len();
Expand Down Expand Up @@ -475,6 +619,14 @@ pub fn kythe_entries(
let mut files = std::collections::HashSet::new();
let (rows, _, _) = relations(krate, crate_name);
for r in rows {
// The v2 predicates are facts about items, not Kythe edges; emitting
// their anchors would change the entry stream.
if matches!(
r.predicate,
"PUBLIC" | "IMPLEMENTS" | "SYNTHETIC" | "REFERENCES"
) {
continue;
}
let Some(s) = &r.site else { continue };
if files.insert(s.file.clone())
&& let Some(f) = krate.files.iter().find(|f| match &f.name {
Expand Down Expand Up @@ -508,7 +660,10 @@ pub fn kythe_entries(
);
}
"CALLS_RESOLVED" | "MAY_DISPATCH" => {
let target = r.object.split(" via ").next().unwrap_or(&r.object);
let target = r
.kythe_target
.as_deref()
.unwrap_or_else(|| r.object.split(" via ").next().unwrap_or(&r.object));
let callee = vname(corpus, target, "", "rust");
emit(
serde_json::json!({"source": a.clone(), "edge_kind": "/kythe/edge/ref/call", "target": callee, "fact_name": "/"}),
Expand Down
10 changes: 9 additions & 1 deletion crates/ruff_charon/src/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ use std::sync::atomic::{AtomicU64, Ordering};
use crate::{GRADE, Llbc, relations};

/// The version of the rows artifact this crate writes and reads.
pub const ROWS_VERSION: u32 = 1;
pub const ROWS_VERSION: u32 = 2;
const ROWS_SCHEMA: &[&str] = &[
"predicate",
"subject",
Expand Down Expand Up @@ -238,6 +238,14 @@ impl Store {
r.predicate, r.subject, r.object
)?;
}
// Whether Charon translated the whole crate: a PARTIAL crate's absent
// rows are not evidence of absence.
let completeness = if llbc.has_errors {
"PARTIAL"
} else {
"COMPLETE"
};
writeln!(out, "CRATE\t{crate_name}\t{completeness}\t{GRADE}\t-\t-\t-")?;
let mut res = Vec::new();
header(&mut res, RESIDUALS_VERSION, RESIDUALS_SCHEMA)?;
for r in &residual {
Expand Down
4 changes: 4 additions & 0 deletions crates/ruff_charon/tests/data/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,7 @@ The LLBC was produced with the AdaWorldAPI Charon fork (`c69e231`, toolchain `ni
cd cg_lib && charon cargo --no-dedup-serialized-ast --dest-file cg_lib.llbc
cd cg_app && charon cargo --no-dedup-serialized-ast --dest-file cg_app.llbc
```

- `fnref.llbc`: Charon LLBC of `fnref.rs` (a function used only as a function pointer, an unused private function, and a call through a pointer argument), produced with `charon rustc --no-dedup-serialized-ast --dest-file fnref.llbc -- --crate-type=lib fnref.rs`. rustc reports `dead_code` for `unused_private` only.
- `cg_lib.kythe.golden`, `cg_app.kythe.golden`: `ruff_charon kythe <file> <crate>` on rows v1 (before the v2 predicates); the v2 Kythe output must equal them.
- `cg_broken` (rev1) produces no LLBC: rustc stops on E0425 and E0432 before Charon translates, so a `CRATE … PARTIAL` row cannot come from a crate that does not compile.
27 changes: 27 additions & 0 deletions crates/ruff_charon/tests/data/cg_app.kythe.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{"fact_name":"/kythe/node/kind","fact_value":"ZmlsZQ==","source":{"corpus":"lance-graph","language":"","path":"cg_app/src/lib.rs","root":"","signature":""}}
{"fact_name":"/kythe/text","fact_value":"dXNlIGNnX2xpYjo6c2hhcGVzOjp7UmVjdCwgU2hhcGV9Owp1c2UgY2dfbGliOjpTcTsKCnB1YiBmbiBydW4oKSAtPiB1MzIgewogICAgbGV0IHMgPSBTcSgzKTsKICAgIGxldCByID0gUmVjdCgxLCAyKTsKICAgIGNnX2xpYjo6YXJlYV9vZigmcykgKyBjZ19saWI6OmFyZWFfZHluKCZyKSArIHMuYXJlYSgpICsgY2dfbGliOjp1dGlsOjpkb3VibGUoMikKfQo=","source":{"corpus":"lance-graph","language":"","path":"cg_app/src/lib.rs","root":"","signature":""}}
{"fact_name":"/kythe/node/kind","fact_value":"YW5jaG9y","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@52:202"}}
{"fact_name":"/kythe/loc/start","fact_value":"NTI=","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@52:202"}}
{"fact_name":"/kythe/loc/end","fact_value":"MjAy","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@52:202"}}
{"fact_name":"/kythe/node/kind","fact_value":"ZnVuY3Rpb24=","source":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_app::run"}}
{"edge_kind":"/kythe/edge/defines","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@52:202"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_app::run"}}
{"fact_name":"/kythe/node/kind","fact_value":"YW5jaG9y","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@121:140"}}
{"fact_name":"/kythe/loc/start","fact_value":"MTIx","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@121:140"}}
{"fact_name":"/kythe/loc/end","fact_value":"MTQw","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@121:140"}}
{"edge_kind":"/kythe/edge/ref/call","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@121:140"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_lib::area_of"}}
{"edge_kind":"/kythe/edge/childof","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@121:140"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_app::run"}}
{"fact_name":"/kythe/node/kind","fact_value":"YW5jaG9y","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@143:163"}}
{"fact_name":"/kythe/loc/start","fact_value":"MTQz","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@143:163"}}
{"fact_name":"/kythe/loc/end","fact_value":"MTYz","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@143:163"}}
{"edge_kind":"/kythe/edge/ref/call","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@143:163"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_lib::area_dyn"}}
{"edge_kind":"/kythe/edge/childof","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@143:163"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_app::run"}}
{"fact_name":"/kythe/node/kind","fact_value":"YW5jaG9y","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@166:174"}}
{"fact_name":"/kythe/loc/start","fact_value":"MTY2","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@166:174"}}
{"fact_name":"/kythe/loc/end","fact_value":"MTc0","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@166:174"}}
{"edge_kind":"/kythe/edge/ref/call","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@166:174"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_lib::shapes::impl_Shape_for_Square::area"}}
{"edge_kind":"/kythe/edge/childof","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@166:174"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_app::run"}}
{"fact_name":"/kythe/node/kind","fact_value":"YW5jaG9y","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@177:200"}}
{"fact_name":"/kythe/loc/start","fact_value":"MTc3","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@177:200"}}
{"fact_name":"/kythe/loc/end","fact_value":"MjAw","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@177:200"}}
{"edge_kind":"/kythe/edge/ref/call","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@177:200"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_lib::util::double"}}
{"edge_kind":"/kythe/edge/childof","fact_name":"/","source":{"corpus":"lance-graph","language":"rust","path":"cg_app/src/lib.rs","root":"","signature":"@177:200"},"target":{"corpus":"lance-graph","language":"rust","path":"","root":"","signature":"cg_app::run"}}
Loading
Loading