From e15e8f05d6500866c3e029ae5e287b40bafdcd6b Mon Sep 17 00:00:00 2001 From: Pranav Gaddamadugu Date: Wed, 5 Oct 2022 14:59:06 -0700 Subject: [PATCH] Additional validation for program scope and imported files --- compiler/ast/src/passes/reconstructor.rs | 1 + compiler/ast/src/program/program_scope.rs | 3 ++ compiler/compiler/src/compiler.rs | 19 ++++++++++- compiler/compiler/src/test.rs | 1 + compiler/parser/src/parser/file.rs | 34 +++++++++++++++---- .../rename_program.rs | 1 + errors/src/errors/compiler/compiler_errors.rs | 7 ++++ errors/src/errors/package/package_errors.rs | 2 +- errors/src/errors/parser/parser_errors.rs | 21 ++++++++++++ leo/commands/build.rs | 10 ++++-- leo/package/src/source/directory.rs | 3 +- tests/test-framework/benches/leo_compiler.rs | 1 + 12 files changed, 91 insertions(+), 12 deletions(-) diff --git a/compiler/ast/src/passes/reconstructor.rs b/compiler/ast/src/passes/reconstructor.rs index fe5f513af2..9ff2f89407 100644 --- a/compiler/ast/src/passes/reconstructor.rs +++ b/compiler/ast/src/passes/reconstructor.rs @@ -344,6 +344,7 @@ pub trait ProgramReconstructor: StatementReconstructor { .into_iter() .map(|(i, f)| (i, self.reconstruct_function(f))) .collect(), + span: input.span, } } diff --git a/compiler/ast/src/program/program_scope.rs b/compiler/ast/src/program/program_scope.rs index 93d5ab9def..00001bfe17 100644 --- a/compiler/ast/src/program/program_scope.rs +++ b/compiler/ast/src/program/program_scope.rs @@ -19,6 +19,7 @@ use crate::{Function, Identifier, Mapping, Struct}; use indexmap::IndexMap; +use leo_span::Span; use serde::{Deserialize, Serialize}; use std::fmt; @@ -35,6 +36,8 @@ pub struct ProgramScope { pub mappings: IndexMap, /// A map from function names to function definitions. pub functions: IndexMap, + /// The span associated with the program scope. + pub span: Span, } impl fmt::Display for ProgramScope { diff --git a/compiler/compiler/src/compiler.rs b/compiler/compiler/src/compiler.rs index 542c7042c4..94ec93fcaa 100644 --- a/compiler/compiler/src/compiler.rs +++ b/compiler/compiler/src/compiler.rs @@ -51,6 +51,8 @@ pub struct Compiler<'a> { pub input_ast: Option, /// Compiler options on some optional output files. output_options: OutputOptions, + /// Whether or not we are compiling an imported program. + is_import: bool, } impl<'a> Compiler<'a> { @@ -62,6 +64,7 @@ impl<'a> Compiler<'a> { main_file_path: PathBuf, output_directory: PathBuf, output_options: Option, + is_import: bool, ) -> Self { Self { handler, @@ -72,6 +75,7 @@ impl<'a> Compiler<'a> { ast: Ast::new(Program::default()), input_ast: None, output_options: output_options.unwrap_or_default(), + is_import, } } @@ -97,7 +101,20 @@ impl<'a> Compiler<'a> { // Use the parser to construct the abstract syntax tree (ast). self.ast = leo_parser::parse_ast(self.handler, &prg_sf.src, prg_sf.start_pos)?; - // TODO: Check that the program name matches the file name. + // If the program is imported, then check that the name of its program scope matches the file name. + // Note that parsing enforces that there is exactly one program scope in a file. + // TODO: Clean up check. + let program_scope = self.ast.ast.program_scopes.values().next().unwrap(); + let program_scope_name = + with_session_globals(|s| program_scope.name.name.as_str(s, |string| string.to_string())); + if self.is_import && program_scope_name != self.program_name { + return Err(CompilerError::imported_program_name_does_not_match_filename( + program_scope_name, + self.program_name.clone(), + program_scope.name.span, + ) + .into()); + } if self.output_options.initial_ast { self.write_ast_to_json("initial_ast.json")?; diff --git a/compiler/compiler/src/test.rs b/compiler/compiler/src/test.rs index fa3caacba1..8a99b9d4c4 100644 --- a/compiler/compiler/src/test.rs +++ b/compiler/compiler/src/test.rs @@ -60,6 +60,7 @@ fn new_compiler(handler: &Handler, main_file_path: PathBuf) -> Compiler<'_> { ssa_ast: true, flattened_ast: true, }), + false, ) } diff --git a/compiler/parser/src/parser/file.rs b/compiler/parser/src/parser/file.rs index f5474a695f..0d808fa8d5 100644 --- a/compiler/parser/src/parser/file.rs +++ b/compiler/parser/src/parser/file.rs @@ -28,7 +28,9 @@ impl ParserContext<'_> { let mut imports = IndexMap::new(); let mut program_scopes = IndexMap::new(); - // TODO: Condense logic + // TODO: Remove restrictions on multiple program scopes + let mut parsed_program_scope = false; + while self.has_next() { match &self.token.token { Token::Import => { @@ -36,12 +38,25 @@ impl ParserContext<'_> { imports.insert(id, import); } Token::Program => { - let program_scope = self.parse_program_scope()?; - program_scopes.insert((program_scope.name, program_scope.network), program_scope); + match parsed_program_scope { + // Only one program scope is allowed per file. + true => return Err(ParserError::only_one_program_scope_is_allowed(self.token.span).into()), + false => { + parsed_program_scope = true; + let program_scope = self.parse_program_scope()?; + program_scopes.insert((program_scope.name, program_scope.network), program_scope); + } + } } _ => return Err(Self::unexpected_item(&self.token).into()), } } + + // Requires that at least one program scope is present. + if !parsed_program_scope { + return Err(ParserError::missing_program_scope(self.token.span).into()); + } + Ok(Program { imports, program_scopes, @@ -109,18 +124,22 @@ impl ParserContext<'_> { /// Parsers a program scope `program foo.aleo { ... }`. fn parse_program_scope(&mut self) -> Result { // Parse `program` keyword. - let _start = self.expect(&Token::Program)?; + let start = self.expect(&Token::Program)?; // Parse the program name. let name = self.expect_identifier()?; // Parse the program network. self.expect(&Token::Dot)?; - let network = self.expect_identifier()?; + // Check that the program network is valid. + if network.name != sym::aleo { + return Err(ParserError::invalid_network(network.span).into()); + } + // Parse `{`. - let _open = self.expect(&Token::LeftCurly)?; + self.expect(&Token::LeftCurly)?; // Parse the body of the program scope. let mut functions = IndexMap::new(); @@ -148,7 +167,7 @@ impl ParserContext<'_> { } // Parse `}`. - let _close = self.expect(&Token::RightCurly)?; + let end = self.expect(&Token::RightCurly)?; Ok(ProgramScope { name, @@ -156,6 +175,7 @@ impl ParserContext<'_> { functions, structs, mappings, + span: start + end, }) } diff --git a/compiler/passes/src/static_single_assignment/rename_program.rs b/compiler/passes/src/static_single_assignment/rename_program.rs index 01446eae3a..83f414894c 100644 --- a/compiler/passes/src/static_single_assignment/rename_program.rs +++ b/compiler/passes/src/static_single_assignment/rename_program.rs @@ -101,6 +101,7 @@ impl ProgramScopeConsumer for StaticSingleAssigner { .into_iter() .map(|(i, f)| (i, self.consume_function(f))) .collect(), + span: input.span, } } } diff --git a/errors/src/errors/compiler/compiler_errors.rs b/errors/src/errors/compiler/compiler_errors.rs index d40f60b682..a3a2d2a64c 100644 --- a/errors/src/errors/compiler/compiler_errors.rs +++ b/errors/src/errors/compiler/compiler_errors.rs @@ -63,4 +63,11 @@ create_messages!( msg: format!("Program name `{program_name}` should match file name `{file_name}`"), help: None, } + + @formatted + imported_program_name_does_not_match_filename { + args: (program_scope_name: impl Display, file_name: impl Display), + msg: format!("The program scope `{program_scope_name}` does not match its filename `{file_name}`."), + help: None, + } ); diff --git a/errors/src/errors/package/package_errors.rs b/errors/src/errors/package/package_errors.rs index d174440eed..5306589a81 100644 --- a/errors/src/errors/package/package_errors.rs +++ b/errors/src/errors/package/package_errors.rs @@ -302,7 +302,7 @@ create_messages!( @backtraced source_directory_can_contain_only_one_file { args: (), - msg: "The `src/` directory can contain only one file.".to_string(), + msg: "The `src/` directory can contain only one file and must be named `main.leo`.".to_string(), help: None, } ); diff --git a/errors/src/errors/parser/parser_errors.rs b/errors/src/errors/parser/parser_errors.rs index 1d7ec51bb7..45b0a13192 100644 --- a/errors/src/errors/parser/parser_errors.rs +++ b/errors/src/errors/parser/parser_errors.rs @@ -250,4 +250,25 @@ create_messages!( msg: "The keyword `circuit` is deprecated.", help: Some("Use `struct` instead.".to_string()), } + + @formatted + only_one_program_scope_is_allowed { + args: (), + msg: "Only one program scope is allowed in a Leo file.", + help: None, + } + + @formatted + missing_program_scope { + args: (), + msg: "Missing a program scope in a Leo file.", + help: Some("Add a program scope of the form: `program .aleo { ... }` to the Leo file.".to_string()), + } + + @formatted + invalid_network { + args: (), + msg: "Invalid network identifier. The only supported identifier is `aleo`.", + help: None, + } ); diff --git a/leo/commands/build.rs b/leo/commands/build.rs index 14c85f9cce..fb4d125d91 100644 --- a/leo/commands/build.rs +++ b/leo/commands/build.rs @@ -137,6 +137,7 @@ impl Command for Build { &build_directory, &handler, self.compiler_options.clone(), + false, )?); } @@ -157,6 +158,7 @@ impl Command for Build { &build_imports_directory, &handler, self.compiler_options.clone(), + true, )?); } } @@ -197,6 +199,8 @@ impl Command for Build { } } +/// Compiles a Leo file in the `src/` directory. +#[allow(clippy::too_many_arguments)] fn compile_leo_file( file_path: PathBuf, _package_path: &Path, @@ -205,6 +209,7 @@ fn compile_leo_file( build: &Path, handler: &Handler, options: BuildOptions, + is_import: bool, ) -> Result> { // Construct the Leo file name with extension `foo.leo`. let file_name = file_path @@ -218,13 +223,14 @@ fn compile_leo_file( .ok_or_else(PackageError::failed_to_get_file_name)?; // Create a new instance of the Leo compiler. - let mut program = Compiler::new( + let mut compiler = Compiler::new( program_name.to_string(), program_id.network().to_string(), handler, file_path.clone(), outputs.to_path_buf(), Some(options.into()), + is_import, ); // TODO: Temporarily removing checksum files. Need to redesign this scheme. @@ -258,7 +264,7 @@ fn compile_leo_file( // if checksum_differs { // Compile the Leo program into Aleo instructions. - let (symbol_table, instructions) = program.compile_and_generate_instructions()?; + let (symbol_table, instructions) = compiler.compile_and_generate_instructions()?; // Create the path to the Aleo file. let mut aleo_file_path = build.to_path_buf(); diff --git a/leo/package/src/source/directory.rs b/leo/package/src/source/directory.rs index a5ce28396d..be66709bf3 100644 --- a/leo/package/src/source/directory.rs +++ b/leo/package/src/source/directory.rs @@ -18,6 +18,7 @@ use crate::parse_file_paths; use leo_errors::{PackageError, Result}; +use crate::source::MAIN_FILENAME; use std::{ borrow::Cow, fs, @@ -59,7 +60,7 @@ impl SourceDirectory { pub fn check_files(paths: &[PathBuf]) -> Result<()> { match paths.len() { 0 => Err(PackageError::empty_source_directory().into()), - 1 => Ok(()), + 1 if paths[0].as_path().ends_with(MAIN_FILENAME) => Ok(()), _ => Err(PackageError::source_directory_can_contain_only_one_file().into()), } } diff --git a/tests/test-framework/benches/leo_compiler.rs b/tests/test-framework/benches/leo_compiler.rs index e1003526eb..38cb37396e 100644 --- a/tests/test-framework/benches/leo_compiler.rs +++ b/tests/test-framework/benches/leo_compiler.rs @@ -80,6 +80,7 @@ fn new_compiler(handler: &Handler) -> Compiler<'_> { PathBuf::from(String::new()), PathBuf::from(String::new()), None, + false, ) }