Skip to content

Commit 7436420

Browse files
Franklin-Qisylvestre
authored andcommitted
parser: reject reserved keywords in qualified identifiers
Validate namespace and literal parts of ns::name against gawk's reserved keyword list when building identifiers, including @namespace directives. Closes: #37
1 parent 67bf583 commit 7436420

5 files changed

Lines changed: 115 additions & 20 deletions

File tree

parser/src/diagnostics.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,12 @@ pub enum ParsingError {
7575
SpecialVariableIndirectCall(Span, String),
7676
#[error("Can't chain non-associative operators.")]
7777
NonAssociativeOperator(Span),
78+
#[error("Using reserved identifier `{1}` as a namespace is not allowed.")]
79+
ReservedNamespace(Span, String),
80+
#[error(
81+
"Using reserved identifier `{1}` as second component of a qualified name is not allowed."
82+
)]
83+
ReservedQualifiedLiteral(Span, String),
7884
}
7985

8086
impl ParsingError {
@@ -121,6 +127,8 @@ impl ParsingError {
121127
Self::SpecialVariableCall(span, _) => Some(span.clone()),
122128
Self::SpecialVariableIndirectCall(span, _) => Some(span.clone()),
123129
Self::NonAssociativeOperator(span) => Some(span.clone()),
130+
Self::ReservedNamespace(span, _) => Some(span.clone()),
131+
Self::ReservedQualifiedLiteral(span, _) => Some(span.clone()),
124132
}
125133
}
126134
fn hint(&self) -> Option<&'static str> {

parser/src/keywords.rs

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// This file is part of the uutils awk package.
2+
//
3+
// For the full copyright and license information, please view the LICENSE
4+
// files that was distributed with this source code.
5+
6+
/// Returns whether `name` is a reserved keyword that cannot appear in a
7+
/// qualified identifier (`ns::name`), matching gawk.
8+
pub fn is_reserved_keyword(name: &str) -> bool {
9+
matches!(
10+
name,
11+
"BEGIN"
12+
| "END"
13+
| "if"
14+
| "else"
15+
| "switch"
16+
| "case"
17+
| "default"
18+
| "do"
19+
| "while"
20+
| "for"
21+
| "in"
22+
| "print"
23+
| "printf"
24+
| "getline"
25+
| "next"
26+
| "nextfile"
27+
| "exit"
28+
| "break"
29+
| "continue"
30+
| "return"
31+
| "delete"
32+
| "function"
33+
| "func"
34+
)
35+
}

parser/src/lib.rs

Lines changed: 51 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
mod ast;
99
mod diagnostics;
1010
mod idempotency;
11+
mod keywords;
1112
mod lex;
1213
mod pratt;
1314
mod sexpr;
@@ -134,7 +135,14 @@ impl<'a> Parser<'a> {
134135
}
135136
Token::NamespaceDirective => {
136137
let namespace = lex.expect_string()?;
137-
self.namespace = lex.lex_ident(namespace.as_ref(), self.arena)?;
138+
let namespace = lex.lex_ident(namespace.as_ref(), self.arena)?;
139+
if keywords::is_reserved_keyword(namespace) {
140+
return Err(ParsingError::ReservedNamespace(
141+
lex.span(),
142+
namespace.to_string(),
143+
));
144+
}
145+
self.namespace = namespace;
138146
lex.expect_with(Token::is_stmnt_end, "expected statement end.".into())?;
139147
}
140148
Token::ConcurrentDirective => {
@@ -544,8 +552,13 @@ impl<'a> Parser<'a> {
544552

545553
fn parse_delete(&mut self, lex: &mut Lexer<'a>) -> Result<SimpleStatement<'a>> {
546554
let next = lex.expect_next()?;
547-
let Ok(var) = self.get_place(lex, next) else {
548-
return Err(ParsingError::OperatorExpectsVariable(lex.span()));
555+
let var = match self.get_place(lex, next) {
556+
Ok(var) => var,
557+
Err(
558+
err @ (ParsingError::ReservedNamespace(..)
559+
| ParsingError::ReservedQualifiedLiteral(..)),
560+
) => return Err(err),
561+
Err(_) => return Err(ParsingError::OperatorExpectsVariable(lex.span())),
549562
};
550563
let index = if lex.consume(&Token::OpenBracket) {
551564
let mut pratt = Pratt::new(self, false);
@@ -561,7 +574,9 @@ impl<'a> Parser<'a> {
561574

562575
#[tracing::instrument]
563576
fn parse_function(&mut self, lex: &mut Lexer<'a>) -> Result<()> {
564-
let name = lex.expect_identifier()?.qualify(self.namespace);
577+
let name = lex
578+
.expect_identifier()?
579+
.try_qualify(self.namespace, &lex.span())?;
565580
let args = self.parse_signature(lex, &name)?;
566581
lex.consume(&Token::Newline);
567582
let body = self.parse_body(lex)?;
@@ -586,7 +601,9 @@ impl<'a> Parser<'a> {
586601
}
587602

588603
loop {
589-
let name = lex.expect_identifier()?.qualify(self.namespace);
604+
let name = lex
605+
.expect_identifier()?
606+
.try_qualify(self.namespace, &lex.span())?;
590607
// Linear search is fine for the numbers we are working with.
591608
if let Some(arg) = args.iter().find(|&a| a == &name) {
592609
return Err(ParsingError::DuplicatedArgument(
@@ -658,6 +675,10 @@ impl<'a> Parser<'a> {
658675
Token::TypedRegex(_) => Err(ParsingError::UnexpectedTypedRegex(lex.span())),
659676
token => match self.get_place(lex, token) {
660677
Ok(var) => Ok(Atom::Variable(var)),
678+
Err(
679+
err @ (ParsingError::ReservedNamespace(..)
680+
| ParsingError::ReservedQualifiedLiteral(..)),
681+
) => Err(err),
661682
Err(_) => Err(ParsingError::UnexpectedToken(
662683
lex.span(),
663684
"is not valid data.".into(),
@@ -667,10 +688,10 @@ impl<'a> Parser<'a> {
667688
}
668689

669690
#[tracing::instrument]
670-
fn get_place(&self, lex: &mut Lexer<'a>, token: Token<'a>) -> Result<Variable<'a>, Token<'a>> {
691+
fn get_place(&self, lex: &mut Lexer<'a>, token: Token<'a>) -> Result<Variable<'a>> {
671692
match token {
672693
Token::Identifier(a) if !(lex.peek_is(&Token::OpenParent) && lex.is_yuxtaposed()) => {
673-
Ok(a.qualify(self.namespace).into())
694+
Ok(a.try_qualify(self.namespace, &lex.span())?.into())
674695
}
675696
Token::NrVariable => Ok(Variable::Nr),
676697
Token::NfVariable => Ok(Variable::Nf),
@@ -687,7 +708,10 @@ impl<'a> Parser<'a> {
687708
Token::RstartVariable => Ok(Variable::Rstart),
688709
Token::RlengthVariable => Ok(Variable::Rlength),
689710
Token::EnvironVariable => Ok(Variable::Environ),
690-
tok => Err(tok),
711+
tok => Err(ParsingError::UnexpectedToken(
712+
lex.span(),
713+
format!("{tok:?}"),
714+
)),
691715
}
692716
}
693717
}
@@ -717,22 +741,35 @@ impl Preprocessor {
717741
}
718742

719743
trait IdentifierExt<'a> {
720-
fn qualify(self, namespace: &'a str) -> Identifier<'a>
744+
fn try_qualify(self, namespace: &'a str, span: &Span) -> Result<Identifier<'a>>
721745
where
722746
Self: 'a;
723747
}
724748

725749
impl<'a> IdentifierExt<'a> for lexer::Identifier<'_> {
726-
fn qualify(self, namespace: &'a str) -> Identifier<'a>
750+
fn try_qualify(self, namespace: &'a str, span: &Span) -> Result<Identifier<'a>>
727751
where
728752
Self: 'a,
729753
{
730754
let literal = self.literal;
731-
if let Some(namespace) = self.namespace {
732-
Identifier { namespace, literal }
755+
let namespace = if let Some(ns) = self.namespace {
756+
if keywords::is_reserved_keyword(ns) {
757+
return Err(ParsingError::ReservedNamespace(
758+
span.clone(),
759+
ns.to_string(),
760+
));
761+
}
762+
if keywords::is_reserved_keyword(literal) {
763+
return Err(ParsingError::ReservedQualifiedLiteral(
764+
span.clone(),
765+
literal.to_string(),
766+
));
767+
}
768+
ns
733769
} else {
734-
Identifier { namespace, literal }
735-
}
770+
namespace
771+
};
772+
Ok(Identifier { namespace, literal })
736773
}
737774
}
738775

parser/src/pratt.rs

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -269,10 +269,12 @@ impl<'a, 'b> Pratt<'a, 'b> {
269269
name.literal.to_string(),
270270
));
271271
}
272+
let span = lex.span();
273+
let qualified = name.try_qualify(self.parser.namespace, &span)?;
272274
self.parser.parse_function_call(
273275
lex,
274-
|args| ExprNode::FunctionCall(name.qualify(self.parser.namespace), args),
275-
lex.span(),
276+
move |args| ExprNode::FunctionCall(qualified, args),
277+
span,
276278
)
277279
} else if let Some(builtin) = next.maps_to_builtin() {
278280
self.parser.parse_function_call(
@@ -289,16 +291,17 @@ impl<'a, 'b> Pratt<'a, 'b> {
289291
name.literal.to_string(),
290292
));
291293
}
292-
let name = Variable::User(name.qualify(self.parser.namespace));
294+
let span = lex.span();
295+
let name = Variable::User(name.try_qualify(self.parser.namespace, &span)?);
293296
self.parser.parse_function_call(
294297
lex,
295-
|args| ExprNode::IndirectCall(name, args),
296-
lex.span(),
298+
move |args| ExprNode::IndirectCall(name, args),
299+
span,
297300
)
298301
} else if next.is_place() && lex.peek_is(&Token::OpenParent) && lex.is_yuxtaposed() {
299302
let name = match self.parser.get_place(lex, next) {
300303
Ok(var) => var.to_string(),
301-
Err(tok) => format!("{tok:?}"),
304+
Err(err) => err.to_string(),
302305
};
303306
Err(ParsingError::SpecialVariableCall(lex.span(), name))
304307
} else {

parser/src/tests.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,18 @@ fn test_parser_invalid_patterns() {
182182
test_parser!(is_err!("BEGIN", "END", "BEGINFILE", "ENDFILE", "print 1;"));
183183
}
184184

185+
#[test]
186+
fn test_parser_reserved_qualified_identifiers() {
187+
test_parser!(is_err!(
188+
"{ if::while }",
189+
"{ foo::while }",
190+
"{ while::foo }",
191+
"@namespace \"if\"; BEGIN {}",
192+
"function foo::while() {}",
193+
"function while::foo() {}"
194+
));
195+
}
196+
185197
#[test]
186198
fn test_parser_non_assoc() {
187199
test_parser!(is_err!(

0 commit comments

Comments
 (0)