diff --git a/ethers-solc/src/resolver.rs b/ethers-solc/src/resolver.rs index 3d93eff3..c2db78e4 100644 --- a/ethers-solc/src/resolver.rs +++ b/ethers-solc/src/resolver.rs @@ -27,7 +27,7 @@ //! which is defined on a per source file basis. use std::{ - collections::{HashMap, VecDeque}, + collections::{HashMap, HashSet, VecDeque}, path::{Path, PathBuf}, }; @@ -72,6 +72,21 @@ impl Graph { &self.nodes[index] } + /// Returns an iterator that yields all nodes of the dependency tree that the given node id + /// spans, starting with the node itself. + /// + /// # Panics + /// + /// if the `start` node id is not included in the graph + pub fn node_ids(&self, start: usize) -> impl Iterator + '_ { + NodesIter::new(start, self) + } + + /// Same as `Self::node_ids` but returns the actual `Node` + pub fn nodes(&self, start: usize) -> impl Iterator + '_ { + self.node_ids(start).map(move |idx| self.node(idx)) + } + /// Returns all files together with their paths pub fn into_sources(self) -> Sources { self.nodes.into_iter().map(|node| (node.path, node.source)).collect() @@ -241,32 +256,18 @@ impl Graph { } /// Filters incompatible versions from the `candidates`. - fn retain_compatible_versions( - &self, - idx: usize, - candidates: &mut Vec<&crate::SolcVersion>, - traversed: &mut std::collections::HashSet<(usize, usize)>, - ) -> std::result::Result<(), String> { - let node = self.node(idx); - if let Some(ref req) = node.data.version_req { - candidates.retain(|v| req.matches(v.as_ref())); - } - for dep in self.imported_nodes(idx).iter().copied() { - // check for circular deps which would result in endless recursion SO here - // a circular dependency exists, if there was already a `dependency imports current - // node` relationship in the traversed path we skip this node - traversed.insert((idx, dep)); - if traversed.contains(&(dep, idx)) { - tracing::warn!( - "Detected cyclic imports {} <-> {}", - utils::source_name(&self.nodes[idx].path, &self.root).display(), - utils::source_name(&self.nodes[dep].path, &self.root).display() - ); - continue + fn retain_compatible_versions(&self, idx: usize, candidates: &mut Vec<&crate::SolcVersion>) { + let nodes: HashSet<_> = self.node_ids(idx).collect(); + for node in nodes { + let node = self.node(node); + if let Some(ref req) = node.data.version_req { + candidates.retain(|v| req.matches(v.as_ref())); + } + if candidates.is_empty() { + // nothing to filter anymore + return } - self.retain_compatible_versions(dep, candidates, traversed)?; } - Ok(()) } /// Ensures that all files are compatible with all of their imports. @@ -304,11 +305,7 @@ impl Graph { // walking through the node's dep tree and filtering the versions along the way for idx in 0..self.num_input_files { let mut candidates = all_versions.iter().collect::>(); - let mut traveresd = std::collections::HashSet::new(); - if let Err(msg) = self.retain_compatible_versions(idx, &mut candidates, &mut traveresd) - { - errors.push(msg); - } + self.retain_compatible_versions(idx, &mut candidates); if candidates.is_empty() && !erroneous_nodes.contains(&idx) { let mut msg = String::new(); @@ -344,6 +341,34 @@ impl Graph { } } +/// An iterator over a node and its dependencies +#[derive(Debug)] +pub struct NodesIter<'a> { + /// stack of nodes + stack: VecDeque, + visited: HashSet, + graph: &'a Graph, +} + +impl<'a> NodesIter<'a> { + fn new(start: usize, graph: &'a Graph) -> Self { + Self { stack: VecDeque::from([start]), visited: Default::default(), graph } + } +} + +impl<'a> Iterator for NodesIter<'a> { + type Item = usize; + fn next(&mut self) -> Option { + let node = self.stack.pop_front()?; + + if self.visited.insert(node) { + // push the node's direct dependencies to the stack if we haven't visited it already + self.stack.extend(self.graph.imported_nodes(node).iter().copied()); + } + Some(node) + } +} + /// Container type for solc versions and their compatible sources #[cfg(all(feature = "svm", feature = "async"))] #[derive(Debug)]