Эх сурвалжийг харах

Track `import_no` in `File` (#1419)

This PR:
1. Adds a `resolved_files: Vec<ResolvedFile>` field to `FileResolver`;
2. Populates it during the execution of `FileResolver.resolve_file()`;
3. Extends `FileResolver`'s API with `get_resolved_file(file_no: usize)`
to allow access of this data.
 
Prior to this PR, the resolution root of each file is discarded after
`parse_and_resolve` and has to be recomputed by a client (and this
recomputation depends on implementation-specifics such as source root
visit order during file resolution). This PR persists this data.

Signed-off-by: Ben Kushigian <ben.kushigian@certora.com>
Co-authored-by: Sean Young <sean@mess.org>
BenTheKush 2 жил өмнө
parent
commit
15cc63e712

+ 20 - 0
src/file_resolver.rs

@@ -33,6 +33,13 @@ pub struct ResolvedFile {
     base: PathBuf,
 }
 
+impl ResolvedFile {
+    /// Get the import number associated with this `ResolvedFile`
+    pub fn get_import_no(&self) -> usize {
+        self.import_no
+    }
+}
+
 impl Default for FileResolver {
     fn default() -> Self {
         FileResolver::new()
@@ -72,6 +79,19 @@ impl FileResolver {
         }
     }
 
+    /// Get the import path and the optional mapping corresponding to `import_no`.
+    pub fn get_import_path(&self, import_no: usize) -> Option<&(Option<OsString>, PathBuf)> {
+        self.import_paths.get(import_no)
+    }
+
+    /// Get the import path corresponding to a map
+    pub fn get_import_map(&self, map: &OsString) -> Option<&PathBuf> {
+        self.import_paths
+            .iter()
+            .find(|(m, _)| m.as_ref() == Some(map))
+            .map(|(_, pb)| pb)
+    }
+
     /// Update the cache for the filename with the given contents
     pub fn set_file_contents(&mut self, path: &str, contents: String) {
         let pos = self.files.len();

+ 3 - 0
src/sema/ast.rs

@@ -658,6 +658,9 @@ pub struct File {
     pub line_starts: Vec<usize>,
     /// Indicates the file number in FileResolver.files
     pub cache_no: Option<usize>,
+    /// Index into FileResolver.import_paths. This is `None` when this File was
+    /// created not during `parse_and_resolve` (e.g., builtins)
+    pub import_no: Option<usize>,
 }
 
 /// When resolving a Solidity file, this holds all the resolved items

+ 2 - 0
src/sema/builtin.rs

@@ -1464,6 +1464,7 @@ impl Namespace {
             path: PathBuf::from("solana"),
             line_starts: Vec::new(),
             cache_no: None,
+            import_no: None,
         });
 
         let id = pt::Identifier {
@@ -1648,6 +1649,7 @@ impl Namespace {
             path: PathBuf::from("polkadot"),
             line_starts: Vec::new(),
             cache_no: None,
+            import_no: None,
         });
 
         // The Hash type from ink primitives.

+ 7 - 1
src/sema/file.rs

@@ -11,7 +11,12 @@ pub enum PathDisplay {
 }
 
 impl File {
-    pub fn new(path: path::PathBuf, contents: &str, cache_no: usize) -> Self {
+    pub fn new(
+        path: path::PathBuf,
+        contents: &str,
+        cache_no: usize,
+        import_no: Option<usize>,
+    ) -> Self {
         let mut line_starts = Vec::new();
 
         for (ind, c) in contents.char_indices() {
@@ -24,6 +29,7 @@ impl File {
             path,
             line_starts,
             cache_no: Some(cache_no),
+            import_no,
         }
     }
 

+ 1 - 0
src/sema/mod.rs

@@ -99,6 +99,7 @@ fn sema_file(file: &ResolvedFile, resolver: &mut FileResolver, ns: &mut ast::Nam
         file.full_path.clone(),
         &source_code,
         file_cache_no,
+        Some(file.get_import_no()),
     ));
 
     let (pt, comments) = match parse(&source_code, file_no) {

+ 50 - 1
src/sema/tests/mod.rs

@@ -5,7 +5,8 @@ use crate::sema::ast::{Expression, Parameter, Statement, TryCatch, Type};
 use crate::sema::yul::ast::InlineAssembly;
 use crate::{parse_and_resolve, sema::ast, FileResolver, Target};
 use solang_parser::pt::Loc;
-use std::ffi::OsStr;
+use std::ffi::{OsStr, OsString};
+use std::path::PathBuf;
 
 pub(crate) fn parse(src: &'static str) -> ast::Namespace {
     let mut cache = FileResolver::new();
@@ -622,3 +623,51 @@ contract Child {
         "either 'address' or 'accounts' call argument is required on Solana"
     );
 }
+
+#[test]
+fn get_import_map() {
+    let mut cache = FileResolver::new();
+    let map = OsString::from("@openzepellin");
+    let example_sol_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+        .join("examples")
+        .canonicalize()
+        .unwrap();
+
+    assert!(cache
+        .add_import_map(map.clone(), example_sol_path.clone())
+        .is_ok());
+
+    let retrieved = cache.get_import_map(&map);
+    assert_eq!(Some(&example_sol_path), retrieved);
+}
+
+#[test]
+fn get_import_path() {
+    let mut cache = FileResolver::new();
+    let examples = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
+        .join("examples")
+        .canonicalize()
+        .unwrap();
+
+    let bad_path = PathBuf::from("/IDontExist.sol");
+
+    assert!(cache.add_import_path(&examples).is_ok());
+    assert!(cache.add_import_path(&bad_path).is_err());
+
+    let ns = parse_and_resolve(OsStr::new("example.sol"), &mut cache, Target::EVM);
+
+    let file = ns.files.get(0);
+    assert!(file.is_some());
+    if let Some(file) = file {
+        let import_path = cache.get_import_path(file.import_no.unwrap());
+        assert_eq!(Some(&(None, examples.clone())), import_path);
+    }
+
+    let ns = parse_and_resolve(OsStr::new("incrementer.sol"), &mut cache, Target::EVM);
+    let file = ns.files.get(0);
+    assert!(file.is_some());
+    if let Some(file) = file {
+        let import_path = cache.get_import_path(file.import_no.unwrap());
+        assert_eq!(Some(&(None, examples.clone())), import_path);
+    }
+}