Fix circular source causing Nushell to crash (#12262)

# Description

EngineState now tracks the script currently running, instead of the
parent directory of the script. This also provides an easy way to expose
the current running script to the user (Issue #12195).

Similarly, StateWorkingSet now tracks scripts instead of directories.
`parsed_module_files` and `currently_parsed_pwd` are merged into one
variable, `scripts`, which acts like a stack for tracking the current
running script (which is on the top of the stack).

Circular import check is added for `source` operations, in addition to
module import. A simple testcase is added for circular source.

<!--
if this PR closes one or more issues, you can automatically link the PR
with
them by using one of the [*linking
keywords*](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue#linking-a-pull-request-to-an-issue-using-a-keyword),
e.g.
- this PR should close #xxxx
- fixes #xxxx

you can also mention related issues, PRs or discussions!
-->


<!--
Thank you for improving Nushell. Please, check our [contributing
guide](../CONTRIBUTING.md) and talk to the core team before making major
changes.

Description of your pull request goes here. **Provide examples and/or
screenshots** if your changes affect the user experience.
-->

# User-Facing Changes
<!-- List of all changes that impact the user experience here. This
helps us keep track of breaking changes. -->

It shouldn't have any user facing changes.
This commit is contained in:
YizhePKU
2024-04-19 14:38:08 +08:00
committed by GitHub
parent 351bff8233
commit 6d2cb4382a
14 changed files with 164 additions and 121 deletions

View File

@ -1871,32 +1871,16 @@ pub fn parse_module_block(
(block, module, module_comments)
}
/// Parse a module from a file.
///
/// The module name is inferred from the stem of the file, unless specified in `name_override`.
fn parse_module_file(
working_set: &mut StateWorkingSet,
path: ParserPath,
path_span: Span,
name_override: Option<String>,
) -> Option<ModuleId> {
if let Some(i) = working_set
.parsed_module_files
.iter()
.rposition(|p| p == path.path())
{
let mut files: Vec<String> = working_set
.parsed_module_files
.split_off(i)
.iter()
.map(|p| p.to_string_lossy().to_string())
.collect();
files.push(path.path().to_string_lossy().to_string());
let msg = files.join("\nuses ");
working_set.error(ParseError::CyclicalModuleImport(msg, path_span));
return None;
}
// Infer the module name from the stem of the file, unless overridden.
let module_name = if let Some(name) = name_override {
name
} else if let Some(stem) = path.file_stem() {
@ -1909,6 +1893,7 @@ fn parse_module_file(
return None;
};
// Read the content of the module.
let contents = if let Some(contents) = path.read(working_set) {
contents
} else {
@ -1922,29 +1907,23 @@ fn parse_module_file(
let file_id = working_set.add_file(path.path().to_string_lossy().to_string(), &contents);
let new_span = working_set.get_span_for_file(file_id);
// Check if we've parsed the module before.
if let Some(module_id) = working_set.find_module_by_span(new_span) {
return Some(module_id);
}
// Change the currently parsed directory
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
working_set.currently_parsed_cwd.replace(parent.into())
} else {
working_set.currently_parsed_cwd.clone()
};
// Add the file to the stack of parsed module files
working_set.parsed_module_files.push(path.path_buf());
// Add the file to the stack of files being processed.
if let Err(e) = working_set.files.push(path.path_buf(), path_span) {
working_set.error(e);
return None;
}
// Parse the module
let (block, module, module_comments) =
parse_module_block(working_set, new_span, module_name.as_bytes());
// Remove the file from the stack of parsed module files
working_set.parsed_module_files.pop();
// Restore the currently parsed directory back
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
// Remove the file from the stack of files being processed.
working_set.files.pop();
let _ = working_set.add_block(Arc::new(block));
let module_id = working_set.add_module(&module_name, module, module_comments);
@ -3425,12 +3404,11 @@ pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteComman
if let Some(path) = find_in_dirs(&filename, working_set, &cwd, LIB_DIRS_VAR) {
if let Some(contents) = path.read(working_set) {
// Change currently parsed directory
let prev_currently_parsed_cwd = if let Some(parent) = path.parent() {
working_set.currently_parsed_cwd.replace(parent.into())
} else {
working_set.currently_parsed_cwd.clone()
};
// Add the file to the stack of files being processed.
if let Err(e) = working_set.files.push(path.clone().path_buf(), spans[1]) {
working_set.error(e);
return garbage_pipeline(spans);
}
// This will load the defs from the file into the
// working set, if it was a successful parse.
@ -3441,8 +3419,8 @@ pub fn parse_source(working_set: &mut StateWorkingSet, lite_command: &LiteComman
scoped,
);
// Restore the currently parsed directory back
working_set.currently_parsed_cwd = prev_currently_parsed_cwd;
// Remove the file from the stack of files being processed.
working_set.files.pop();
// Save the block into the working set
let block_id = working_set.add_block(block);
@ -3831,11 +3809,10 @@ pub fn find_in_dirs(
dirs_var_name: &str,
) -> Option<ParserPath> {
// Choose whether to use file-relative or PWD-relative path
let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd {
currently_parsed_cwd.as_path()
} else {
Path::new(cwd)
};
let actual_cwd = working_set
.files
.current_working_directory()
.unwrap_or(Path::new(cwd));
// Try if we have an existing virtual path
if let Some(virtual_path) = working_set.find_virtual_path(filename) {
@ -3895,11 +3872,10 @@ pub fn find_in_dirs(
dirs_env: &str,
) -> Option<PathBuf> {
// Choose whether to use file-relative or PWD-relative path
let actual_cwd = if let Some(currently_parsed_cwd) = &working_set.currently_parsed_cwd {
currently_parsed_cwd.as_path()
} else {
Path::new(cwd)
};
let actual_cwd = working_set
.files
.current_working_directory()
.unwrap_or(Path::new(cwd));
if let Ok(p) = canonicalize_with(filename, actual_cwd) {
Some(p)