Skip to content

PSParser Tokenize with Switch Default fails #13462

Closed

Description

@LAltinell

Trouble when trying to Tokenize a script containing a Switch-statement with a Default case in Powershell 7.1.0-preview.5. This works as expected in Powershell 7.0.3 (stable). If one does comment-out the default case, it works as expected. This has been reproduced on more than one computer.

Steps to reproduce

Create a file containing a simple function that has a switch-statement with a default case (see below). Save that file. Then try to tokenize that file (also see below) from a console running Powershell 7.1.0-preview.5.

# Save this as a file called 'TEST.ps1'
Function Test {
    Param (
        $String
    )

    Switch ($String) {
        "test" {
            "sfdsf"
        }

        "test2" {
            "sfdsf"
        }

        default {
            "sdfsdf"
        }
    }
}
# From a Powershell 7.1.0-preview.5 prompt execute the following:
$scripts = Get-ChildItem -Path "C:\__TEMP\TEST.ps1"

Foreach ($file in $scripts) {
    $contents = Get-Content -Path $file.fullname -ErrorAction Stop
    $errors = $null
    $null = [System.Management.Automation.PSParser]::Tokenize($contents, [ref]$errors)
    $errors.Count -eq 0
}

Expected behavior

True

Actual behavior

MethodInvocationException:
Line |
   4 |      $null = [System.Management.Automation.PSParser]::Tokenize($conten …
     |      ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     | Exception calling "Tokenize" with "2" argument(s): "Index was outside the bounds of the array."
True

Environment data


Name                           Value
----                           -----
PSVersion                      7.1.0-preview.5
PSEdition                      Core
GitCommitId                    7.1.0-preview.5
OS                             Microsoft Windows 10.0.19041
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Activity

  1. added
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on Aug 17, 2020
  2. vexx32 commented on Aug 17, 2020

    @vexx32
    Collaborator

    PSParser is legacy and I'm not sure it's actually supported in a meaningful way.

    Instead, you can use:

    $tokens = $parseErrors = $null
    $ast = [System.Management.Automation.Language.Parser]::ParseInput($script, [ref]$tokens, [ref]$parseErrors)

    My guess is the PSParser code path was probably broken with #10487 cc Steve Lee (@SteveL-MSFT) Rob Holt (@rjmholt) not sure PSParser is really supported / what would need to be done to "fix" it following that change, or whether it's worth looking into as there are fully functional and supported alternatives available.

  3. LAltinell commented on Aug 17, 2020

    @LAltinell
    Author

    Thank you for your prompt reply!

    Perfect, that works for me. Hopefully this will help someone else as well.

  4. rjmholt commented on Aug 17, 2020

    @rjmholt
    Collaborator

    This sounds like something we should still fix though -- the PSParser API needs to be maintained for broad module compatibility

  5. added
    WG-Enginecore PowerShell engine, interpreter, and runtime
    Issue-BugIssue has been identified as a bug in the product
    and removed
    Issue-Questionideally support can be provided via other mechanisms, but sometimes folks do open an issue to get a
    on Aug 17, 2020
  6. rjmholt commented on Aug 18, 2020

    @rjmholt
    Collaborator

    Full stack trace:

    
    Exception             :
        Type           : System.Management.Automation.MethodInvocationException
        ErrorRecord    :
            Exception             :
                Type    : System.Management.Automation.ParentContainsErrorRecordException
                Message : Exception calling "Tokenize" with "2" argument(s): "Index was outside the
    bounds of the array."
                HResult : -2146233087
            CategoryInfo          : NotSpecified: (:) [], ParentContainsErrorRecordException
            FullyQualifiedErrorId : IndexOutOfRangeException
            InvocationInfo        :
                ScriptLineNumber : 1
                OffsetInLine     : 1
                HistoryId        : -1
                Line             : [System.Management.Automation.PSParser]::Tokenize($s, [ref]$errs)
                PositionMessage  : At line:1 char:1
                                   + [System.Management.Automation.PSParser]::Tokenize($s, [ref]$errs)
                                   + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                CommandOrigin    : Internal
            ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
        TargetSite     :
            Name          : ConvertToMethodInvocationException
            DeclaringType : System.Management.Automation.ExceptionHandlingOps,
    System.Management.Automation, Version=7.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
            MemberType    : Method
            Module        : System.Management.Automation.dll
        StackTrace     :
       at System.Management.Automation.ExceptionHandlingOps.ConvertToMethodInvocationException(Exception
    exception, Type typeToThrow, String methodName, Int32 numArgs, MemberInfo memberInfo)
       at CallSite.Target(Closure , CallSite , Type , Object , PSReference )
       at System.Dynamic.UpdateDelegates.UpdateAndExecute3[T0,T1,T2,TRet](CallSite site, T0 arg0, T1
    arg1, T2 arg2)
       at System.Management.Automation.Interpreter.DynamicInstruction`4.Run(InterpretedFrame frame)
       at System.Management.Automation.Interpreter.EnterTryCatchFinallyInstruction.Run(InterpretedFrame
    frame)
        Message        : Exception calling "Tokenize" with "2" argument(s): "Index was outside the
    bounds of the array."
        Data           : System.Collections.ListDictionaryInternal
        InnerException :
            Type       : System.IndexOutOfRangeException
            TargetSite :
                Name          : GetPSTokenType
                DeclaringType : System.Management.Automation.PSToken
                MemberType    : Method
                Module        : System.Management.Automation.dll
            StackTrace :
       at System.Management.Automation.PSToken.GetPSTokenType(Token token)
       at System.Management.Automation.PSToken..ctor(Token token)
       at System.Management.Automation.PSParser.get_Tokens()
       at System.Management.Automation.PSParser.Tokenize(String script, Collection`1& errors)
       at CallSite.Target(Closure , CallSite , Type , Object , PSReference )
            Message    : Index was outside the bounds of the array.
            Source     : System.Management.Automation
            HResult    : -2146233080
        Source         : System.Management.Automation
        HResult        : -2146233087
    CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    FullyQualifiedErrorId : IndexOutOfRangeException
    InvocationInfo        :
        ScriptLineNumber : 1
        OffsetInLine     : 1
        HistoryId        : -1
        Line             : [System.Management.Automation.PSParser]::Tokenize($s, [ref]$errs)
        PositionMessage  : At line:1 char:1
                           + [System.Management.Automation.PSParser]::Tokenize($s, [ref]$errs)
                           + ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        CommandOrigin    : Internal
    ScriptStackTrace      : at <ScriptBlock>, <No file>: line 1
    
  7. rjmholt commented on Aug 18, 2020

    @rjmholt
    Collaborator

    The problem is that this mapping hasn't been maintained:

    private static readonly PSTokenType[] s_tokenKindMapping = new PSTokenType[]
    {
    #region Flags for unclassified tokens
    /* Unknown */ PSTokenType.Unknown,
    /* Variable */ PSTokenType.Variable,
    /* SplattedVariable */ PSTokenType.Variable,
    /* Parameter */ PSTokenType.CommandParameter,
    /* Number */ PSTokenType.Number,
    /* Label */ PSTokenType.LoopLabel,
    /* Identifier */ PSTokenType.CommandArgument,
    /* Generic */ PSTokenType.CommandArgument,
    /* Newline */ PSTokenType.NewLine,
    /* LineContinuation */ PSTokenType.LineContinuation,
    /* Comment */ PSTokenType.Comment,
    /* EndOfInput */ PSTokenType.Unknown,
    #endregion Flags for unclassified tokens
    #region Flags for strings
    /* StringLiteral */ PSTokenType.String,
    /* StringExpandable */ PSTokenType.String,
    /* HereStringLiteral */ PSTokenType.String,
    /* HereStringExpandable */ PSTokenType.String,
    #endregion Flags for strings
    #region Flags for punctuators
    /* LParen */ PSTokenType.GroupStart,
    /* RParen */ PSTokenType.GroupEnd,
    /* LCurly */ PSTokenType.GroupStart,
    /* RCurly */ PSTokenType.GroupEnd,
    /* LBracket */ PSTokenType.Operator,
    /* RBracket */ PSTokenType.Operator,
    /* AtParen */ PSTokenType.GroupStart,
    /* AtCurly */ PSTokenType.GroupStart,
    /* DollarParen */ PSTokenType.GroupStart,
    /* Semi */ PSTokenType.StatementSeparator,
    #endregion Flags for punctuators
    #region Flags for operators
    /* AndAnd */ PSTokenType.Operator,
    /* OrOr */ PSTokenType.Operator,
    /* Ampersand */ PSTokenType.Operator,
    /* Pipe */ PSTokenType.Operator,
    /* Comma */ PSTokenType.Operator,
    /* MinusMinus */ PSTokenType.Operator,
    /* PlusPlus */ PSTokenType.Operator,
    /* DotDot */ PSTokenType.Operator,
    /* ColonColon */ PSTokenType.Operator,
    /* Dot */ PSTokenType.Operator,
    /* Exclaim */ PSTokenType.Operator,
    /* Multiply */ PSTokenType.Operator,
    /* Divide */ PSTokenType.Operator,
    /* Rem */ PSTokenType.Operator,
    /* Plus */ PSTokenType.Operator,
    /* Minus */ PSTokenType.Operator,
    /* Equals */ PSTokenType.Operator,
    /* PlusEquals */ PSTokenType.Operator,
    /* MinusEquals */ PSTokenType.Operator,
    /* MultiplyEquals */ PSTokenType.Operator,
    /* DivideEquals */ PSTokenType.Operator,
    /* RemainderEquals */ PSTokenType.Operator,
    /* Redirection */ PSTokenType.Operator,
    /* RedirectInStd */ PSTokenType.Operator,
    /* Format */ PSTokenType.Operator,
    /* Not */ PSTokenType.Operator,
    /* Bnot */ PSTokenType.Operator,
    /* And */ PSTokenType.Operator,
    /* Or */ PSTokenType.Operator,
    /* Xor */ PSTokenType.Operator,
    /* Band */ PSTokenType.Operator,
    /* Bor */ PSTokenType.Operator,
    /* Bxor */ PSTokenType.Operator,
    /* Join */ PSTokenType.Operator,
    /* Ieq */ PSTokenType.Operator,
    /* Ine */ PSTokenType.Operator,
    /* Ige */ PSTokenType.Operator,
    /* Igt */ PSTokenType.Operator,
    /* Ilt */ PSTokenType.Operator,
    /* Ile */ PSTokenType.Operator,
    /* Ilike */ PSTokenType.Operator,
    /* Inotlike */ PSTokenType.Operator,
    /* Imatch */ PSTokenType.Operator,
    /* Inotmatch */ PSTokenType.Operator,
    /* Ireplace */ PSTokenType.Operator,
    /* Icontains */ PSTokenType.Operator,
    /* Inotcontains */ PSTokenType.Operator,
    /* Iin */ PSTokenType.Operator,
    /* Inotin */ PSTokenType.Operator,
    /* Isplit */ PSTokenType.Operator,
    /* Ceq */ PSTokenType.Operator,
    /* Cne */ PSTokenType.Operator,
    /* Cge */ PSTokenType.Operator,
    /* Cgt */ PSTokenType.Operator,
    /* Clt */ PSTokenType.Operator,
    /* Cle */ PSTokenType.Operator,
    /* Clike */ PSTokenType.Operator,
    /* Cnotlike */ PSTokenType.Operator,
    /* Cmatch */ PSTokenType.Operator,
    /* Cnotmatch */ PSTokenType.Operator,
    /* Creplace */ PSTokenType.Operator,
    /* Ccontains */ PSTokenType.Operator,
    /* Cnotcontains */ PSTokenType.Operator,
    /* Cin */ PSTokenType.Operator,
    /* Cnotin */ PSTokenType.Operator,
    /* Csplit */ PSTokenType.Operator,
    /* Is */ PSTokenType.Operator,
    /* IsNot */ PSTokenType.Operator,
    /* As */ PSTokenType.Operator,
    /* PostFixPlusPlus */ PSTokenType.Operator,
    /* PostFixMinusMinus */ PSTokenType.Operator,
    /* Shl */ PSTokenType.Operator,
    /* Shr */ PSTokenType.Operator,
    /* Reserved slot 1 */ PSTokenType.Unknown,
    /* Reserved slot 2 */ PSTokenType.Unknown,
    /* Reserved slot 3 */ PSTokenType.Unknown,
    /* Reserved slot 4 */ PSTokenType.Unknown,
    /* Reserved slot 5 */ PSTokenType.Unknown,
    /* Reserved slot 6 */ PSTokenType.Unknown,
    /* Reserved slot 7 */ PSTokenType.Unknown,
    /* Reserved slot 8 */ PSTokenType.Unknown,
    /* Reserved slot 9 */ PSTokenType.Unknown,
    /* Reserved slot 10 */ PSTokenType.Unknown,
    /* Reserved slot 11 */ PSTokenType.Unknown,
    /* Reserved slot 12 */ PSTokenType.Unknown,
    /* Reserved slot 13 */ PSTokenType.Unknown,
    /* Reserved slot 14 */ PSTokenType.Unknown,
    /* Reserved slot 15 */ PSTokenType.Unknown,
    /* Reserved slot 16 */ PSTokenType.Unknown,
    /* Reserved slot 17 */ PSTokenType.Unknown,
    /* Reserved slot 18 */ PSTokenType.Unknown,
    /* Reserved slot 19 */ PSTokenType.Unknown,
    /* Reserved slot 20 */ PSTokenType.Unknown,
    #endregion Flags for operators
    #region Flags for keywords
    /* Begin */ PSTokenType.Keyword,
    /* Break */ PSTokenType.Keyword,
    /* Catch */ PSTokenType.Keyword,
    /* Class */ PSTokenType.Keyword,
    /* Continue */ PSTokenType.Keyword,
    /* Data */ PSTokenType.Keyword,
    /* Define */ PSTokenType.Keyword,
    /* Do */ PSTokenType.Keyword,
    /* Dynamicparam */ PSTokenType.Keyword,
    /* Else */ PSTokenType.Keyword,
    /* ElseIf */ PSTokenType.Keyword,
    /* End */ PSTokenType.Keyword,
    /* Exit */ PSTokenType.Keyword,
    /* Filter */ PSTokenType.Keyword,
    /* Finally */ PSTokenType.Keyword,
    /* For */ PSTokenType.Keyword,
    /* Foreach */ PSTokenType.Keyword,
    /* From */ PSTokenType.Keyword,
    /* Function */ PSTokenType.Keyword,
    /* If */ PSTokenType.Keyword,
    /* In */ PSTokenType.Keyword,
    /* Param */ PSTokenType.Keyword,
    /* Process */ PSTokenType.Keyword,
    /* Return */ PSTokenType.Keyword,
    /* Switch */ PSTokenType.Keyword,
    /* Throw */ PSTokenType.Keyword,
    /* Trap */ PSTokenType.Keyword,
    /* Try */ PSTokenType.Keyword,
    /* Until */ PSTokenType.Keyword,
    /* Using */ PSTokenType.Keyword,
    /* Var */ PSTokenType.Keyword,
    /* While */ PSTokenType.Keyword,
    /* Workflow */ PSTokenType.Keyword,
    /* Parallel */ PSTokenType.Keyword,
    /* Sequence */ PSTokenType.Keyword,
    /* InlineScript */ PSTokenType.Keyword,
    /* Configuration */ PSTokenType.Keyword,
    /* DynamicKeyword */ PSTokenType.Keyword,
    /* Public */ PSTokenType.Keyword,
    /* Private */ PSTokenType.Keyword,
    /* Static */ PSTokenType.Keyword,
    /* Interface */ PSTokenType.Keyword,
    /* Enum */ PSTokenType.Keyword,
    /* Namespace */ PSTokenType.Keyword,
    /* Module */ PSTokenType.Keyword,
    /* Type */ PSTokenType.Keyword,
    /* Assembly */ PSTokenType.Keyword,
    /* Command */ PSTokenType.Keyword,
    /* Def */ PSTokenType.Keyword,
    #endregion Flags for keywords
    /* LastToken */ PSTokenType.Unknown,
    };

    That's ostensibly easy to fix, but:

    • We need testing for it
    • We need to work out what else might be missing. I notice for example that base doesn't seem to be in there
    • There may be tokens that we could return but we can't for compatibility reasons -- something I'm not sure how to handle (haven't thought about this properly). For example, how should a caller built to handle PS v3 code deal with the class keyword?
  8. rjmholt commented on Aug 18, 2020

    @rjmholt
    Collaborator
  9. daxian-dbw commented on Aug 18, 2020

    @daxian-dbw
    Member

    Rob Holt (@rjmholt) Is this specific regression caused by #10487?

  10. rjmholt commented on Aug 18, 2020

    @rjmholt
    Collaborator

    Rob Holt (@rjmholt) Is this specific regression caused by #10487?

    I believe so

  11. daxian-dbw commented on Aug 18, 2020

    @daxian-dbw
    Member

    base seems to be working fine:

    PS:162> $es = $null; [System.Management.Automation.PSParser]::Tokenize("class foo { foo() : base() }", [ref]$es)
    ...
    Content     : base
    Type        : Unknown
    Start       : 20
    Length      : 4
    StartLine   : 1
    StartColumn : 21
    EndLine     : 1
    EndColumn   : 25
    ...
    

    [Update] I took a look at the code, and the keyword base works just by accident.

  12. rjmholt commented on Aug 18, 2020

    @rjmholt
    Collaborator

    Type : Unknown

    That looks like this:

  13. daxian-dbw commented on Aug 18, 2020

    @daxian-dbw
    Member

    Yeah. Let's first fix this regression by simply updating s_tokenKindMapping. And also open a new issue to discuss how to deal with PSParser in the long term to keep it up-to-date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Issue-BugIssue has been identified as a bug in the productMustHaveResolution-FixedThe issue is fixed.WG-Enginecore PowerShell engine, interpreter, and runtime

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions