fix(solc): traverse nodes iteratively (#800)
This commit is contained in:
parent
a30a4ecd16
commit
579311bfdd
|
@ -27,7 +27,7 @@
|
||||||
//! which is defined on a per source file basis.
|
//! which is defined on a per source file basis.
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashMap, VecDeque},
|
collections::{HashMap, HashSet, VecDeque},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -72,6 +72,21 @@ impl Graph {
|
||||||
&self.nodes[index]
|
&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<Item = usize> + '_ {
|
||||||
|
NodesIter::new(start, self)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Same as `Self::node_ids` but returns the actual `Node`
|
||||||
|
pub fn nodes(&self, start: usize) -> impl Iterator<Item = &Node> + '_ {
|
||||||
|
self.node_ids(start).map(move |idx| self.node(idx))
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns all files together with their paths
|
/// Returns all files together with their paths
|
||||||
pub fn into_sources(self) -> Sources {
|
pub fn into_sources(self) -> Sources {
|
||||||
self.nodes.into_iter().map(|node| (node.path, node.source)).collect()
|
self.nodes.into_iter().map(|node| (node.path, node.source)).collect()
|
||||||
|
@ -241,32 +256,18 @@ impl Graph {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Filters incompatible versions from the `candidates`.
|
/// Filters incompatible versions from the `candidates`.
|
||||||
fn retain_compatible_versions(
|
fn retain_compatible_versions(&self, idx: usize, candidates: &mut Vec<&crate::SolcVersion>) {
|
||||||
&self,
|
let nodes: HashSet<_> = self.node_ids(idx).collect();
|
||||||
idx: usize,
|
for node in nodes {
|
||||||
candidates: &mut Vec<&crate::SolcVersion>,
|
let node = self.node(node);
|
||||||
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 {
|
if let Some(ref req) = node.data.version_req {
|
||||||
candidates.retain(|v| req.matches(v.as_ref()));
|
candidates.retain(|v| req.matches(v.as_ref()));
|
||||||
}
|
}
|
||||||
for dep in self.imported_nodes(idx).iter().copied() {
|
if candidates.is_empty() {
|
||||||
// check for circular deps which would result in endless recursion SO here
|
// nothing to filter anymore
|
||||||
// a circular dependency exists, if there was already a `dependency imports current
|
return
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
self.retain_compatible_versions(dep, candidates, traversed)?;
|
|
||||||
}
|
}
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Ensures that all files are compatible with all of their imports.
|
/// 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
|
// walking through the node's dep tree and filtering the versions along the way
|
||||||
for idx in 0..self.num_input_files {
|
for idx in 0..self.num_input_files {
|
||||||
let mut candidates = all_versions.iter().collect::<Vec<_>>();
|
let mut candidates = all_versions.iter().collect::<Vec<_>>();
|
||||||
let mut traveresd = std::collections::HashSet::new();
|
self.retain_compatible_versions(idx, &mut candidates);
|
||||||
if let Err(msg) = self.retain_compatible_versions(idx, &mut candidates, &mut traveresd)
|
|
||||||
{
|
|
||||||
errors.push(msg);
|
|
||||||
}
|
|
||||||
|
|
||||||
if candidates.is_empty() && !erroneous_nodes.contains(&idx) {
|
if candidates.is_empty() && !erroneous_nodes.contains(&idx) {
|
||||||
let mut msg = String::new();
|
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<usize>,
|
||||||
|
visited: HashSet<usize>,
|
||||||
|
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<Self::Item> {
|
||||||
|
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
|
/// Container type for solc versions and their compatible sources
|
||||||
#[cfg(all(feature = "svm", feature = "async"))]
|
#[cfg(all(feature = "svm", feature = "async"))]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
|
Loading…
Reference in New Issue