diff --git a/crates/paralegal-flow/src/ana/graph_converter.rs b/crates/paralegal-flow/src/ana/graph_converter.rs new file mode 100644 index 0000000000..086e8f06cc --- /dev/null +++ b/crates/paralegal-flow/src/ana/graph_converter.rs @@ -0,0 +1,741 @@ +use crate::{ + ana::inline_judge::InlineJudge, + ann::MarkerAnnotation, + desc::*, + discover::FnToAnalyze, + rust::{hir::def, *}, + utils::*, + DefId, HashMap, HashSet, MarkerCtx, +}; +use flowistry::mir::placeinfo::PlaceInfo; +use flowistry_pdg::SourceUse; +use paralegal_spdg::Node; +use rustc_utils::cache::Cache; + +use std::{ + cell::RefCell, + fmt::Display, + rc::Rc, + time::{Duration, Instant}, +}; + +use self::call_string_resolver::CallStringResolver; + +use super::{default_index, path_for_item, src_loc_for_span, SPDGGenerator}; +use anyhow::{anyhow, Result}; +use either::Either; +use flowistry_pdg_construction::{ + determine_async, + graph::{DepEdge, DepEdgeKind, DepGraph, DepNode}, + is_async_trait_fn, is_non_default_trait_method, match_async_trait_assign, CallChangeCallback, + CallChanges, CallInfo, InlineMissReason, PdgParams, + SkipCall::Skip, +}; +use petgraph::{ + visit::{IntoNodeReferences, NodeIndexable, NodeRef}, + Direction, +}; + +/// Structure responsible for converting one [`DepGraph`] into an [`SPDG`]. +/// +/// Intended usage is to call [`Self::new_with_flowistry`] to initialize, then +/// [`Self::make_spdg`] to convert. +pub struct GraphConverter<'tcx, 'a, C> { + // Immutable information + /// The parent generator + generator: &'a SPDGGenerator<'tcx>, + /// Information about the function this PDG belongs to + target: &'a FnToAnalyze, + /// The flowistry graph we are converting + dep_graph: Rc>, + /// Same as the ID stored in self.target, but as a local def id + local_def_id: LocalDefId, + + // Mutable fields + /// Where we write every [`DefId`] we encounter into. + known_def_ids: &'a mut C, + /// A map of which nodes are of which (marked) type. We build this up during + /// conversion. + types: HashMap>, + /// Mapping from old node indices to new node indices. Use + /// [`Self::register_node`] to insert and [`Self::new_node_for`] to query. + index_map: Box<[Node]>, + /// The converted graph we are creating + spdg: SPDGImpl, + marker_assignments: HashMap>, + call_string_resolver: call_string_resolver::CallStringResolver<'tcx>, + place_info_cache: PlaceInfoCache<'tcx>, +} + +pub type PlaceInfoCache<'tcx> = Rc>>; + +impl<'a, 'tcx, C: Extend> GraphConverter<'tcx, 'a, C> { + /// Initialize a new converter by creating an initial PDG using flowistry. + pub fn new_with_flowistry( + generator: &'a SPDGGenerator<'tcx>, + known_def_ids: &'a mut C, + target: &'a FnToAnalyze, + place_info_cache: PlaceInfoCache<'tcx>, + ) -> Result { + let local_def_id = target.def_id.expect_local(); + let start = Instant::now(); + let dep_graph = Self::create_flowistry_graph(generator, local_def_id)?; + if generator.opts.dbg().dump_flowistry_pdg() { + dep_graph.generate_graphviz(format!( + "{}.flowistry-pdg.pdf", + generator.tcx.def_path_str(target.def_id) + ))? + } + + Ok(Self { + generator, + known_def_ids, + target, + index_map: vec![default_index(); dep_graph.graph.node_bound()].into(), + dep_graph: dep_graph.into(), + local_def_id, + types: Default::default(), + spdg: Default::default(), + marker_assignments: Default::default(), + call_string_resolver: CallStringResolver::new(generator.tcx, local_def_id), + place_info_cache, + }) + } + + fn tcx(&self) -> TyCtxt<'tcx> { + self.generator.tcx + } + + fn marker_ctx(&self) -> &MarkerCtx<'tcx> { + &self.generator.marker_ctx() + } + + /// Is the top-level function (entrypoint) an `async fn` + fn entrypoint_is_async(&self) -> bool { + entrypoint_is_async(self.tcx(), self.local_def_id) + } + + /// Insert this node into the converted graph, return it's auto-assigned id + /// and register it as corresponding to `old` in the initial graph. Fails if + /// there is already a node registered as corresponding to `old`. + fn register_node(&mut self, old: Node, new: NodeInfo) -> Node { + let new_node = self.spdg.add_node(new); + let r = &mut self.index_map[old.index()]; + assert_eq!(*r, default_index()); + *r = new_node; + new_node + } + + /// Get the id of the new node that was registered for this old node. + fn new_node_for(&self, old: Node) -> Node { + let res = self.index_map[old.index()]; + assert_ne!(res, default_index()); + res + } + + fn register_markers(&mut self, node: Node, markers: impl IntoIterator) { + let mut markers = markers.into_iter().peekable(); + + if markers.peek().is_some() { + self.marker_assignments + .entry(node) + .or_default() + .extend(markers); + } + } + + fn place_info(&self, def_id: LocalDefId) -> &PlaceInfo<'tcx> { + self.place_info_cache.get(def_id, |_| { + PlaceInfo::build( + self.tcx(), + def_id.to_def_id(), + &self.tcx().body_for_def_id(def_id).unwrap(), + ) + }) + } + + /// Find direct annotations on this node and register them in the marker map. + fn node_annotations(&mut self, old_node: Node, weight: &DepNode<'tcx>) { + let leaf_loc = weight.at.leaf(); + let node = self.new_node_for(old_node); + + let body = &self.tcx().body_for_def_id(leaf_loc.function).unwrap().body; + + let graph = self.dep_graph.clone(); + + match leaf_loc.location { + RichLocation::Start + if matches!(body.local_kind(weight.place.local), mir::LocalKind::Arg) => + { + let function_id = leaf_loc.function.to_def_id(); + let arg_num = weight.place.local.as_u32() - 1; + self.known_def_ids.extend(Some(function_id)); + + self.register_annotations_for_function(node, function_id, |ann| { + ann.refinement.on_argument().contains(arg_num).unwrap() + }); + } + RichLocation::End if weight.place.local == mir::RETURN_PLACE => { + let function_id = leaf_loc.function.to_def_id(); + self.known_def_ids.extend(Some(function_id)); + self.register_annotations_for_function(node, function_id, |ann| { + ann.refinement.on_return() + }); + } + RichLocation::Location(loc) => { + let stmt_at_loc = body.stmt_at(loc); + if let crate::Either::Right( + term @ mir::Terminator { + kind: mir::TerminatorKind::Call { destination, .. }, + .. + }, + ) = stmt_at_loc + { + let res = self.call_string_resolver.resolve(weight.at); + let (fun, ..) = res + .try_monomorphize(self.tcx(), self.tcx().param_env(res.def_id()), term) + .as_instance_and_args(self.tcx()) + .unwrap(); + self.known_def_ids.extend(Some(fun.def_id())); + + // Question: Could a function with no input produce an + // output that has aliases? E.g. could some place, where the + // local portion isn't the local from the destination of + // this function call be affected/modified by this call? If + // so, that location would also need to have this marker + // attached + let needs_return_markers = weight.place.local == destination.local + || graph + .graph + .edges_directed(old_node, Direction::Incoming) + .any(|e| { + if weight.at != e.weight().at { + // Incoming edges are either from our operation or from control flow + let at = e.weight().at; + debug_assert!( + at.leaf().function == leaf_loc.function + && if let RichLocation::Location(loc) = + at.leaf().location + { + matches!( + body.stmt_at(loc), + Either::Right(mir::Terminator { + kind: mir::TerminatorKind::SwitchInt { .. }, + .. + }) + ) + } else { + false + } + ); + false + } else { + e.weight().target_use.is_return() + } + }); + + if needs_return_markers { + self.register_annotations_for_function(node, fun.def_id(), |ann| { + ann.refinement.on_return() + }); + } + + for e in graph.graph.edges_directed(old_node, Direction::Outgoing) { + let SourceUse::Argument(arg) = e.weight().source_use else { + continue; + }; + self.register_annotations_for_function(node, fun.def_id(), |ann| { + ann.refinement.on_argument().contains(arg as u32).unwrap() + }); + } + } + } + _ => (), + } + } + + /// Reconstruct the type for the data this node represents. + fn determine_place_type( + &self, + at: CallString, + place: mir::PlaceRef<'tcx>, + ) -> Option> { + let tcx = self.tcx(); + let locations = at.iter_from_root().collect::>(); + let (last, mut rest) = locations.split_last().unwrap(); + + if self.entrypoint_is_async() { + let (first, tail) = rest.split_first().unwrap(); + // The body of a top-level `async` function binds a closure to the + // return place `_0`. Here we expect are looking at the statement + // that does this binding. + assert!(expect_stmt_at(self.tcx(), *first).is_left()); + rest = tail; + } + + // So actually we're going to check the base place only, because + // Flowistry sometimes tracks subplaces instead but we want the marker + // from the base place. + let place = if self.entrypoint_is_async() && place.local.as_u32() == 1 && rest.len() == 1 { + if place.projection.is_empty() { + return None; + } + // in the case of targeting the top-level async closure (e.g. async args) + // we'll keep the first projection. + mir::Place { + local: place.local, + projection: self.tcx().mk_place_elems(&place.projection[..1]), + } + } else { + place.local.into() + }; + + let resolution = self.call_string_resolver.resolve(at); + + // Thread through each caller to recover generic arguments + let body = tcx.body_for_def_id(last.function).unwrap(); + let raw_ty = place.ty(&body.body, tcx); + Some(*resolution.try_monomorphize(tcx, ty::ParamEnv::reveal_all(), &raw_ty)) + } + + /// Fetch annotations item identified by this `id`. + /// + /// The callback is used to filter out annotations where the "refinement" + /// doesn't match. The idea is that the caller of this function knows + /// whether they are looking for annotations on an argument or return of a + /// function identified by this `id` or on a type and the callback should be + /// used to enforce this. + fn register_annotations_for_function( + &mut self, + node: Node, + function: DefId, + mut filter: impl FnMut(&MarkerAnnotation) -> bool, + ) { + let parent = get_parent(self.tcx(), function); + let marker_ctx = self.marker_ctx().clone(); + self.register_markers( + node, + marker_ctx + .combined_markers(function) + .chain( + parent + .into_iter() + .flat_map(|parent| marker_ctx.combined_markers(parent)), + ) + .filter(|ann| filter(ann)) + .map(|ann| ann.marker), + ); + self.known_def_ids.extend(parent); + } + + /// Check if this node is of a marked type and register that type. + fn handle_node_types(&mut self, old_node: Node, weight: &DepNode<'tcx>) { + let i = self.new_node_for(old_node); + + let Some(place_ty) = self.determine_place_type(weight.at, weight.place.as_ref()) else { + return; + }; + let place_info = self.place_info(weight.at.leaf().function); + let deep = !place_info.children(weight.place).is_empty(); + let mut node_types = self.type_is_marked(place_ty, deep).collect::>(); + for (p, _) in weight.place.iter_projections() { + if let Some(place_ty) = self.determine_place_type(weight.at, p) { + node_types.extend(self.type_is_marked(place_ty, false)); + } + } + self.known_def_ids.extend(node_types.iter().copied()); + let tcx = self.tcx(); + if !node_types.is_empty() { + self.types + .entry(i) + .or_default() + .extend(node_types.iter().filter(|t| match tcx.def_kind(*t) { + def::DefKind::Generator => false, + kind => !kind.is_fn_like(), + })) + } + } + + /// Create an initial flowistry graph for the function identified by + /// `local_def_id`. + fn create_flowistry_graph( + generator: &SPDGGenerator<'tcx>, + local_def_id: LocalDefId, + ) -> Result> { + let tcx = generator.tcx; + let opts = generator.opts; + let judge = generator.inline_judge.clone(); + let params = PdgParams::new(tcx, local_def_id) + .map_err(|_| anyhow!("unable to contruct PDG for {local_def_id:?}"))? + .with_call_change_callback(MyCallback { judge, tcx }) + .with_dump_mir(generator.opts.dbg().dump_mir()); + + if opts.dbg().dump_mir() { + let mut file = std::fs::File::create(format!( + "{}.mir", + tcx.def_path_str(local_def_id.to_def_id()) + ))?; + mir::pretty::write_mir_fn( + tcx, + &tcx.body_for_def_id_default_policy(local_def_id) + .ok_or_else(|| anyhow!("Body not found"))? + .body, + &mut |_, _| Ok(()), + &mut file, + )? + } + let flowistry_time = Instant::now(); + let pdg = flowistry_pdg_construction::compute_pdg(params); + Ok(pdg) + } + + /// Consume the generator and compile the [`SPDG`]. + pub fn make_spdg(mut self) -> SPDG { + let start = Instant::now(); + self.make_spdg_impl(); + let arguments = self.determine_arguments(); + let return_ = self.determine_return(); + SPDG { + path: path_for_item(self.local_def_id.to_def_id(), self.tcx()), + graph: self.spdg, + id: self.local_def_id, + name: Identifier::new(self.target.name()), + arguments, + markers: self + .marker_assignments + .into_iter() + .map(|(k, v)| (k, v.into_iter().collect())) + .collect(), + return_, + type_assigns: self + .types + .into_iter() + .map(|(k, v)| (k, Types(v.into()))) + .collect(), + } + } + + /// This initializes the fields `spdg` and `index_map` and should be called first + fn make_spdg_impl(&mut self) { + use petgraph::prelude::*; + let g_ref = self.dep_graph.clone(); + let input = &g_ref.graph; + let tcx = self.tcx(); + + for (i, weight) in input.node_references() { + let at = weight.at.leaf(); + let body = &tcx.body_for_def_id(at.function).unwrap().body; + + let node_span = body.local_decls[weight.place.local].source_info.span; + let new_idx = self.register_node( + i, + NodeInfo { + at: weight.at, + description: format!("{:?}", weight.place), + span: src_loc_for_span(node_span, tcx), + }, + ); + trace!( + "Node {new_idx:?}\n description: {:?}\n at: {at}\n stmt: {}", + weight.place, + match at.location { + RichLocation::Location(loc) => { + match body.stmt_at(loc) { + Either::Left(s) => format!("{:?}", s.kind), + Either::Right(s) => format!("{:?}", s.kind), + } + } + RichLocation::End => "end".to_string(), + RichLocation::Start => "start".to_string(), + } + ); + self.node_annotations(i, weight); + + self.handle_node_types(i, weight); + } + + for e in input.edge_references() { + let DepEdge { + kind, + at, + source_use, + target_use, + } = *e.weight(); + self.spdg.add_edge( + self.new_node_for(e.source()), + self.new_node_for(e.target()), + EdgeInfo { + at, + kind: match kind { + DepEdgeKind::Control => EdgeKind::Control, + DepEdgeKind::Data => EdgeKind::Data, + }, + source_use, + target_use, + }, + ); + } + } + + /// Return the (sub)types of this type that are marked. + fn type_is_marked( + &'a self, + typ: mir::tcx::PlaceTy<'tcx>, + deep: bool, + ) -> impl Iterator + 'a { + if deep { + Either::Left(self.marker_ctx().deep_type_markers(typ.ty).iter().copied()) + } else { + Either::Right(self.marker_ctx().shallow_type_markers(typ.ty)) + } + .map(|(d, _)| d) + + // self.marker_ctx() + // .all_type_markers(typ.ty) + // .map(|t| t.1 .1) + // .collect() + } + + /// Similar to `CallString::is_at_root`, but takes into account top-level + /// async functions + fn try_as_root(&self, at: CallString) -> Option { + if self.entrypoint_is_async() && at.len() == 2 { + at.iter_from_root().nth(1) + } else if at.is_at_root() { + Some(at.leaf()) + } else { + None + } + } + + /// Try to find the node corresponding to the values returned from this + /// controller. + /// + /// TODO: Include mutable inputs + fn determine_return(&self) -> Box<[Node]> { + // In async functions + let return_candidates = self + .spdg + .node_references() + .filter(|n| { + let weight = n.weight(); + let at = weight.at; + matches!(self.try_as_root(at), Some(l) if l.location == RichLocation::End) + }) + .map(|n| n.id()) + .collect::>(); + if return_candidates.len() != 1 { + warn!("Found many candidates for the return: {return_candidates:?}."); + } + return_candidates + } + + /// Determine the set if nodes corresponding to the inputs to the + /// entrypoint. The order is guaranteed to be the same as the source-level + /// function declaration. + fn determine_arguments(&self) -> Box<[Node]> { + let mut g_nodes: Vec<_> = self + .dep_graph + .graph + .node_references() + .filter(|n| { + let at = n.weight().at; + let is_candidate = + matches!(self.try_as_root(at), Some(l) if l.location == RichLocation::Start); + is_candidate + }) + .collect(); + + g_nodes.sort_by_key(|(_, i)| i.place.local); + + g_nodes + .into_iter() + .map(|n| self.new_node_for(n.id())) + .collect() + } +} + +struct MyCallback<'tcx> { + judge: InlineJudge<'tcx>, + tcx: TyCtxt<'tcx>, +} + +impl<'tcx> CallChangeCallback<'tcx> for MyCallback<'tcx> { + fn on_inline(&self, info: CallInfo<'tcx>) -> CallChanges<'tcx> { + let mut changes = CallChanges::default(); + + let mut skip = true; + + if is_non_default_trait_method(self.tcx, info.callee.def_id()).is_some() { + self.tcx.sess.span_warn( + self.tcx.def_span(info.callee.def_id()), + "Skipping analysis of unresolvable trait method.", + ); + } else if self.judge.should_inline(info.callee) { + skip = false; + }; + + if skip { + changes = changes.with_skip(Skip); + } + changes + } + + fn on_inline_miss( + &self, + resolution: FnResolution<'tcx>, + loc: Location, + parent: FnResolution<'tcx>, + call_string: Option, + reason: InlineMissReason, + ) { + let body = self + .tcx + .body_for_def_id(parent.def_id().expect_local()) + .unwrap(); + let span = body + .body + .stmt_at(loc) + .either(|s| s.source_info.span, |t| t.source_info.span); + let markers_reachable = self.judge.marker_ctx().get_reachable_markers(resolution); + self.tcx.sess.span_err( + span, + format!( + "Could not inline this function call in {:?}, at {} because {reason:?}. {}", + parent.def_id(), + call_string.map_or("root".to_owned(), |c| c.to_string()), + Print(|f| if markers_reachable.is_empty() { + f.write_str("No markers are reachable") + } else { + f.write_str("Markers ")?; + write_sep(f, ", ", markers_reachable.iter(), Display::fmt)?; + f.write_str(" are reachable") + }) + ), + ); + } +} + +/// Find the statement at this location or fail. +fn expect_stmt_at<'tcx>( + tcx: TyCtxt<'tcx>, + loc: GlobalLocation, +) -> Either<&'tcx mir::Statement<'tcx>, &'tcx mir::Terminator<'tcx>> { + let body = &tcx.body_for_def_id(loc.function).unwrap().body; + let RichLocation::Location(loc) = loc.location else { + unreachable!(); + }; + body.stmt_at(loc) +} + +/// If `did` is a method of an `impl` of a trait, then return the `DefId` that +/// refers to the method on the trait definition. +fn get_parent(tcx: TyCtxt, did: DefId) -> Option { + let ident = tcx.opt_item_ident(did)?; + let kind = match tcx.def_kind(did) { + kind if kind.is_fn_like() => ty::AssocKind::Fn, + // todo allow constants and types also + _ => return None, + }; + let r#impl = tcx.impl_of_method(did)?; + let r#trait = tcx.trait_id_of_impl(r#impl)?; + let id = tcx + .associated_items(r#trait) + .find_by_name_and_kind(tcx, ident, kind, r#trait)? + .def_id; + Some(id) +} + +fn entrypoint_is_async(tcx: TyCtxt, local_def_id: LocalDefId) -> bool { + tcx.asyncness(local_def_id).is_async() + || is_async_trait_fn( + tcx, + local_def_id.to_def_id(), + &tcx.body_for_def_id(local_def_id).unwrap().body, + ) +} + +mod call_string_resolver { + //! Resolution of [`CallString`]s to [`FnResolution`]s. + //! + //! This is a separate mod so that we can use encapsulation to preserve the + //! internal invariants of the resolver. + + use flowistry_pdg::{rustc_portable::LocalDefId, CallString}; + use flowistry_pdg_construction::{try_resolve_function, FnResolution}; + use rustc_utils::cache::Cache; + + use crate::{Either, TyCtxt}; + + use super::{map_either, match_async_trait_assign, AsFnAndArgs}; + + /// Cached resolution of [`CallString`]s to [`FnResolution`]s. + /// + /// Only valid for a single controller. Each controller should initialize a + /// new resolver. + pub struct CallStringResolver<'tcx> { + cache: Cache>, + tcx: TyCtxt<'tcx>, + entrypoint_is_async: bool, + } + + impl<'tcx> CallStringResolver<'tcx> { + /// Tries to resolve to the monomophized function in which this call + /// site exists. That is to say that `return.def_id() == + /// cs.leaf().function`. + /// + /// Unlike `Self::resolve_internal` this can be called on any valid + /// [`CallString`]. + pub fn resolve(&self, cs: CallString) -> FnResolution<'tcx> { + let (this, opt_prior_loc) = cs.pop(); + if let Some(prior_loc) = opt_prior_loc { + if prior_loc.len() == 1 && self.entrypoint_is_async { + FnResolution::Partial(this.function.to_def_id()) + } else { + self.resolve_internal(prior_loc) + } + } else { + FnResolution::Partial(this.function.to_def_id()) + } + } + + pub fn new(tcx: TyCtxt<'tcx>, entrypoint: LocalDefId) -> Self { + Self { + cache: Default::default(), + tcx, + entrypoint_is_async: super::entrypoint_is_async(tcx, entrypoint), + } + } + + /// This resolves the monomorphized function *being called at* this call + /// site. + /// + /// This function is internal because it panics if `cs.leaf().location` + /// is not either a function call or a statement where an async closure + /// is created and assigned. + fn resolve_internal(&self, cs: CallString) -> FnResolution<'tcx> { + *self.cache.get(cs, |_| { + let this = cs.leaf(); + let prior = self.resolve(cs); + + let tcx = self.tcx; + + let base_stmt = super::expect_stmt_at(tcx, this); + let param_env = tcx.param_env_reveal_all_normalized(prior.def_id()); + let normalized = map_either( + base_stmt, + |stmt| prior.try_monomorphize(tcx, param_env, stmt), + |term| prior.try_monomorphize(tcx, param_env, term), + ); + let res = match normalized { + Either::Right(term) => term.as_ref().as_instance_and_args(tcx).unwrap().0, + Either::Left(stmt) => { + let (def_id, generics) = match_async_trait_assign(stmt.as_ref()).unwrap(); + try_resolve_function(tcx, def_id, param_env, generics) + } + }; + res + }) + } + } +} diff --git a/crates/paralegal-flow/src/ana/inline_judge.rs b/crates/paralegal-flow/src/ana/inline_judge.rs index 3063ad91ef..111c05c777 100644 --- a/crates/paralegal-flow/src/ana/inline_judge.rs +++ b/crates/paralegal-flow/src/ana/inline_judge.rs @@ -1,5 +1,6 @@ use crate::{utils::FnResolution, AnalysisCtrl, MarkerCtx, TyCtxt}; +#[derive(Clone)] /// The interpretation of marker placement as it pertains to inlining and inline /// elision. /// @@ -38,4 +39,8 @@ impl<'tcx> InlineJudge<'tcx> { pub fn should_inline(&self, function: FnResolution<'tcx>) -> bool { self.analysis_control.use_recursive_analysis() && !self.function_has_markers(function) } + + pub fn marker_ctx(&self) -> &MarkerCtx<'tcx> { + &self.marker_ctx + } } diff --git a/crates/paralegal-flow/src/ana/mod.rs b/crates/paralegal-flow/src/ana/mod.rs index 98bab09862..ec5b3a2585 100644 --- a/crates/paralegal-flow/src/ana/mod.rs +++ b/crates/paralegal-flow/src/ana/mod.rs @@ -33,26 +33,38 @@ use flowistry_pdg_construction::{ SkipCall::Skip, }; +mod graph_converter; mod inline_judge; +use self::{ + graph_converter::{GraphConverter, PlaceInfoCache}, + inline_judge::InlineJudge, +}; + /// Read-only database of information the analysis needs. /// /// [`Self::analyze`] serves as the main entrypoint to SPDG generation. pub struct SPDGGenerator<'tcx> { - pub marker_ctx: MarkerCtx<'tcx>, + pub inline_judge: InlineJudge<'tcx>, pub opts: &'static crate::Args, pub tcx: TyCtxt<'tcx>, + place_info_cache: PlaceInfoCache<'tcx>, } impl<'tcx> SPDGGenerator<'tcx> { pub fn new(marker_ctx: MarkerCtx<'tcx>, opts: &'static crate::Args, tcx: TyCtxt<'tcx>) -> Self { Self { - marker_ctx, + inline_judge: InlineJudge::new(marker_ctx, tcx, &opts.anactrl()), opts, tcx, + place_info_cache: Default::default(), } } + pub fn marker_ctx(&self) -> &MarkerCtx<'tcx> { + self.inline_judge.marker_ctx() + } + /// Perform the analysis for one `#[paralegal_flow::analyze]` annotated function and /// return the representation suitable for emitting into Forge. /// @@ -60,13 +72,18 @@ impl<'tcx> SPDGGenerator<'tcx> { fn handle_target( &self, //_hash_verifications: &mut HashVerifications, - target: FnToAnalyze, + target: &FnToAnalyze, known_def_ids: &mut impl Extend, ) -> Result<(Endpoint, SPDG)> { debug!("Handling target {}", target.name()); let local_def_id = target.def_id.expect_local(); - let converter = GraphConverter::new_with_flowistry(self, known_def_ids, target)?; + let converter = GraphConverter::new_with_flowistry( + self, + known_def_ids, + target, + self.place_info_cache.clone(), + )?; let spdg = converter.make_spdg(); Ok((local_def_id, spdg)) @@ -93,7 +110,7 @@ impl<'tcx> SPDGGenerator<'tcx> { let mut known_def_ids = HashSet::new(); targets - .into_iter() + .iter() .map(|desc| { let target_name = desc.name(); with_reset_level_if_target(self.opts, target_name, || { @@ -105,7 +122,7 @@ impl<'tcx> SPDGGenerator<'tcx> { }) }) .collect::>>() - .map(|controllers| self.make_program_description(controllers, &known_def_ids)) + .map(|controllers| self.make_program_description(controllers, known_def_ids)) } /// Given the PDGs and a record of all [`DefId`]s we've seen, compile @@ -114,22 +131,23 @@ impl<'tcx> SPDGGenerator<'tcx> { fn make_program_description( &self, controllers: HashMap, - known_def_ids: &HashSet, + mut known_def_ids: HashSet, ) -> ProgramDescription { let tcx = self.tcx; - // And now, for every mentioned method in an impl, add the markers on - // the corresponding trait method also to the impl method. + let instruction_info = self.collect_instruction_info(&controllers); + + let type_info = self.collect_type_info(); + known_def_ids.extend(type_info.keys()); let def_info = known_def_ids .iter() - .map(|id| (*id, def_info_for_item(*id, tcx))) + .map(|id| (*id, def_info_for_item(*id, self.marker_ctx(), tcx))) .collect(); - let type_info = self.collect_type_info(); type_info_sanity_check(&controllers, &type_info); ProgramDescription { type_info, - instruction_info: self.collect_instruction_info(&controllers), + instruction_info, controllers, def_info, } @@ -155,26 +173,25 @@ impl<'tcx> SPDGGenerator<'tcx> { .map(|i| { let body = &self.tcx.body_for_def_id(i.function).unwrap().body; - let kind = match i.location { - RichLocation::End => InstructionKind::Return, - RichLocation::Start => InstructionKind::Start, - RichLocation::Location(loc) => { - let kind = match body.stmt_at(loc) { - crate::Either::Right(term) => { - if let Ok((id, ..)) = term.as_fn_and_args(self.tcx) { - InstructionKind::FunctionCall(FunctionCallInfo { - id, - is_inlined: id.is_local(), - }) - } else { - InstructionKind::Terminator - } - } - crate::Either::Left(_) => InstructionKind::Statement, - }; - - kind - } + let (kind, description) = match i.location { + RichLocation::End => (InstructionKind::Return, "start".to_owned()), + RichLocation::Start => (InstructionKind::Start, "end".to_owned()), + RichLocation::Location(loc) => match body.stmt_at(loc) { + crate::Either::Right(term) => { + let kind = if let Ok((id, ..)) = term.as_fn_and_args(self.tcx) { + InstructionKind::FunctionCall(FunctionCallInfo { + id, + is_inlined: id.is_local(), + }) + } else { + InstructionKind::Terminator + }; + (kind, format!("{:?}", term.kind)) + } + crate::Either::Left(stmt) => { + (InstructionKind::Statement, format!("{:?}", stmt.kind)) + } + }, }; let rust_span = match i.location { RichLocation::Location(loc) => { @@ -194,6 +211,7 @@ impl<'tcx> SPDGGenerator<'tcx> { InstructionInfo { kind, span: src_loc_for_span(rust_span, self.tcx), + description: Identifier::new_intern(&description), }, ) }) @@ -203,16 +221,12 @@ impl<'tcx> SPDGGenerator<'tcx> { /// Create a [`TypeDescription`] record for each marked type that as /// mentioned in the PDG. fn collect_type_info(&self) -> TypeInfoMap { - self.marker_ctx + self.marker_ctx() .all_annotations() .filter(|(id, _)| def_kind_for_item(*id, self.tcx).is_type()) .into_grouping_map() .fold_with( - |id, _| TypeDescription { - rendering: format!("{id:?}"), - otypes: vec![], - markers: vec![], - }, + |id, _| (format!("{id:?}"), vec![], vec![]), |mut desc, _, ann| { match ann { Either::Right(MarkerAnnotation { refinement, marker }) @@ -221,14 +235,26 @@ impl<'tcx> SPDGGenerator<'tcx> { marker, })) => { assert!(refinement.on_self()); - desc.markers.push(*marker) + desc.2.push(*marker) } - Either::Left(Annotation::OType(id)) => desc.otypes.push(*id), + Either::Left(Annotation::OType(id)) => desc.1.push(*id), _ => panic!("Unexpected type of annotation {ann:?}"), } desc }, ) + .into_iter() + .map(|(k, (rendering, otypes, markers))| { + ( + k, + TypeDescription { + rendering, + otypes: otypes.into(), + markers, + }, + ) + }) + .collect() } } @@ -267,483 +293,14 @@ fn src_loc_for_span(span: RustSpan, tcx: TyCtxt) -> Span { fn default_index() -> ::NodeId { ::NodeId::end() } - -/// Structure responsible for converting one [`DepGraph`] into an [`SPDG`]. -/// -/// Intended usage is to call [`Self::new_with_flowistry`] to initialize, then -/// [`Self::make_spdg`] to convert. -struct GraphConverter<'tcx, 'a, C> { - // Immutable information - /// The parent generator - generator: &'a SPDGGenerator<'tcx>, - /// Information about the function this PDG belongs to - target: FnToAnalyze, - /// The flowistry graph we are converting - dep_graph: Rc>, - /// Same as the ID stored in self.target, but as a local def id - local_def_id: LocalDefId, - - // Mutable fields - /// Where we write every [`DefId`] we encounter into. - known_def_ids: &'a mut C, - /// A map of which nodes are of which (marked) type. We build this up during - /// conversion. - types: HashMap, - /// Mapping from old node indices to new node indices. Use - /// [`Self::register_node`] to insert and [`Self::new_node_for`] to query. - index_map: Box<[Node]>, - /// The converted graph we are creating - spdg: SPDGImpl, -} - -impl<'a, 'tcx, C: Extend> GraphConverter<'tcx, 'a, C> { - /// Initialize a new converter by creating an initial PDG using flowistry. - fn new_with_flowistry( - generator: &'a SPDGGenerator<'tcx>, - known_def_ids: &'a mut C, - target: FnToAnalyze, - ) -> Result { - let local_def_id = target.def_id.expect_local(); - let dep_graph = Rc::new(Self::create_flowistry_graph(generator, local_def_id)?); - - if generator.opts.dbg().dump_flowistry_pdg() { - dep_graph.generate_graphviz(format!("{}.flowistry-pdg.pdf", target.name))? - } - - Ok(Self { - generator, - known_def_ids, - target, - index_map: vec![default_index(); dep_graph.as_ref().graph.node_bound()].into(), - dep_graph, - local_def_id, - types: Default::default(), - spdg: Default::default(), - }) - } - - fn tcx(&self) -> TyCtxt<'tcx> { - self.generator.tcx - } - - fn marker_ctx(&self) -> &MarkerCtx<'tcx> { - &self.generator.marker_ctx - } - - /// Is the top-level function (entrypoint) an `async fn` - fn entrypoint_is_async(&self) -> bool { - self.tcx().asyncness(self.local_def_id).is_async() - } - - /// Find the statement at this location or fail. - fn expect_stmt_at( - &self, - loc: GlobalLocation, - ) -> Either<&'tcx mir::Statement<'tcx>, &'tcx mir::Terminator<'tcx>> { - let body = &self.tcx().body_for_def_id(loc.function).unwrap().body; - let RichLocation::Location(loc) = loc.location else { - unreachable!(); - }; - body.stmt_at(loc) - } - - /// Insert this node into the converted graph, return it's auto-assigned id - /// and register it as corresponding to `old` in the initial graph. Fails if - /// there is already a node registered as corresponding to `old`. - fn register_node(&mut self, old: Node, new: NodeInfo) -> Node { - let new_node = self.spdg.add_node(new); - let r = &mut self.index_map[old.index()]; - assert_eq!(*r, default_index()); - *r = new_node; - new_node - } - - /// Get the id of the new node that was registered for this old node. - fn new_node_for(&self, old: Node) -> Node { - let res = self.index_map[old.index()]; - assert_ne!(res, default_index()); - res - } - - /// Try to discern if this node is a special [`NodeKind`]. Also returns if - /// the location corresponds to a function call for an external function and - /// any marker annotations on this node. - fn determine_node_kind(&mut self, weight: &DepNode<'tcx>) -> (NodeKind, bool, Vec) { - let leaf_loc = weight.at.leaf(); - - let body = &self.tcx().body_for_def_id(leaf_loc.function).unwrap().body; - - match leaf_loc.location { - RichLocation::Start - if matches!(body.local_kind(weight.place.local), mir::LocalKind::Arg) => - { - let function_id = leaf_loc.function.to_def_id(); - let arg_num = weight.place.local.as_u32() - 1; - self.known_def_ids.extend(Some(function_id)); - - let (annotations, parent) = self.annotations_for_function(function_id, |ann| { - ann.refinement.on_argument().contains(arg_num).unwrap() - }); - - self.known_def_ids.extend(parent); - (NodeKind::FormalParameter(arg_num as u8), false, annotations) - } - RichLocation::End if weight.place.local == mir::RETURN_PLACE => { - let function_id = leaf_loc.function.to_def_id(); - self.known_def_ids.extend(Some(function_id)); - let (annotations, parent) = - self.annotations_for_function(function_id, |ann| ann.refinement.on_return()); - self.known_def_ids.extend(parent); - (NodeKind::FormalReturn, false, annotations) - } - RichLocation::Location(loc) => { - let stmt_at_loc = body.stmt_at(loc); - let matches_place = |place| weight.place.simple_overlaps(place).contains_other(); - if let crate::Either::Right( - term @ mir::Terminator { - kind: - mir::TerminatorKind::Call { - args, destination, .. - }, - .. - }, - ) = stmt_at_loc - { - let indices: TinyBitSet = args - .iter() - .enumerate() - .filter_map(|(i, op)| matches_place(op.place()?).then_some(i as u32)) - .collect::(); - let (fun, ..) = term.as_fn_and_args(self.tcx()).unwrap(); - self.known_def_ids.extend(Some(fun)); - let is_external = !fun.is_local(); - let kind = if !indices.is_empty() { - NodeKind::ActualParameter(indices) - } else if matches_place(*destination) { - NodeKind::ActualReturn - } else { - NodeKind::Unspecified - }; - // TODO implement matching the unspecified node type. OR we - // could make sure that there are no unspecified nodes here - let annotations = match kind { - NodeKind::ActualReturn => { - self.annotations_for_function(fun, |ann| ann.refinement.on_return()) - .0 - } - NodeKind::ActualParameter(index) => { - self.annotations_for_function(fun, |ann| { - !ann.refinement.on_argument().intersection(index).is_empty() - }) - .0 - } - NodeKind::Unspecified => vec![], - _ => unreachable!(), - }; - (kind, is_external, annotations) - } else { - // TODO attach annotations if the return value is a marked type - (NodeKind::Unspecified, false, vec![]) - } - } - _ => (NodeKind::Unspecified, false, vec![]), - } - } - - /// Reconstruct the type for the data this node represents. - fn determine_place_type(&self, weight: &DepNode<'tcx>) -> mir::tcx::PlaceTy<'tcx> { - let tcx = self.tcx(); - let locations = weight.at.iter_from_root().collect::>(); - let (last, mut rest) = locations.split_last().unwrap(); - - if self.entrypoint_is_async() { - let (first, tail) = rest.split_first().unwrap(); - // The body of a top-level `async` function binds a closure to the - // return place `_0`. Here we expect are looking at the statement - // that does this binding. - assert!(self.expect_stmt_at(*first).is_left()); - rest = tail; - } - let resolution = rest.iter().fold( - FnResolution::Partial(self.local_def_id.to_def_id()), - |resolution, caller| { - let crate::Either::Right(terminator) = self.expect_stmt_at(*caller) else { - unreachable!() - }; - let term = match resolution { - FnResolution::Final(instance) => { - Cow::Owned(instance.subst_mir_and_normalize_erasing_regions( - tcx, - tcx.param_env(resolution.def_id()), - ty::EarlyBinder::bind(terminator.clone()), - )) - } - FnResolution::Partial(_) => Cow::Borrowed(terminator), - }; - let (instance, ..) = term.as_instance_and_args(tcx).unwrap(); - instance - }, - ); - // Thread through each caller to recover generic arguments - let body = tcx.body_for_def_id(last.function).unwrap(); - let raw_ty = weight.place.ty(&body.body, tcx); - match resolution { - FnResolution::Partial(_) => raw_ty, - FnResolution::Final(instance) => instance.subst_mir_and_normalize_erasing_regions( - tcx, - ty::ParamEnv::reveal_all(), - ty::EarlyBinder::bind(tcx.erase_regions(raw_ty)), - ), - } - } - - /// Fetch annotations item identified by this `id`. - /// - /// The callback is used to filter out annotations where the "refinement" - /// doesn't match. The idea is that the caller of this function knows - /// whether they are looking for annotations on an argument or return of a - /// function identified by this `id` or on a type and the callback should be - /// used to enforce this. - fn annotations_for_function( - &self, - function: DefId, - mut filter: impl FnMut(&MarkerAnnotation) -> bool, - ) -> (Vec, Option) { - let parent = get_parent(self.tcx(), function); - let annotations = self - .marker_ctx() - .combined_markers(function) - .chain( - parent - .into_iter() - .flat_map(|parent| self.marker_ctx().combined_markers(parent)), - ) - .filter(|ann| filter(ann)) - .map(|ann| ann.marker) - .collect::>(); - (annotations, parent) - } - - /// Check if this node is of a marked type and register that type. - fn handle_node_types( - &mut self, - i: Node, - weight: &DepNode<'tcx>, - is_external_call_source: bool, - ) { - let place_ty = self.determine_place_type(weight); - - if matches!( - place_ty.ty.peel_refs().kind(), - TyKind::FnDef { .. } - | TyKind::FnPtr(_) - | TyKind::Closure { .. } - | TyKind::Generator { .. } - ) { - // Functions are handled separately - return; - } - let type_markers = self.type_is_marked(place_ty, is_external_call_source); - self.known_def_ids.extend(type_markers.iter().copied()); - if !type_markers.is_empty() { - self.types.entry(i).or_default().0.extend(type_markers) - } - } - - /// Create an initial flowistry graph for the function identified by - /// `local_def_id`. - fn create_flowistry_graph( - generator: &SPDGGenerator<'tcx>, - local_def_id: LocalDefId, - ) -> Result> { - let tcx = generator.tcx; - let opts = generator.opts; - let judge = - inline_judge::InlineJudge::new(generator.marker_ctx.clone(), tcx, opts.anactrl()); - let params = PdgParams::new(tcx, local_def_id) - .map_err(|_| anyhow!("Param creation failed"))? - .with_call_change_callback(CallChangeCallbackFn::new(move |info: CallInfo<'tcx>| { - let changes = CallChanges::default(); - - if judge.should_inline(info.callee) { - changes - } else { - changes.with_skip(Skip) - } - })); - if opts.dbg().dump_mir() { - let mut file = - std::fs::File::create(format!("{}.mir", body_name_pls(tcx, local_def_id)))?; - mir::pretty::write_mir_fn( - tcx, - &tcx.body_for_def_id_default_policy(local_def_id) - .ok_or_else(|| anyhow!("Body not found"))? - .body, - &mut |_, _| Ok(()), - &mut file, - )? - } - - Ok(compute_pdg(params)) - } - - /// Consume the generator and compile the [`SPDG`]. - fn make_spdg(mut self) -> SPDG { - let markers = self.make_spdg_impl(); - let arguments = self.determine_arguments(); - let return_ = self.determine_return(); - SPDG { - graph: self.spdg, - name: Identifier::new(self.target.name()), - arguments, - markers, - return_, - type_assigns: self.types, - } - } - - /// This initializes the fields `spdg` and `index_map` and should be called first - fn make_spdg_impl(&mut self) -> HashMap> { - use petgraph::prelude::*; - let g_ref = self.dep_graph.clone(); - let input = &g_ref.graph; - let tcx = self.tcx(); - let mut markers: HashMap> = HashMap::new(); - - for (i, weight) in input.node_references() { - let (kind, is_external_call_source, node_markers) = self.determine_node_kind(weight); - let at = weight.at.leaf(); - let body = &tcx.body_for_def_id(at.function).unwrap().body; - - let node_span = body.local_decls[weight.place.local].source_info.span; - let new_idx = self.register_node( - i, - NodeInfo { - at: weight.at, - description: format!("{:?}", weight.place), - kind, - span: src_loc_for_span(node_span, tcx), - }, - ); - - if !node_markers.is_empty() { - markers.entry(new_idx).or_default().extend(node_markers) - } - - // TODO decide if this is correct. - if kind.is_actual_return() - || (kind.is_formal_parameter() - && matches!(self.try_as_root(weight.at), Some(l) if l.location == RichLocation::Start)) - { - self.handle_node_types(new_idx, weight, is_external_call_source); - } - } - - for e in input.edge_references() { - self.spdg.add_edge( - self.new_node_for(e.source()), - self.new_node_for(e.target()), - EdgeInfo { - at: e.weight().at, - kind: match e.weight().kind { - DepEdgeKind::Control => EdgeKind::Control, - DepEdgeKind::Data => EdgeKind::Data, - }, - }, - ); - } - - markers - } - - /// Return the (sub)types of this type that are marked. - fn type_is_marked(&self, typ: mir::tcx::PlaceTy<'tcx>, walk: bool) -> Vec { - if walk { - self.marker_ctx() - .all_type_markers(typ.ty) - .map(|t| t.1 .1) - .collect() - } else { - self.marker_ctx() - .type_has_surface_markers(typ.ty) - .into_iter() - .collect() - } - } - - /// Similar to `CallString::is_at_root`, but takes into account top-level - /// async functions - fn try_as_root(&self, at: CallString) -> Option { - if self.entrypoint_is_async() && at.len() == 2 { - at.iter_from_root().nth(1) - } else if at.is_at_root() { - Some(at.leaf()) - } else { - None - } - } - - /// Try to find the node corresponding to the values returned from this - /// controller. - /// - /// TODO: Include mutable inputs - fn determine_return(&self) -> Option { - // In async functions - let mut return_candidates = self - .spdg - .node_references() - .filter(|n| { - let weight = n.weight(); - let at = weight.at; - weight.kind.is_formal_return() - && matches!(self.try_as_root(at), Some(l) if l.location == RichLocation::End) - }) - .map(|n| n.id()) - .peekable(); - let picked = return_candidates.next()?; - assert!( - return_candidates.peek().is_none(), - "Found too many candidates for the return." - ); - Some(picked) - } - - /// Determine the set if nodes corresponding to the inputs to the - /// entrypoint. The order is guaranteed to be the same as the source-level - /// function declaration. - fn determine_arguments(&self) -> Vec { - let mut g_nodes: Vec<_> = self - .dep_graph - .graph - .node_references() - .filter(|n| { - let at = n.weight().at; - let is_candidate = - matches!(self.try_as_root(at), Some(l) if l.location == RichLocation::Start); - is_candidate - }) - .collect(); - - g_nodes.sort_by_key(|(_, i)| i.place.local); - - g_nodes - .into_iter() - .map(|n| self.new_node_for(n.id())) - .collect() - } -} - /// Checks the invariant that [`SPDGGenerator::collect_type_info`] should /// produce a map that is a superset of the types found in all the `types` maps /// on [`SPDG`]. -/// -/// Additionally this also inserts missing types into the map *only* for -/// generators created by async functions. fn type_info_sanity_check(controllers: &ControllerMap, types: &TypeInfoMap) { controllers .values() .flat_map(|spdg| spdg.type_assigns.values()) - .flat_map(|types| &types.0) + .flat_map(|types| types.0.iter()) .for_each(|t| { assert!( types.contains_key(t), @@ -751,25 +308,6 @@ fn type_info_sanity_check(controllers: &ControllerMap, types: &TypeInfoMap) { ); }) } - -/// If `did` is a method of an `impl` of a trait, then return the `DefId` that -/// refers to the method on the trait definition. -fn get_parent(tcx: TyCtxt, did: DefId) -> Option { - let ident = tcx.opt_item_ident(did)?; - let kind = match tcx.def_kind(did) { - kind if kind.is_fn_like() => ty::AssocKind::Fn, - // todo allow constants and types also - _ => return None, - }; - let r#impl = tcx.impl_of_method(did)?; - let r#trait = tcx.trait_id_of_impl(r#impl)?; - let id = tcx - .associated_items(r#trait) - .find_by_name_and_kind(tcx, ident, kind, r#trait)? - .def_id; - Some(id) -} - fn def_kind_for_item(id: DefId, tcx: TyCtxt) -> DefKind { match tcx.def_kind(id) { def::DefKind::Closure => DefKind::Closure, @@ -780,15 +318,13 @@ fn def_kind_for_item(id: DefId, tcx: TyCtxt) -> DefKind { | def::DefKind::OpaqueTy | def::DefKind::TyAlias { .. } | def::DefKind::Enum => DefKind::Type, - _ => unreachable!("{}", tcx.def_path_debug_str(id)), + kind => unreachable!("{} ({:?})", tcx.def_path_debug_str(id), kind), } } -fn def_info_for_item(id: DefId, tcx: TyCtxt) -> DefInfo { - let name = crate::utils::identifier_for_item(tcx, id); - let kind = def_kind_for_item(id, tcx); +fn path_for_item(id: DefId, tcx: TyCtxt) -> Box<[Identifier]> { let def_path = tcx.def_path(id); - let path = std::iter::once(Identifier::new(tcx.crate_name(def_path.krate))) + std::iter::once(Identifier::new(tcx.crate_name(def_path.krate))) .chain(def_path.data.iter().filter_map(|segment| { use hir::definitions::DefPathDataName::*; match segment.data.name() { @@ -796,12 +332,26 @@ fn def_info_for_item(id: DefId, tcx: TyCtxt) -> DefInfo { Anon { .. } => None, } })) - .collect(); + .collect() +} + +fn def_info_for_item(id: DefId, markers: &MarkerCtx, tcx: TyCtxt) -> DefInfo { + let name = crate::utils::identifier_for_item(tcx, id); + let kind = def_kind_for_item(id, tcx); DefInfo { name, - path, + path: path_for_item(id, tcx), kind, src_info: src_loc_for_span(tcx.def_span(id), tcx), + markers: markers + .combined_markers(id) + .cloned() + .map(|ann| paralegal_spdg::MarkerAnnotation { + marker: ann.marker, + on_return: ann.refinement.on_return(), + on_argument: ann.refinement.on_argument(), + }) + .collect(), } } diff --git a/crates/paralegal-flow/src/ann/db.rs b/crates/paralegal-flow/src/ann/db.rs index aec204d13a..9c0b2b2e58 100644 --- a/crates/paralegal-flow/src/ann/db.rs +++ b/crates/paralegal-flow/src/ann/db.rs @@ -20,9 +20,11 @@ use crate::{ resolve::expect_resolve_string_to_def_id, AsFnAndArgs, FnResolution, FnResolutionExt, IntoDefId, IntoHirId, MetaItemMatch, TyCtxtExt, TyExt, }, - DefId, Either, HashMap, LocalDefId, TyCtxt, + DefId, Either, HashMap, HashSet, LocalDefId, TyCtxt, }; -use rustc_utils::cache::CopyCache; +use flowistry_pdg_construction::determine_async; +use paralegal_spdg::Identifier; +use rustc_utils::cache::Cache; use std::{borrow::Cow, rc::Rc}; @@ -98,7 +100,9 @@ impl<'tcx> MarkerCtx<'tcx> { let def_kind = self.tcx().def_kind(def_id); if matches!(def_kind, DefKind::Generator) { if let Some(parent) = self.tcx().opt_parent(def_id) { - if self.tcx().asyncness(parent).is_async() { + if matches!(self.tcx().def_kind(parent), DefKind::AssocFn | DefKind::Fn) + && self.tcx().asyncness(parent).is_async() + { return parent; } }; @@ -157,60 +161,115 @@ impl<'tcx> MarkerCtx<'tcx> { /// Queries the transitive marker cache. pub fn has_transitive_reachable_markers(&self, res: FnResolution<'tcx>) -> bool { + !self.get_reachable_markers(res).is_empty() + } + + pub fn get_reachable_markers(&self, res: FnResolution<'tcx>) -> &[Identifier] { self.db() - .marker_reachable_cache - .get_maybe_recursive(res, |_| self.compute_marker_reachable(res)) - .unwrap_or(false) + .reachable_markers + .get_maybe_recursive(res, |_| self.compute_reachable_markers(res)) + .map_or(&[], Box::as_ref) + } + + fn get_reachable_and_self_markers( + &self, + res: FnResolution<'tcx>, + ) -> impl Iterator + '_ { + if res.def_id().is_local() { + let mut direct_markers = self + .combined_markers(res.def_id()) + .map(|m| m.marker) + .peekable(); + let non_direct = direct_markers + .peek() + .is_none() + .then(|| self.get_reachable_markers(res)); + + Either::Right(direct_markers.chain(non_direct.into_iter().flatten().copied())) + } else { + Either::Left( + self.all_function_markers(res) + .map(|m| m.0.marker) + .collect::>(), + ) + } + .into_iter() } /// If the transitive marker cache did not contain the answer, this is what /// computes it. - fn compute_marker_reachable(&self, res: FnResolution<'tcx>) -> bool { - let Some(body) = self - .tcx() - .body_for_def_id_default_policy(res.def_id().expect_local()) - else { - return false; + fn compute_reachable_markers(&self, res: FnResolution<'tcx>) -> Box<[Identifier]> { + trace!("Computing reachable markers for {res:?}"); + let Some(local) = res.def_id().as_local() else { + trace!(" Is not local"); + return Box::new([]); }; - let body = &body.body; - body.basic_blocks.iter().any(|bbdat| { - let term = match res { - FnResolution::Final(inst) => { - Cow::Owned(inst.subst_mir_and_normalize_erasing_regions( - self.tcx(), - ty::ParamEnv::reveal_all(), - ty::EarlyBinder::bind(bbdat.terminator().clone()), - )) - } - FnResolution::Partial(_) => Cow::Borrowed(bbdat.terminator()), - }; - self.terminator_carries_marker(&body.local_decls, term.as_ref()) - }) + if self.is_marked(res.def_id()) { + trace!(" Is marked"); + return Box::new([]); + } + let Some(body) = self.tcx().body_for_def_id_default_policy(local) else { + trace!(" Cannot find body"); + return Box::new([]); + }; + let mono_body = res.try_monomorphize( + self.tcx(), + self.tcx().param_env_reveal_all_normalized(local), + &body.body, + ); + if let Some((async_fn, _)) = determine_async(self.tcx(), local, &mono_body) { + return self.get_reachable_markers(async_fn).into(); + } + mono_body + .basic_blocks + .iter() + .flat_map(|bbdat| { + self.terminator_reachable_markers(&mono_body.local_decls, bbdat.terminator()) + }) + .collect::>() + .into_iter() + .collect() } /// Does this terminator carry a marker? - fn terminator_carries_marker( + fn terminator_reachable_markers( &self, local_decls: &mir::LocalDecls, terminator: &mir::Terminator<'tcx>, - ) -> bool { - if let Ok((res, _args, _)) = terminator.as_instance_and_args(self.tcx()) { - debug!( - "Checking function {} for markers", + ) -> impl Iterator + '_ { + trace!( + " Finding reachable markers for terminator {:?}", + terminator.kind + ); + let res = if let Ok((res, _, _)) = terminator.as_instance_and_args(self.tcx()) { + trace!( + " Checking function {} for markers", self.tcx().def_path_debug_str(res.def_id()) ); - if self.marker_is_reachable(res) { - return true; - } - if let ty::TyKind::Alias(ty::AliasKind::Opaque, alias) = + let transitive_reachable = self.get_reachable_and_self_markers(res).collect::>(); + trace!(" Found transitively reachable markers {transitive_reachable:?}"); + + // We have to proceed differently than graph construction, + // because we are not given the closure function, instead + // we are provided the id of the function that creates the + // future. As such we can't just monomorphize and traverse, + // we have to find the generator first. + let others = if let ty::TyKind::Alias(ty::AliasKind::Opaque, alias) = local_decls[mir::RETURN_PLACE].ty.kind() && let ty::TyKind::Generator(closure_fn, substs, _) = self.tcx().type_of(alias.def_id).skip_binder().kind() { - return self.marker_is_reachable( + trace!(" fits opaque type"); + Either::Left(self.get_reachable_and_self_markers( FnResolution::Final(ty::Instance::expect_resolve(self.tcx(), ty::ParamEnv::reveal_all(), *closure_fn, substs)) - ); - } - } - false + )) + } else { + Either::Right(std::iter::empty()) + }; + Either::Right(transitive_reachable.into_iter().chain(others)) + } else { + Either::Left(std::iter::empty()) + }.into_iter(); + trace!(" Done with {:?}", terminator.kind); + res } /// All the markers applied to this type and its subtypes. @@ -230,6 +289,95 @@ impl<'tcx> MarkerCtx<'tcx> { }) } + pub fn shallow_type_markers<'a>( + &'a self, + key: ty::Ty<'tcx>, + ) -> impl Iterator + 'a { + use ty::*; + let def_id = match key.kind() { + Adt(def, _) => Some(def.did()), + Alias(_, inner) => Some(inner.def_id), + _ => None, + }; + def_id + .map(|def_id| { + self.combined_markers(def_id) + .map(move |m| (def_id, m.marker)) + }) + .into_iter() + .flatten() + } + + pub fn deep_type_markers<'a>(&'a self, key: ty::Ty<'tcx>) -> &'a TypeMarkers { + self.0 + .type_markers + .get_maybe_recursive(key, |key| { + use ty::*; + let mut markers = self.shallow_type_markers(key).collect::>(); + match key.kind() { + Bool + | Char + | Int(_) + | Uint(_) + | Float(_) + | Foreign(_) + | Str + | FnDef { .. } + | FnPtr { .. } + | Closure { .. } + | Generator { .. } + | GeneratorWitness { .. } + | GeneratorWitnessMIR { .. } + | Never + | Bound { .. } + | Error(_) => (), + Adt(def, generics) => markers.extend(self.type_markers_for_adt(def, &generics)), + Tuple(tys) => { + markers.extend(tys.iter().flat_map(|ty| self.deep_type_markers(ty))) + } + Alias(_, _) => { + trace!("Alias type {key:?} remains. Was not normalized."); + return Box::new([]); + } + // We can't track indices so we simply overtaint to the entire array + Array(inner, _) | Slice(inner) => { + markers.extend(self.deep_type_markers(*inner)) + } + RawPtr(ty::TypeAndMut { ty, .. }) | Ref(_, ty, _) => { + markers.extend(self.deep_type_markers(*ty)) + } + Param(_) | Dynamic { .. } => self + .tcx() + .sess + .warn(format!("Cannot determine markers for type {key:?}")), + Placeholder(_) | Infer(_) => self + .tcx() + .sess + .fatal(format!("Did not expect this type here {key:?}")), + } + markers.as_slice().into() + }) + .map_or(&[], Box::as_ref) + } + + fn type_markers_for_adt<'a>( + &'a self, + adt: &'a ty::AdtDef<'tcx>, + generics: &'tcx ty::List>, + ) -> impl Iterator { + let tcx = self.tcx(); + adt.variants() + .iter_enumerated() + .flat_map(move |(_, vdef)| { + vdef.fields.iter_enumerated().flat_map(move |(_, fdef)| { + let f_ty = fdef.ty(tcx, generics); + self.deep_type_markers(f_ty) + }) + }) + .collect::>() + .into_iter() + } + pub fn type_has_surface_markers(&self, ty: ty::Ty) -> Option { let def_id = ty.defid()?; self.combined_markers(def_id) @@ -295,6 +443,10 @@ impl<'tcx> MarkerCtx<'tcx> { ) } } + +pub type TypeMarkerElem = (DefId, Identifier); +pub type TypeMarkers = [TypeMarkerElem]; + /// The structure inside of [`MarkerCtx`]. pub struct MarkerDatabase<'tcx> { tcx: TyCtxt<'tcx>, @@ -303,9 +455,10 @@ pub struct MarkerDatabase<'tcx> { local_annotations: HashMap>, external_annotations: ExternalMarkers, /// Cache whether markers are reachable transitively. - marker_reachable_cache: CopyCache, bool>, + reachable_markers: Cache, Box<[Identifier]>>, /// Configuration options config: &'static MarkerControl, + type_markers: Cache, Box>, } impl<'tcx> MarkerDatabase<'tcx> { @@ -315,8 +468,9 @@ impl<'tcx> MarkerDatabase<'tcx> { tcx, local_annotations: HashMap::default(), external_annotations: resolve_external_markers(args, tcx), - marker_reachable_cache: Default::default(), + reachable_markers: Default::default(), config: args.marker_control(), + type_markers: Default::default(), } } diff --git a/crates/paralegal-flow/src/test_utils.rs b/crates/paralegal-flow/src/test_utils.rs index b30222b6d5..f4cca52f5a 100644 --- a/crates/paralegal-flow/src/test_utils.rs +++ b/crates/paralegal-flow/src/test_utils.rs @@ -15,17 +15,15 @@ use std::process::Command; use paralegal_spdg::{ rustc_portable::DefId, traverse::{generic_flows_to, EdgeSelection}, - DefInfo, EdgeInfo, Node, NodeKind, SPDG, + DefInfo, EdgeInfo, Node, SPDG, }; +use flowistry_pdg::rustc_portable::LocalDefId; use flowistry_pdg::CallString; use itertools::Itertools; -use paralegal_spdg::rustc_portable::LocalDefId; -use petgraph::visit::IntoNeighbors; -use petgraph::visit::Visitable; -use petgraph::visit::{ - Control, Data, DfsEvent, EdgeRef, FilterEdge, GraphBase, IntoEdges, IntoNodeReferences, -}; +use petgraph::visit::{Control, Data, DfsEvent, EdgeRef, FilterEdge, GraphBase, IntoEdges}; +use petgraph::visit::{IntoNeighbors, IntoNodeReferences}; +use petgraph::visit::{NodeRef as _, Visitable}; use petgraph::Direction; use std::path::Path; @@ -306,12 +304,7 @@ impl<'g> HasGraph<'g> for &CtrlRef<'g> { impl<'g> CtrlRef<'g> { pub fn return_value(&self) -> NodeRefs { // TODO only include mutable formal parameters? - let nodes = self - .ctrl - .return_ - .as_ref() - .map_or(&[] as &[_], std::slice::from_ref) - .to_vec(); + let nodes = self.ctrl.return_.to_vec(); NodeRefs { nodes, graph: self } } @@ -377,7 +370,7 @@ impl<'g> CtrlRef<'g> { self.ctrl .type_assigns .get(&target) - .map_or(&[], |t| t.0.as_slice()) + .map_or(&[], |t| t.0.as_ref()) } } @@ -429,23 +422,13 @@ impl<'g> CallStringRef<'g> { // Alternative?? let mut nodes: Vec<_> = graph .edge_references() - .filter(|e| e.weight().at == self.call_site && e.weight().is_data()) - .map(|e| e.source()) + .filter(|e| e.weight().at == self.call_site) + .map(|e| (e.weight().source_use, e.source())) .collect(); - // let mut nodes: Vec<_> = graph - // .node_references() - // .filter(|(_n, weight)| weight.at == self.call_site) - // .filter_map(|(n, weight)| match weight.kind { - // NodeKind::ActualParameter(p) => Some((n, p)), - // _ => None, - // }) - // .flat_map(move |(src, idxes)| idxes.into_iter_set_in_domain().map(move |i| (src, i))) - // .collect(); - // nodes.sort_by_key(|s| s.1); nodes.sort(); nodes.dedup(); NodeRefs { - nodes, //.into_iter().map(|t| t.0).collect(), + nodes: nodes.into_iter().map(|t| t.1).collect(), graph: self.ctrl, } } @@ -454,15 +437,13 @@ impl<'g> CallStringRef<'g> { let graph = &self.ctrl.ctrl.graph; let mut nodes: Vec<_> = graph .edge_references() - .filter(|e| e.weight().at == self.call_site && e.weight().is_data()) + .filter(|e| e.weight().at == self.call_site) .map(|e| e.target()) .chain( graph .node_references() - .filter(|(_n, weight)| weight.at == self.call_site) - .filter_map(|(n, weight)| { - matches!(weight.kind, NodeKind::ActualReturn).then_some(n) - }), + .filter(|n| n.weight().at == self.call_site) + .map(|n| n.id()), ) .collect(); nodes.sort(); @@ -501,10 +482,7 @@ impl Debug for NodeRefs<'_> { let mut list = f.debug_list(); for &n in &self.nodes { let weight = self.graph.ctrl.graph.node_weight(n).unwrap(); - list.entry(&format!( - "{n:?} {} @ {} ({:?})", - weight.description, weight.at, weight.kind - )); + list.entry(&format!("{n:?} {} @ {} ", weight.description, weight.at)); } list.finish() } diff --git a/crates/paralegal-flow/src/utils/mod.rs b/crates/paralegal-flow/src/utils/mod.rs index 72020cc994..a85d05016a 100644 --- a/crates/paralegal-flow/src/utils/mod.rs +++ b/crates/paralegal-flow/src/utils/mod.rs @@ -1143,3 +1143,14 @@ impl<'tcx> Spanned<'tcx> for (LocalDefId, mir::Location) { (&body.body, self.1).span(tcx) } } + +pub fn map_either( + either: Either, + f: impl FnOnce(A) -> C, + g: impl FnOnce(B) -> D, +) -> Either { + match either { + Either::Left(l) => Either::Left(f(l)), + Either::Right(r) => Either::Right(g(r)), + } +} diff --git a/crates/paralegal-policy/src/context.rs b/crates/paralegal-policy/src/context.rs index bb366a5eaf..9a09224e0d 100644 --- a/crates/paralegal-policy/src/context.rs +++ b/crates/paralegal-policy/src/context.rs @@ -202,6 +202,7 @@ impl Context { .get(candidate) .ok_or_else(|| anyhow!("Impossible"))? .path + .as_ref() == path { return Ok(*candidate); @@ -401,7 +402,7 @@ impl Context { self.desc.controllers[&node.controller_id()] .type_assigns .get(&node.local_node()) - .map_or(&[], |v| v.0.as_slice()) + .map_or(&[], |v| v.0.as_ref()) } /// Returns whether the given Node has the marker applied to it directly or via its type. @@ -465,7 +466,7 @@ impl Context { self.desc() .type_info .get(&id) - .map_or(&[], |info| info.otypes.as_slice()) + .map_or(&[], |info| info.otypes.as_ref()) } /// Enforce that on every data flow path from the `starting_points` to `is_terminal` a @@ -662,7 +663,7 @@ impl<'a> std::fmt::Display for DisplayDef<'a> { let info = &self.ctx.desc().def_info[&self.def_id]; f.write_str(info.kind.as_ref())?; f.write_str(" `")?; - for segment in &info.path { + for segment in info.path.iter() { f.write_str(segment.as_str())?; f.write_str("::")?; } @@ -813,58 +814,6 @@ fn test_context() { ); } -#[test] -#[ignore = "Something is weird with the PDG construction here. - See https://github.com/willcrichton/flowistry/issues/95"] -fn test_happens_before() -> Result<()> { - use std::fs::File; - let ctx = crate::test_utils::test_ctx(); - - let start_marker = Identifier::new_intern("start"); - let bless_marker = Identifier::new_intern("bless"); - - let ctrl_name = ctx.controller_by_name(Identifier::new_intern("happens_before_pass"))?; - let ctrl = &ctx.desc.controllers[&ctrl_name]; - let f = File::create("graph.gv")?; - ctrl.dump_dot(f)?; - - let Some(ret) = ctrl.return_ else { - unreachable!("No return found") - }; - - let is_terminal = |end: GlobalNode| -> bool { - assert_eq!(end.controller_id(), ctrl_name); - ret == end.local_node() - }; - let start = ctx - .all_nodes_for_ctrl(ctrl_name) - .filter(|n| ctx.has_marker(start_marker, *n)) - .collect::>(); - - let pass = ctx.always_happens_before( - start, - |checkpoint| ctx.has_marker(bless_marker, checkpoint), - is_terminal, - )?; - - ensure!(pass.holds(), "{pass}"); - ensure!(!pass.is_vacuous(), "{pass}"); - - let ctrl_name = ctx.controller_by_name(Identifier::new_intern("happens_before_fail"))?; - - let fail = ctx.always_happens_before( - ctx.all_nodes_for_ctrl(ctrl_name) - .filter(|n| ctx.has_marker(start_marker, *n)), - |check| ctx.has_marker(bless_marker, check), - is_terminal, - )?; - - ensure!(!fail.holds()); - ensure!(!fail.is_vacuous()); - - Ok(()) -} - #[test] fn test_influencees() -> Result<()> { let ctx = crate::test_utils::test_ctx(); diff --git a/crates/paralegal-spdg/src/dot.rs b/crates/paralegal-spdg/src/dot.rs index e6c228f682..bf8f3ea37c 100644 --- a/crates/paralegal-spdg/src/dot.rs +++ b/crates/paralegal-spdg/src/dot.rs @@ -131,7 +131,7 @@ impl<'a, 'd> dot::Labeller<'a, CallString, GlobalEdge> for DotPrintableProgramDe for &n in nodes { let weight = ctrl.graph.node_weight(n).unwrap(); - let markers = ctrl.markers.get(&n).into_iter().flatten(); + let markers = ctrl.markers.get(&n).into_iter().flat_map(|b| b.iter()); let type_markers = ctrl .type_assigns .get(&n) diff --git a/crates/paralegal-spdg/src/lib.rs b/crates/paralegal-spdg/src/lib.rs index 5ba464d017..c21a6dd563 100644 --- a/crates/paralegal-spdg/src/lib.rs +++ b/crates/paralegal-spdg/src/lib.rs @@ -33,6 +33,7 @@ use itertools::Itertools; use rustc_portable::DefId; use serde::{Deserialize, Serialize}; use std::{fmt, hash::Hash, path::PathBuf}; +use utils::write_sep; use utils::serde_map_via_vec; @@ -85,6 +86,37 @@ mod ser_localdefid_map { } } +/// A marker annotation and its refinements. +#[derive(PartialEq, Eq, PartialOrd, Ord, Debug, Clone, Serialize, Deserialize)] +pub struct MarkerAnnotation { + /// The (unchanged) name of the marker as provided by the user + pub marker: Identifier, + pub on_return: bool, + pub on_argument: TinyBitSet, +} + +impl MarkerAnnotation { + /// Get the refinements on arguments + pub fn on_argument(&self, arg: u16) -> bool { + self.on_argument.contains(arg as u32).unwrap_or(false) + } + + /// Is this refinement targeting the return value? + pub fn on_return(&self) -> bool { + self.on_return + } + + /// True if this refinement is empty, i.e. the annotation is targeting the + /// item itself. + pub fn on_self(&self) -> bool { + self.on_argument.is_empty() && !self.on_return + } +} + +fn const_false() -> bool { + false +} + #[cfg(feature = "rustc")] mod ser_defid_map { use serde::{Deserialize, Serialize}; @@ -122,11 +154,34 @@ pub struct DefInfo { /// generated in the case of closures and generators pub name: Identifier, /// Def path to the object - pub path: Vec, + pub path: Box<[Identifier]>, /// Kind of object pub kind: DefKind, /// Information about the span pub src_info: Span, + /// Marker annotations on this item + pub markers: Box<[MarkerAnnotation]>, +} + +/// Provides a way to format rust paths +pub struct DisplayPath<'a>(&'a [Identifier]); + +impl Display for DisplayPath<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + write_sep(f, "::", self.0, Display::fmt) + } +} + +impl<'a> From<&'a [Identifier]> for DisplayPath<'a> { + fn from(value: &'a [Identifier]) -> Self { + Self(value) + } +} + +impl<'a> From<&'a Box<[Identifier]>> for DisplayPath<'a> { + fn from(value: &'a Box<[Identifier]>) -> Self { + value.as_ref().into() + } } /// Similar to `DefKind` in rustc but *not the same*! @@ -246,6 +301,8 @@ pub struct InstructionInfo { pub kind: InstructionKind, /// The source code span pub span: Span, + /// Textual rendering of the MIR + pub description: Identifier, } /// information about each encountered type. @@ -284,14 +341,14 @@ pub struct TypeDescription { /// How rustc would debug print this type pub rendering: String, /// Aliases - #[cfg_attr(feature = "rustc", serde(with = "ser_defid_vec"))] - pub otypes: Vec, + #[cfg_attr(feature = "rustc", serde(with = "ser_defid_seq"))] + pub otypes: Box<[TypeId]>, /// Attached markers. Guaranteed not to be empty. pub markers: Vec, } #[cfg(feature = "rustc")] -mod ser_defid_vec { +mod ser_defid_seq { use flowistry_pdg::rustc_proxies; use serde::{Deserialize, Deserializer, Serialize, Serializer}; @@ -299,18 +356,15 @@ mod ser_defid_vec { #[repr(transparent)] struct DefIdWrap(#[serde(with = "rustc_proxies::DefId")] crate::DefId); - pub fn serialize( - v: &Vec, - serializer: S, - ) -> Result { - unsafe { Vec::::serialize(std::mem::transmute(v), serializer) } + pub fn serialize(v: &[crate::DefId], serializer: S) -> Result { + unsafe { <[DefIdWrap]>::serialize(std::mem::transmute(v), serializer) } } pub fn deserialize<'de, D: Deserializer<'de>>( deserializer: D, - ) -> Result, D::Error> { + ) -> Result, D::Error> { unsafe { - Ok(std::mem::transmute(Vec::::deserialize( + Ok(std::mem::transmute(Box::<[DefIdWrap]>::deserialize( deserializer, )?)) } @@ -610,47 +664,13 @@ pub struct NodeInfo { pub at: CallString, /// The debug print of the `mir::Place` that this node represents pub description: String, - /// Additional information of how this node is used in the source. - pub kind: NodeKind, /// Span information for this node pub span: Span, } impl Display for NodeInfo { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - write!(f, "{} @ {} ({})", self.description, self.at, self.kind) - } -} - -/// Additional information about what a given node may represent -#[derive(Clone, Debug, Serialize, Deserialize, Copy, strum::EnumIs)] -pub enum NodeKind { - /// The node is (part of) a formal parameter of a function (0-indexed). e.g. - /// in `fn foo(x: usize)` `x` would be a `FormalParameter(0)`. - FormalParameter(u8), - /// Formal return of a function, e.g. `x` in `return x`; - FormalReturn, - /// Parameter given to a function at the call site, e.g. `x` in `foo(x)`. - ActualParameter(TinyBitSet), - /// Return value received from a call, e.g. `x` in `let x = foo(...);` - ActualReturn, - /// Any other kind of node - Unspecified, -} - -impl Display for NodeKind { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - match self { - NodeKind::FormalParameter(i) => { - write!(f, "Formal Parameter [{i}]") - } - NodeKind::FormalReturn => f.write_str("Formal Return"), - NodeKind::ActualParameter(p) => { - write!(f, "Actual Parameters {}", p.display_pretty()) - } - NodeKind::ActualReturn => f.write_str("Actual Return"), - NodeKind::Unspecified => f.write_str("Unspecified"), - } + write!(f, "{} @ {}", self.description, self.at) } } @@ -661,6 +681,11 @@ pub struct EdgeInfo { pub kind: EdgeKind, /// Where in the program this edge arises from pub at: CallString, + + /// Why the source of this edge is read + pub source_use: SourceUse, + /// Why the target of this edge is written + pub target_use: TargetUse, } impl Display for EdgeInfo { @@ -700,14 +725,19 @@ pub type SPDGImpl = petgraph::Graph; pub struct SPDG { /// The identifier of the entry point to this computation pub name: Identifier, + /// The module path to this controller function + pub path: Box<[Identifier]>, + /// The id + #[cfg_attr(feature = "rustc", serde(with = "rustc_proxies::LocalDefId"))] + pub id: LocalDefId, /// The PDG pub graph: SPDGImpl, /// Nodes to which markers are assigned. - pub markers: HashMap>, + pub markers: HashMap>, /// The nodes that represent arguments to the entrypoint - pub arguments: Vec, + pub arguments: Box<[Node]>, /// If the return is `()` or `!` then this is `None` - pub return_: Option, + pub return_: Box<[Node]>, /// Stores the assignment of relevant (e.g. marked) types to nodes. Node /// that this contains multiple types for a single node, because it hold /// top-level types and subtypes that may be marked. @@ -716,7 +746,7 @@ pub struct SPDG { /// Holds [`TypeId`]s that were assigned to a node. #[derive(Clone, Serialize, Deserialize, Debug, Default)] -pub struct Types(#[cfg_attr(feature = "rustc", serde(with = "ser_defid_vec"))] pub Vec); +pub struct Types(#[cfg_attr(feature = "rustc", serde(with = "ser_defid_seq"))] pub Box<[TypeId]>); impl SPDG { /// Retrieve metadata for this node @@ -786,9 +816,8 @@ impl<'a> Display for DisplayNode<'a> { if self.detailed { write!( f, - "{{{}}} ({}) {} @ {}", + "{{{}}} {} @ {}", self.node.index(), - weight.kind, weight.description, weight.at )