Skip to content

Commit f3168af

Browse files
committed
Merge #312: Fix of external SDK crate override bug
f2af871 Added DependencyMapBuilder for safe public API usage (Sdoba16) Pull request description: Closes #310 Previously, users could interact directly with the DependencyMap API in SimplicityHL, they could manually set the crate keyword to any path they want. This happened because the insert method allowed adding the crate alias without throwing any validation errors. Fix Was introduced `DependencyMapBuilder ` which collects dependencies and then could with build command it can be built. While building are made following validations: - entry source must be canonical and non-anonymous - every dependency target must be a canonical directory - every context prefix must be a canonical directory - alias must be a valid dependency identifier - alias must not be keyword - duplicate context+alias remappings rejected - overlapping crate roots are explicitly modeled - resolved files must remain inside their package root Remapping was made pub(crate) to hide from external API. `resolution.rs` was separated into 2 files `source.rs` and `resolution.rs` for readability. ACKs for top commit: KyrylR: ACK f2af871; successfully ran local tests, review Tree-SHA512: e68727b24fea181162386a8ea6e02eed263aa548762715501eafb9bdf604427c636adc608c3e248852d4dee5d998dffc664d24d543e1b1273fca7e621000338b
2 parents d596cb1 + f2af871 commit f3168af

9 files changed

Lines changed: 716 additions & 389 deletions

File tree

src/driver/mod.rs

Lines changed: 8 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -38,66 +38,20 @@ use chumsky::container::Container;
3838

3939
use crate::error::{Error, ErrorCollector, RichError, Span};
4040
use crate::parse::{self, ParseFromStrWithErrors};
41-
use crate::resolution::{CanonPath, DependencyMap, SourceFile};
41+
use crate::resolution::DependencyMap;
42+
use crate::source::{CanonPath, CanonSourceFile, SourceFile};
4243

4344
pub use crate::driver::resolve_order::{FileScoped, Program, SymbolTable};
4445

4546
/// The reserved identifier for the program's entry point.
4647
pub(crate) const MAIN_STR: &str = "main";
4748

4849
/// The reserved identifier for the local workspace root.
49-
pub const CRATE_STR: &str = "crate";
50+
pub(crate) const CRATE_STR: &str = "crate";
5051

5152
/// The root node index in the [`DependencyGraph`] representing the entry file.
5253
pub(crate) const MAIN_MODULE: usize = 0;
5354

54-
/// Caches the canonicalized path of a source file to prevent redundant,
55-
/// expensive, and potentially failing filesystem operations.
56-
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
57-
pub struct CanonSourceFile {
58-
/// The path of the source file (e.g., "./src/main.simf").
59-
name: CanonPath,
60-
/// The actual text content of the source file.
61-
content: Arc<str>,
62-
}
63-
64-
impl TryFrom<SourceFile> for CanonSourceFile {
65-
type Error = String;
66-
67-
fn try_from(source: SourceFile) -> Result<Self, Self::Error> {
68-
let name = if let Some(root_name) = source.name() {
69-
CanonPath::canonicalize(root_name)?
70-
} else {
71-
return Err(
72-
"Cannot canonicalize the SourceFile because it is missing a file name.".to_string(),
73-
);
74-
};
75-
76-
Ok(CanonSourceFile {
77-
name,
78-
content: source.content(),
79-
})
80-
}
81-
}
82-
83-
impl CanonSourceFile {
84-
pub fn new(name: CanonPath, content: Arc<str>) -> Self {
85-
Self { name, content }
86-
}
87-
88-
pub fn name(&self) -> &CanonPath {
89-
&self.name
90-
}
91-
92-
pub fn str_name(&self) -> String {
93-
self.name.as_path().display().to_string()
94-
}
95-
96-
pub fn content(&self) -> Arc<str> {
97-
self.content.clone()
98-
}
99-
}
100-
10155
/// Represents a single, isolated file in the SimplicityHL project.
10256
/// In this architecture, a file and a module are the exact same thing.
10357
#[derive(Debug, Clone)]
@@ -351,6 +305,7 @@ impl DependencyGraph {
351305
pub(crate) mod tests {
352306
use super::*;
353307
use crate::resolution::tests::canon;
308+
use crate::resolution::DependencyMapBuilder;
354309
use crate::test_utils::TempWorkspace;
355310

356311
/// Initializes a raw graph environment for testing, explicitly allowing for and capturing failure states.
@@ -389,15 +344,10 @@ pub(crate) mod tests {
389344
let lib_dir = canon(&ws.create_dir("workspace/libs/lib"));
390345

391346
// Set up the dependency map for imports (e.g. `use lib::...`)
392-
let mut map = DependencyMap::new();
393-
map.insert(workspace_dir.clone(), "lib".to_string(), lib_dir.clone())
394-
.expect("Failed to insert dependency map");
395-
396-
// Register the strict crate boundaries so local files are forced to use `crate::`
397-
map.insert(workspace_dir.clone(), CRATE_STR.to_string(), workspace_dir)
398-
.expect("Failed to insert workspace crate boundary");
399-
map.insert(lib_dir.clone(), CRATE_STR.to_string(), lib_dir)
400-
.expect("Failed to insert library crate boundary");
347+
let map = DependencyMapBuilder::new(workspace_dir.clone())
348+
.add_dependency(workspace_dir.clone(), "lib".to_string(), lib_dir.clone())
349+
.build()
350+
.expect("Failed to create dependency map");
401351
let map = Arc::new(map);
402352

403353
let mut root_file_path = None;

src/error.rs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use simplicity::elements;
1515

1616
use crate::lexer::Token;
1717
use crate::parse::MatchPattern;
18-
use crate::resolution::SourceFile;
18+
use crate::source::SourceFile;
1919
use crate::str::{AliasName, FunctionName, Identifier, JetName, ModuleName, WitnessName};
2020
use crate::types::{ResolvedType, UIntType};
2121

@@ -474,6 +474,11 @@ impl fmt::Display for ErrorCollector {
474474
/// Records _what_ happened but not where.
475475
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
476476
pub enum Error {
477+
DependencyPathNotFound(String),
478+
DependencyNotADirectory(String),
479+
ReservedDependencyKeyword(String),
480+
DuplicateDependencyAlias(String, String),
481+
InvalidDependencyIdentifier(String),
477482
Internal(String),
478483
UnknownLibrary(String),
479484
ArraySizeNonZero(usize),
@@ -533,6 +538,11 @@ pub enum Error {
533538
impl fmt::Display for Error {
534539
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
535540
match self {
541+
Error::DependencyPathNotFound(path) => write!(f, "Path not found: {}", path),
542+
Error::DependencyNotADirectory(path) => write!(f, "Path must be a directory: {}", path),
543+
Error::ReservedDependencyKeyword(kw) => write!(f, "The '{}' keyword is reserved and cannot be manually mapped. Use the builder's context definitions instead.", kw),
544+
Error::DuplicateDependencyAlias(alias, context) => write!(f, "Duplicate dependency mapping: alias '{}' is defined multiple times for context '{}'", alias, context),
545+
Error::InvalidDependencyIdentifier(alias) => write!(f, "Invalid dependency alias '{}': must be a valid identifier and not a reserved keyword", alias),
536546
Error::Internal(err) => write!(
537547
f,
538548
"INTERNAL ERROR: {err}"

src/lexer.rs

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -255,24 +255,14 @@ pub fn lex<'src>(input: &'src str) -> (Option<Tokens<'src>>, Vec<crate::error::R
255255
)
256256
}
257257

258+
/// A list of all reserved keywords.
259+
pub const KEYWORDS: &[&str] = &[
260+
"pub", "use", "as", "fn", "let", "type", "mod", "const", "match", CRATE_STR, "true", "false",
261+
];
262+
258263
/// Checks whether a given string is a keyword.
259-
#[cfg(feature = "arbitrary")]
260264
pub fn is_keyword(s: &str) -> bool {
261-
matches!(
262-
s,
263-
"pub"
264-
| "use"
265-
| "as"
266-
| "fn"
267-
| "let"
268-
| "type"
269-
| "mod"
270-
| "const"
271-
| "match"
272-
| CRATE_STR
273-
| "true"
274-
| "false"
275-
)
265+
KEYWORDS.contains(&s)
276266
}
277267

278268
#[cfg(test)]

src/lib.rs

Lines changed: 39 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ pub mod num;
1616
pub mod parse;
1717
pub mod pattern;
1818
pub mod resolution;
19+
pub mod source;
20+
1921
#[cfg(feature = "serde")]
2022
mod serde;
2123
pub mod str;
@@ -39,7 +41,8 @@ use crate::debug::DebugSymbols;
3941
use crate::driver::DependencyGraph;
4042
use crate::error::{ErrorCollector, WithContent, WithSource as _};
4143
use crate::parse::ParseFromStrWithErrors;
42-
use crate::resolution::{DependencyMap, SourceFile};
44+
use crate::resolution::DependencyMap;
45+
use crate::source::SourceFile;
4346
pub use crate::types::ResolvedType;
4447
pub use crate::value::Value;
4548
pub use crate::witness::{Arguments, Parameters, WitnessTypes, WitnessValues};
@@ -72,23 +75,18 @@ impl TemplateProgram {
7275
.ok_or_else(|| error_handler.to_string())?;
7376

7477
// 2. Create the driver program
75-
let driver_program: driver::Program = if dependency_map.is_empty() {
76-
driver::Program::from_parse(&parsed_program, source.content(), &mut error_handler)
77-
.ok_or_else(|| error_handler.to_string())?
78-
} else {
79-
let graph = DependencyGraph::new(
80-
source.clone(),
81-
Arc::from(dependency_map.clone()),
82-
&parsed_program,
83-
&mut error_handler,
84-
)?
78+
let graph = DependencyGraph::new(
79+
source.clone(),
80+
Arc::from(dependency_map.clone()),
81+
&parsed_program,
82+
&mut error_handler,
83+
)?
84+
.ok_or_else(|| error_handler.to_string())?;
85+
86+
let driver_program: driver::Program = graph
87+
.linearize_and_build(&mut error_handler)?
8588
.ok_or_else(|| error_handler.to_string())?;
8689

87-
graph
88-
.linearize_and_build(&mut error_handler)?
89-
.ok_or_else(|| error_handler.to_string())?
90-
};
91-
9290
// 3. AST Analysis
9391
let ast_program = ast::Program::analyze(&driver_program).with_source(source.clone())?;
9492
Ok(Self {
@@ -399,6 +397,10 @@ pub trait ArbitraryOfType: Sized {
399397
#[cfg(test)]
400398
pub(crate) mod tests {
401399
use crate::parse::ParseFromStr;
400+
use crate::resolution::tests::canon;
401+
use crate::resolution::DependencyMapBuilder;
402+
use crate::source::CanonPath;
403+
use crate::test_utils::TempWorkspace;
402404
use base64::display::Base64Display;
403405
use base64::engine::general_purpose::STANDARD;
404406
use simplicity::BitMachine;
@@ -494,33 +496,19 @@ pub(crate) mod tests {
494496
I: IntoIterator<Item = (P, K, P)>,
495497
K: Into<String>,
496498
{
497-
let mut dependency_map = DependencyMap::new();
498-
499-
if let Some(parent) = prog_path.as_ref().parent() {
500-
let canon_root = crate::resolution::tests::canon(parent);
501-
let _ = dependency_map.insert(
502-
canon_root.clone(),
503-
crate::driver::CRATE_STR.to_string(),
504-
canon_root,
505-
);
506-
}
499+
let parent = prog_path.as_ref().parent().unwrap();
500+
let canon_root = canon(parent);
501+
let mut builder = DependencyMapBuilder::new(canon_root);
507502

508503
for (context, alias, target) in dependencies {
509-
let context = crate::resolution::tests::canon(context.as_ref());
510-
let target = crate::resolution::tests::canon(target.as_ref());
511-
512-
dependency_map
513-
.insert(context.clone(), alias.into(), target.clone())
514-
.unwrap();
515-
516-
// Treat each mapped dependency as an isolated external package to satisfy strict local-file checks
517-
let _ = dependency_map.insert(
518-
target.clone(),
519-
crate::driver::CRATE_STR.to_string(),
520-
target,
521-
);
504+
let context = canon(context.as_ref());
505+
let target = canon(target.as_ref());
506+
507+
builder = builder.add_dependency(context, alias.into(), target);
522508
}
523509

510+
let dependency_map = builder.build().unwrap();
511+
524512
TestCase::<TemplateProgram>::template_deps(prog_path.as_ref(), &dependency_map)
525513
.with_arguments(Arguments::default())
526514
}
@@ -727,9 +715,6 @@ pub(crate) mod tests {
727715

728716
#[test]
729717
fn test_crate_keyword_compilation_success() {
730-
use crate::resolution::{CanonPath, DependencyMap};
731-
use crate::test_utils::TempWorkspace;
732-
733718
let ws = TempWorkspace::new("crate_success");
734719
let root = ws.create_dir("workspace");
735720
ws.create_file(
@@ -742,15 +727,9 @@ pub(crate) mod tests {
742727
);
743728

744729
let main_path = root.join("main.simf");
745-
let mut dependency_map = DependencyMap::new();
746730
let canon_root = CanonPath::canonicalize(&root).unwrap();
747-
dependency_map
748-
.insert(
749-
canon_root.clone(),
750-
crate::driver::CRATE_STR.to_string(),
751-
canon_root,
752-
)
753-
.unwrap();
731+
732+
let dependency_map = DependencyMapBuilder::new(canon_root).build().unwrap();
754733

755734
TestCase::<TemplateProgram>::template_deps(&main_path, &dependency_map)
756735
.with_arguments(Arguments::default())
@@ -1164,18 +1143,18 @@ mod error_tests {
11641143
use super::*;
11651144

11661145
use crate::resolution::tests::canon;
1167-
use crate::resolution::CanonPath;
1146+
use crate::resolution::DependencyMapBuilder;
1147+
use crate::source::CanonPath;
11681148
use crate::test_utils::TempWorkspace;
11691149

11701150
fn dependency_map(root_dir: &Path, drp: &str, lib_dir: &Path) -> DependencyMap {
1171-
let mut dependency_map = DependencyMap::new();
1172-
11731151
let context = CanonPath::canonicalize(root_dir).unwrap();
11741152
let target = CanonPath::canonicalize(lib_dir).unwrap();
11751153

1176-
dependency_map.insert(context, drp.into(), target).unwrap();
1177-
1178-
dependency_map
1154+
DependencyMapBuilder::new(context.clone())
1155+
.add_dependency(context, drp.into(), target)
1156+
.build()
1157+
.unwrap()
11791158
}
11801159

11811160
fn source_file(path: &Path) -> SourceFile {
@@ -1213,6 +1192,7 @@ mod error_tests {
12131192
#[test]
12141193
fn omitted_context_dependency_applies_inside_dependency_files() {
12151194
let ws = TempWorkspace::new("omitted_context_dependency");
1195+
let root_dir = ws.create_dir("workspace");
12161196
let lib_dir = ws.create_dir("workspace/lib");
12171197
let main_path = ws.create_file(
12181198
"workspace/main.simf",
@@ -1224,7 +1204,7 @@ mod error_tests {
12241204
);
12251205
ws.create_file("workspace/lib/base.simf", "pub fn one() -> u32 { 1 }\n");
12261206

1227-
let dependencies = dependency_map(&main_path, "lib", &lib_dir);
1207+
let dependencies = dependency_map(&root_dir, "lib", &lib_dir);
12281208
let _err = TemplateProgram::new_with_dep(source_file(&main_path), &dependencies)
12291209
.expect_err("omitted-context dependencies");
12301210
}
@@ -1335,7 +1315,7 @@ mod functional_tests {
13351315
}
13361316

13371317
#[test]
1338-
#[should_panic(expected = "not found")]
1318+
#[should_panic(expected = "DependencyPathNotFound")]
13391319
fn file_not_found_error() {
13401320
run_dependency_test(
13411321
format!("{}/file-not-found", ERROR_TESTS_DIR).as_str(),
@@ -1344,7 +1324,7 @@ mod functional_tests {
13441324
}
13451325

13461326
#[test]
1347-
#[should_panic(expected = "not found")]
1327+
#[should_panic(expected = "DependencyPathNotFound")]
13481328
fn lib_not_found_error() {
13491329
run_dependency_test(format!("{}/lib-not-found", ERROR_TESTS_DIR).as_str(), "lib");
13501330
}

0 commit comments

Comments
 (0)