From 8807f11395a5844fc9e48050eeccfee5d1be266f Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 30 Jan 2024 16:33:15 -0800 Subject: [PATCH 1/3] Validate the value for 'using namespace' during semantic checks --- .../engine/parser/SemanticChecks.cs | 46 ++++++++++++++++--- .../resources/ParserStrings.resx | 3 ++ .../Language/Parser/UsingNamespace.Tests.ps1 | 5 +- 3 files changed, 46 insertions(+), 8 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/SemanticChecks.cs b/src/System.Management.Automation/engine/parser/SemanticChecks.cs index b108e6330b4..f41b7d2a805 100644 --- a/src/System.Management.Automation/engine/parser/SemanticChecks.cs +++ b/src/System.Management.Automation/engine/parser/SemanticChecks.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Runtime.CompilerServices; using System.Text; +using System.Text.RegularExpressions; using Microsoft.PowerShell; using System.Management.Automation.Security; @@ -17,7 +18,7 @@ namespace System.Management.Automation.Language { - internal sealed class SemanticChecks : AstVisitor2, IAstPostVisitHandler + internal sealed partial class SemanticChecks : AstVisitor2, IAstPostVisitHandler { private readonly Parser _parser; @@ -1319,20 +1320,51 @@ public override AstVisitAction VisitScriptBlockExpression(ScriptBlockExpressionA public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatementAst) { - bool usingKindSupported = usingStatementAst.UsingStatementKind == UsingStatementKind.Namespace || - usingStatementAst.UsingStatementKind == UsingStatementKind.Assembly || - usingStatementAst.UsingStatementKind == UsingStatementKind.Module; - if (!usingKindSupported || - usingStatementAst.Alias != null) + UsingStatementKind kind = usingStatementAst.UsingStatementKind; + bool usingKindSupported = kind is UsingStatementKind.Namespace or UsingStatementKind.Assembly or UsingStatementKind.Module; + if (!usingKindSupported || usingStatementAst.Alias != null) { - _parser.ReportError(usingStatementAst.Extent, + _parser.ReportError( + usingStatementAst.Extent, nameof(ParserStrings.UsingStatementNotSupported), ParserStrings.UsingStatementNotSupported); } + if (kind is UsingStatementKind.Namespace) + { + Regex nsPattern = NamespacePattern(); + string name = usingStatementAst.Name.Value; + if (!nsPattern.IsMatch(usingStatementAst.Name.Value)) + { + _parser.ReportError( + usingStatementAst.Extent, + nameof(ParserStrings.InvalidNamespaceValue), + ParserStrings.InvalidNamespaceValue); + } + } + return AstVisitAction.Continue; } + /// + /// This regular expression is for validating if a namespace string is valid. + /// + /// In C#, a legit namespace is defined as `identifier ('.' identifier)*` [see https://learn.microsoft.com/dotnet/csharp/language-reference/language-specification/namespaces#143-namespace-declarations]. + /// And `identifier` is defined in https://learn.microsoft.com/dotnet/csharp/fundamentals/coding-style/identifier-names#naming-rules, summarized below: + /// - Identifiers must start with a letter or underscore (_). + /// - Identifiers can contain + /// * Unicode letter characters (categories: Lu, Ll, Lt, Lm, Lo or Nl); + /// * decimal digit characters (category: Nd); + /// * Unicode connecting characters (category: Pc); + /// * Unicode combining characters (categories: Mn, Mc); + /// * Unicode formatting characters (category: Cf). + /// + /// For details about how Unicode categories are represented in regular expression, see the "Unicode Categories" section in the following article: + /// - https://www.regular-expressions.info/unicode.html + /// + [GeneratedRegex(@"^[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}\p{Cf}_]*(?:\.[\p{L}\p{Nl}_][\p{L}\p{Nl}\p{Nd}\p{Pc}\p{Mn}\p{Mc}\p{Cf}_]*)*$")] + private static partial Regex NamespacePattern(); + public override AstVisitAction VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst) { // diff --git a/src/System.Management.Automation/resources/ParserStrings.resx b/src/System.Management.Automation/resources/ParserStrings.resx index a45a5165b03..aaf7d758608 100644 --- a/src/System.Management.Automation/resources/ParserStrings.resx +++ b/src/System.Management.Automation/resources/ParserStrings.resx @@ -1229,6 +1229,9 @@ ModuleVersion : Version of module to import. If used, ModuleName must represent This syntax of the 'using' statement is not supported. + + The specified namespace in the 'using' statement contains invalid characters. + information stream diff --git a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 index 58d9b8738cc..d5cc3cc35f2 100644 --- a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 +++ b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 @@ -128,7 +128,10 @@ Describe "Using Namespace" -Tags "CI" { ShouldBeParseError "1; using namespace System" UsingMustBeAtStartOfScript 3 ShouldBeParseError "using namespace Foo = System" UsingStatementNotSupported 0 + ShouldBeParseError "using namespace ''" InvalidNamespaceValue 16 + ShouldBeParseError "using namespace [System]" InvalidNamespaceValue 16 + ShouldBeParseError "using namespace ',System'" InvalidNamespaceValue 16 + ShouldBeParseError "using namespace ]System" InvalidNamespaceValue 16 # TODO: add diagnostic (low pri) # ShouldBeParseError "using namespace System; using namespace System" UsingNamespaceAlreadySpecified 24 } - From 0a0e6c3f3cec2983cd03fd63185042532b7acfbf Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Tue, 30 Jan 2024 16:51:14 -0800 Subject: [PATCH 2/3] Remove unused variable --- src/System.Management.Automation/engine/parser/SemanticChecks.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/System.Management.Automation/engine/parser/SemanticChecks.cs b/src/System.Management.Automation/engine/parser/SemanticChecks.cs index f41b7d2a805..c37f4ba4c7e 100644 --- a/src/System.Management.Automation/engine/parser/SemanticChecks.cs +++ b/src/System.Management.Automation/engine/parser/SemanticChecks.cs @@ -1333,7 +1333,6 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem if (kind is UsingStatementKind.Namespace) { Regex nsPattern = NamespacePattern(); - string name = usingStatementAst.Name.Value; if (!nsPattern.IsMatch(usingStatementAst.Name.Value)) { _parser.ReportError( From 49e4dc38a81fa87a8cc80be76561010530310780 Mon Sep 17 00:00:00 2001 From: Dongbo Wang Date: Wed, 31 Jan 2024 12:06:50 -0800 Subject: [PATCH 3/3] Address failing tests --- .../engine/parser/SemanticChecks.cs | 2 +- test/powershell/Language/Parser/UsingNamespace.Tests.ps1 | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/System.Management.Automation/engine/parser/SemanticChecks.cs b/src/System.Management.Automation/engine/parser/SemanticChecks.cs index c37f4ba4c7e..20d0a552cfa 100644 --- a/src/System.Management.Automation/engine/parser/SemanticChecks.cs +++ b/src/System.Management.Automation/engine/parser/SemanticChecks.cs @@ -1336,7 +1336,7 @@ public override AstVisitAction VisitUsingStatement(UsingStatementAst usingStatem if (!nsPattern.IsMatch(usingStatementAst.Name.Value)) { _parser.ReportError( - usingStatementAst.Extent, + usingStatementAst.Name.Extent, nameof(ParserStrings.InvalidNamespaceValue), ParserStrings.InvalidNamespaceValue); } diff --git a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 index d5cc3cc35f2..0370a8c5e1d 100644 --- a/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 +++ b/test/powershell/Language/Parser/UsingNamespace.Tests.ps1 @@ -131,7 +131,7 @@ Describe "Using Namespace" -Tags "CI" { ShouldBeParseError "using namespace ''" InvalidNamespaceValue 16 ShouldBeParseError "using namespace [System]" InvalidNamespaceValue 16 ShouldBeParseError "using namespace ',System'" InvalidNamespaceValue 16 - ShouldBeParseError "using namespace ]System" InvalidNamespaceValue 16 + ShouldBeParseError "using namespace ']System'" InvalidNamespaceValue 16 # TODO: add diagnostic (low pri) # ShouldBeParseError "using namespace System; using namespace System" UsingNamespaceAlreadySpecified 24 }