Forbid reserved variable names for function arguments (#11169)

Works for all arguments and flags. Because the signature parsing doesn't
give the spans, it is flags the entire signature.

Also added a constant with reserved variable names.

Fix #11158.
This commit is contained in:
Andrej Kolchin 2023-11-29 17:29:07 +00:00 committed by GitHub
parent e290fa0e68
commit 0e1322e6d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 40 additions and 4 deletions

View file

@ -40,6 +40,8 @@ use crate::{
/// These parser keywords can be aliased
pub const ALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[b"overlay hide", b"overlay new", b"overlay use"];
pub const RESERVED_VARIABLE_NAMES: [&str; 3] = ["in", "nu", "env"];
/// These parser keywords cannot be aliased (either not possible, or support not yet added)
pub const UNALIASABLE_PARSER_KEYWORDS: &[&[u8]] = &[
b"export",
@ -350,6 +352,13 @@ pub fn parse_for(working_set: &mut StateWorkingSet, spans: &[Span]) -> Expressio
}
}
/// If `name` is a keyword, emit an error.
fn verify_not_reserved_variable_name(working_set: &mut StateWorkingSet, name: &str, span: Span) {
if RESERVED_VARIABLE_NAMES.contains(&name) {
working_set.error(ParseError::NameIsBuiltinVar(name.to_string(), span))
}
}
// Returns also the parsed command name and ID
pub fn parse_def(
working_set: &mut StateWorkingSet,
@ -515,6 +524,19 @@ pub fn parse_def(
let mut result = None;
if let (Some(mut signature), Some(block_id)) = (sig.as_signature(), block.as_block()) {
for arg_name in &signature.required_positional {
verify_not_reserved_variable_name(working_set, &arg_name.name, sig.span);
}
for arg_name in &signature.optional_positional {
verify_not_reserved_variable_name(working_set, &arg_name.name, sig.span);
}
for arg_name in &signature.rest_positional {
verify_not_reserved_variable_name(working_set, &arg_name.name, sig.span);
}
for flag_name in &signature.get_names() {
verify_not_reserved_variable_name(working_set, flag_name, sig.span);
}
if has_wrapped {
if let Some(rest) = &signature.rest_positional {
if let Some(var_id) = rest.var_id {
@ -2997,7 +3019,7 @@ pub fn parse_let(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
.trim_start_matches('$')
.to_string();
if ["in", "nu", "env"].contains(&var_name.as_str()) {
if RESERVED_VARIABLE_NAMES.contains(&var_name.as_str()) {
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
}
@ -3104,8 +3126,7 @@ pub fn parse_const(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipelin
.trim_start_matches('$')
.to_string();
// TODO: Remove the hard-coded variables, too error-prone
if ["in", "nu", "env"].contains(&var_name.as_str()) {
if RESERVED_VARIABLE_NAMES.contains(&var_name.as_str()) {
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
}
@ -3246,7 +3267,7 @@ pub fn parse_mut(working_set: &mut StateWorkingSet, spans: &[Span]) -> Pipeline
.trim_start_matches('$')
.to_string();
if ["in", "nu", "env"].contains(&var_name.as_str()) {
if RESERVED_VARIABLE_NAMES.contains(&var_name.as_str()) {
working_set.error(ParseError::NameIsBuiltinVar(var_name, lvalue.span))
}

View file

@ -304,6 +304,21 @@ fn parse_function_signature(#[case] phrase: &str) {
assert!(actual.err.is_empty());
}
#[rstest]
#[case("def test [ in ] {}")]
#[case("def test [ in: string ] {}")]
#[case("def test [ nu: int ] {}")]
#[case("def test [ env: record<> ] {}")]
#[case("def test [ --env ] {}")]
#[case("def test [ --nu: int ] {}")]
#[case("def test [ --in (-i): list<any> ] {}")]
#[case("def test [ a: string, b: int, in: table<a: int b: int> ] {}")]
#[case("def test [ env, in, nu ] {}")]
fn parse_function_signature_name_is_builtin_var(#[case] phrase: &str) {
let actual = nu!(phrase);
assert!(actual.err.contains("nu::parser::name_is_builtin_var"))
}
#[rstest]
#[case("let a: int = 1")]
#[case("let a: string = 'qwe'")]