fix(solc): traverse nodes iteratively (#800)

This commit is contained in:
Matthias Seitz 2022-01-17 13:05:52 +01:00 committed by GitHub
parent a30a4ecd16
commit 579311bfdd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 55 additions and 30 deletions

View File

@ -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<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
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);
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()));
}
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
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::<Vec<_>>();
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<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
#[cfg(all(feature = "svm", feature = "async"))]
#[derive(Debug)]