/
githubmirror
/
roslyn
Обзор
Документация
Войти
/
githubmirror
/
roslyn
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/Compilers/VisualBasic/Portable/Scanner/Directives.vb
663 строки
30 KB
Todd Grunke
Ensure VB parser is disposed (#76636)
08 янв 2025, 23:24
Не верифицирован
08 янв 2025, 23:24
df75ee9
Код
Авторство
О чём код?
' Licensed to the .NET Foundation under one or more agreements. ' The .NET Foundation licenses this file to you under the MIT license. ' See the LICENSE file in the project root for more information. '----------------------------------------------------------------------------- ' Contains scanner functionality related to Directives and Preprocessor '----------------------------------------------------------------------------- Option Compare Binary Option Strict On Imports System.Collections.Immutable Imports System.Runtime.InteropServices Imports Microsoft.CodeAnalysis.PooledObjects Imports Microsoft.CodeAnalysis.Syntax.InternalSyntax Imports Microsoft.CodeAnalysis.Text Imports Microsoft.CodeAnalysis.VisualBasic.SyntaxFacts Namespace Microsoft.CodeAnalysis.VisualBasic.Syntax.InternalSyntax Partial Friend Class Scanner Private _isScanningDirective As Boolean = False Protected _scannerPreprocessorState As PreprocessorState Private Function TryScanDirective(tList As SyntaxListBuilder) As Boolean Debug.Assert(IsAtNewLine()) ' leading whitespace until we see # should be regular whitespace If CanGet() AndAlso IsWhitespace(Peek()) Then Dim ws = ScanWhitespace() tList.Add(ws) End If ' SAVE the lookahead state and clear current token Dim restorePoint = CreateRestorePoint() Me._isScanningDirective = True ' since we do not have lookahead tokens, this just ' resets current token to _lineBufferOffset Me.GetNextTokenInState(ScannerState.VB) Dim currentNonterminal = Me.GetCurrentSyntaxNode() Dim directiveTrivia = TryCast(currentNonterminal, DirectiveTriviaSyntax) ' if we are lucky to get whole directive statement, we can just reuse it. If directiveTrivia IsNot Nothing Then Me.MoveToNextSyntaxNodeInTrivia() ' adjust current token to just after the node ' we need that in case we need to skip some disabled text '(yes we do tokenize disabled text for compatibility reasons) Me.GetNextTokenInState(ScannerState.VB) Else Using parser = New Parser(Me) directiveTrivia = parser.ParseConditionalCompilationStatement() directiveTrivia = parser.ConsumeStatementTerminatorAfterDirective(directiveTrivia) End Using End If Debug.Assert(directiveTrivia.FullWidth > 0, "should at least get #") ProcessDirective(directiveTrivia, tList) ResetLineBufferOffset() ' RESTORE lookahead state and current token if there were any restorePoint.RestoreTokens(includeLookAhead:=True) Me._isScanningDirective = False Return True End Function ''' <summary> ''' Entry point to directive processing for Scanner. ''' </summary> Private Sub ProcessDirective(directiveTrivia As DirectiveTriviaSyntax, tList As SyntaxListBuilder) Dim disabledCode As CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode) = Nothing Dim statement As DirectiveTriviaSyntax = directiveTrivia Dim newState = ApplyDirective(_scannerPreprocessorState, statement) _scannerPreprocessorState = newState ' if we are in a not taken branch, skip text Dim conditionals = newState.ConditionalStack If conditionals.Count <> 0 AndAlso Not conditionals.Peek.BranchTaken = ConditionalState.BranchTakenState.Taken Then ' we should not see #const inside disabled sections Debug.Assert(statement.Kind <> SyntaxKind.ConstDirectiveTrivia) ' skip disabled text disabledCode = SkipConditionalCompilationSection() End If ' Here we add the directive and disabled text that follows it ' (if there is any) to the trivia list ' processing statement could add an error to it, ' so we may need to rebuild the trivia node If statement IsNot directiveTrivia Then directiveTrivia = statement End If ' add the directive trivia to the list tList.Add(directiveTrivia) ' if had disabled code, add that too If disabledCode.Node IsNot Nothing Then tList.AddRange(disabledCode) End If End Sub ''' <summary> ''' Gets an initial preprocessor state and applies all directives from a given node. ''' Entry point for blender ''' </summary> Protected Shared Function ApplyDirectives(preprocessorState As PreprocessorState, node As VisualBasicSyntaxNode) As PreprocessorState If node.ContainsDirectives Then preprocessorState = ApplyDirectivesRecursive(preprocessorState, node) End If Return preprocessorState End Function Private Shared Function ApplyDirectivesRecursive(preprocessorState As PreprocessorState, node As GreenNode) As PreprocessorState Debug.Assert(node.ContainsDirectives, "we should not be processing nodes without Directives") ' node is a directive Dim directive = TryCast(node, DirectiveTriviaSyntax) If directive IsNot Nothing Then Dim statement = directive preprocessorState = ApplyDirective(preprocessorState, statement) Debug.Assert((statement Is directive) OrElse node.ContainsDiagnostics, "since we have no errors, we should not be changing statement") Return preprocessorState End If ' node is nonterminal Dim sCount = node.SlotCount If sCount > 0 Then For i As Integer = 0 To sCount - 1 Dim child = node.GetSlot(i) If child IsNot Nothing AndAlso child.ContainsDirectives Then preprocessorState = ApplyDirectivesRecursive(preprocessorState, child) End If Next Return preprocessorState End If ' node is a token Dim tk = DirectCast(node, SyntaxToken) Dim trivia = tk.GetLeadingTrivia If trivia IsNot Nothing AndAlso trivia.ContainsDirectives Then preprocessorState = ApplyDirectivesRecursive(preprocessorState, trivia) End If trivia = tk.GetTrailingTrivia If trivia IsNot Nothing AndAlso trivia.ContainsDirectives Then preprocessorState = ApplyDirectivesRecursive(preprocessorState, trivia) End If Return preprocessorState End Function ' takes a preprocessor state and applies a directive statement to it Friend Shared Function ApplyDirective(preprocessorState As PreprocessorState, ByRef statement As DirectiveTriviaSyntax) As PreprocessorState Select Case statement.Kind Case SyntaxKind.ConstDirectiveTrivia Dim conditionalsStack = preprocessorState.ConditionalStack If conditionalsStack.Count <> 0 AndAlso Not conditionalsStack.Peek.BranchTaken = ConditionalState.BranchTakenState.Taken Then ' const inside disabled text - do not evaluate Else ' interpret the const preprocessorState = preprocessorState.InterpretConstDirective(statement) End If Case SyntaxKind.IfDirectiveTrivia preprocessorState = preprocessorState.InterpretIfDirective(statement) Case SyntaxKind.ElseIfDirectiveTrivia preprocessorState = preprocessorState.InterpretElseIfDirective(statement) Case SyntaxKind.ElseDirectiveTrivia preprocessorState = preprocessorState.InterpretElseDirective(statement) Case SyntaxKind.EndIfDirectiveTrivia preprocessorState = preprocessorState.InterpretEndIfDirective(statement) Case SyntaxKind.RegionDirectiveTrivia preprocessorState = preprocessorState.InterpretRegionDirective(statement) Case SyntaxKind.EndRegionDirectiveTrivia preprocessorState = preprocessorState.InterpretEndRegionDirective(statement) Case SyntaxKind.ExternalSourceDirectiveTrivia preprocessorState = preprocessorState.InterpretExternalSourceDirective(statement) Case SyntaxKind.EndExternalSourceDirectiveTrivia preprocessorState = preprocessorState.InterpretEndExternalSourceDirective(statement) Case SyntaxKind.ExternalChecksumDirectiveTrivia, SyntaxKind.BadDirectiveTrivia, SyntaxKind.EnableWarningDirectiveTrivia, 'TODO: Add support for processing #Enable and #Disable SyntaxKind.DisableWarningDirectiveTrivia, SyntaxKind.ReferenceDirectiveTrivia ' These directives require no processing Case Else Throw ExceptionUtilities.UnexpectedValue(statement.Kind) End Select Return preprocessorState End Function ' represents states of a single conditional frame ' immutable as PreprocessorState so requires Friend Class ConditionalState Public Enum BranchTakenState As Byte NotTaken Taken AlreadyTaken End Enum Private ReadOnly _branchTaken As BranchTakenState Private ReadOnly _elseSeen As Boolean Private ReadOnly _ifDirective As IfDirectiveTriviaSyntax ' branch can be taken only once. ' this state transitions NotTaken -> Taken -> AlreadyTaken... Friend ReadOnly Property BranchTaken As BranchTakenState Get Return _branchTaken End Get End Property Friend ReadOnly Property ElseSeen As Boolean Get Return _elseSeen End Get End Property Friend ReadOnly Property IfDirective As IfDirectiveTriviaSyntax Get Return _ifDirective End Get End Property Friend Sub New(branchTaken As BranchTakenState, elseSeen As Boolean, ifDirective As IfDirectiveTriviaSyntax) _branchTaken = branchTaken _elseSeen = elseSeen _ifDirective = ifDirective End Sub End Class ' The class needs to be immutable ' as its instances can get associated with multiple tokens. Friend NotInheritable Class PreprocessorState Private ReadOnly _symbols As ImmutableDictionary(Of String, CConst) Private ReadOnly _conditionals As ImmutableStack(Of ConditionalState) Private ReadOnly _regionDirectives As ImmutableStack(Of RegionDirectiveTriviaSyntax) Private ReadOnly _haveSeenRegionDirectives As Boolean Private ReadOnly _externalSourceDirective As ExternalSourceDirectiveTriviaSyntax Friend Sub New(symbols As ImmutableDictionary(Of String, CConst)) _symbols = symbols _conditionals = ImmutableStack.Create(Of ConditionalState)() _regionDirectives = ImmutableStack.Create(Of RegionDirectiveTriviaSyntax)() End Sub Private Sub New(symbols As ImmutableDictionary(Of String, CConst), conditionals As ImmutableStack(Of ConditionalState), regionDirectives As ImmutableStack(Of RegionDirectiveTriviaSyntax), haveSeenRegionDirectives As Boolean, externalSourceDirective As ExternalSourceDirectiveTriviaSyntax) Me._symbols = symbols Me._conditionals = conditionals Me._regionDirectives = regionDirectives Me._haveSeenRegionDirectives = haveSeenRegionDirectives Me._externalSourceDirective = externalSourceDirective End Sub Friend ReadOnly Property SymbolsMap As ImmutableDictionary(Of String, CConst) Get Return _symbols End Get End Property Private Function SetSymbol(name As String, value As CConst) As PreprocessorState Dim symbols = Me._symbols symbols = symbols.SetItem(name, value) Return New PreprocessorState(symbols, Me._conditionals, Me._regionDirectives, Me._haveSeenRegionDirectives, Me._externalSourceDirective) End Function Friend ReadOnly Property ConditionalStack As ImmutableStack(Of ConditionalState) Get Return _conditionals End Get End Property Private Function WithConditionals(conditionals As ImmutableStack(Of ConditionalState)) As PreprocessorState Return New PreprocessorState(Me._symbols, conditionals, Me._regionDirectives, Me._haveSeenRegionDirectives, Me._externalSourceDirective) End Function Friend ReadOnly Property RegionDirectiveStack As ImmutableStack(Of RegionDirectiveTriviaSyntax) Get Return _regionDirectives End Get End Property Friend ReadOnly Property HaveSeenRegionDirectives As Boolean Get Return _haveSeenRegionDirectives End Get End Property Private Function WithRegions(regions As ImmutableStack(Of RegionDirectiveTriviaSyntax)) As PreprocessorState Return New PreprocessorState(Me._symbols, Me._conditionals, regions, Me._haveSeenRegionDirectives OrElse regions.Count > 0, Me._externalSourceDirective) End Function Friend ReadOnly Property ExternalSourceDirective As ExternalSourceDirectiveTriviaSyntax Get Return _externalSourceDirective End Get End Property Private Function WithExternalSource(externalSource As ExternalSourceDirectiveTriviaSyntax) As PreprocessorState Return New PreprocessorState(Me._symbols, Me._conditionals, Me._regionDirectives, Me._haveSeenRegionDirectives, externalSource) End Function Friend Function InterpretConstDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState Debug.Assert(statement.Kind = SyntaxKind.ConstDirectiveTrivia) Dim constDirective = DirectCast(statement, ConstDirectiveTriviaSyntax) Dim value = ExpressionEvaluator.EvaluateExpression(constDirective.Value, _symbols) Dim err = value.ErrorId If err <> 0 Then statement = Parser.ReportSyntaxError(statement, err, value.ErrorArgs) End If Return SetSymbol(constDirective.Name.IdentifierText, value) End Function Friend Function InterpretExternalSourceDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState Dim externalSourceDirective = DirectCast(statement, ExternalSourceDirectiveTriviaSyntax) If _externalSourceDirective IsNot Nothing Then statement = Parser.ReportSyntaxError(statement, ERRID.ERR_NestedExternalSource) Return Me Else Return WithExternalSource(externalSourceDirective) End If End Function Friend Function InterpretEndExternalSourceDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState If _externalSourceDirective Is Nothing Then statement = Parser.ReportSyntaxError(statement, ERRID.ERR_EndExternalSource) Return Me Else Return WithExternalSource(Nothing) End If End Function Friend Function InterpretRegionDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState Dim regionDirective = DirectCast(statement, RegionDirectiveTriviaSyntax) Return WithRegions(_regionDirectives.Push(regionDirective)) End Function Friend Function InterpretEndRegionDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState If _regionDirectives.Count = 0 Then statement = Parser.ReportSyntaxError(statement, ERRID.ERR_EndRegionNoRegion) Return Me Else Return WithRegions(_regionDirectives.Pop()) End If End Function ' // Interpret a conditional compilation #if or #elseif. Friend Function InterpretIfDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState Debug.Assert(statement.Kind = SyntaxKind.IfDirectiveTrivia) Dim ifDirective = DirectCast(statement, IfDirectiveTriviaSyntax) ' TODO - Is the following comment still relevant? How should the error be reported? ' Evaluate the expression to detect errors, whether or not ' its result is needed. Dim value = ExpressionEvaluator.EvaluateCondition(ifDirective.Condition, _symbols) Dim err = value.ErrorId If err <> 0 Then statement = Parser.ReportSyntaxError(statement, err, value.ErrorArgs) End If Dim takeThisBranch = If(value.IsBad OrElse value.IsBooleanTrue, ConditionalState.BranchTakenState.Taken, ConditionalState.BranchTakenState.NotTaken) Return WithConditionals(_conditionals.Push(New ConditionalState(takeThisBranch, False, DirectCast(statement, IfDirectiveTriviaSyntax)))) End Function Friend Function InterpretElseIfDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState Dim condition As ConditionalState Dim conditionals = Me._conditionals If conditionals.Count = 0 Then statement = Parser.ReportSyntaxError(statement, ERRID.ERR_LbBadElseif) condition = New ConditionalState(ConditionalState.BranchTakenState.NotTaken, False, Nothing) Else condition = conditionals.Peek conditionals = conditionals.Pop If condition.ElseSeen Then statement = Parser.ReportSyntaxError(statement, ERRID.ERR_LbElseifAfterElse) End If End If ' TODO - Is the following comment still relevant? How should the error be reported? ' Evaluate the expression to detect errors, whether or not ' its result is needed. Dim ifDirective = DirectCast(statement, IfDirectiveTriviaSyntax) Dim value = ExpressionEvaluator.EvaluateCondition(ifDirective.Condition, _symbols) Dim err = value.ErrorId If err <> 0 Then statement = Parser.ReportSyntaxError(statement, err, value.ErrorArgs) End If Dim takeThisBranch = condition.BranchTaken If takeThisBranch = ConditionalState.BranchTakenState.Taken Then takeThisBranch = ConditionalState.BranchTakenState.AlreadyTaken ElseIf takeThisBranch = ConditionalState.BranchTakenState.NotTaken AndAlso Not value.IsBad AndAlso value.IsBooleanTrue Then takeThisBranch = ConditionalState.BranchTakenState.Taken End If condition = New ConditionalState(takeThisBranch, condition.ElseSeen, DirectCast(statement, IfDirectiveTriviaSyntax)) Return WithConditionals(conditionals.Push(condition)) End Function Friend Function InterpretElseDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState Dim conditionals = Me._conditionals If conditionals.Count = 0 Then ' If there has been no preceding #If, give an error and pretend that there ' had been one. statement = Parser.ReportSyntaxError(statement, ERRID.ERR_LbElseNoMatchingIf) Return WithConditionals(conditionals.Push(New ConditionalState(ConditionalState.BranchTakenState.Taken, True, Nothing))) End If Dim condition = conditionals.Peek conditionals = conditionals.Pop If condition.ElseSeen Then statement = Parser.ReportSyntaxError(statement, ERRID.ERR_LbElseNoMatchingIf) End If Dim takeThisBranch = condition.BranchTaken If takeThisBranch = ConditionalState.BranchTakenState.Taken Then takeThisBranch = ConditionalState.BranchTakenState.AlreadyTaken ElseIf takeThisBranch = ConditionalState.BranchTakenState.NotTaken Then takeThisBranch = ConditionalState.BranchTakenState.Taken End If condition = New ConditionalState(takeThisBranch, True, condition.IfDirective) Return WithConditionals(conditionals.Push(condition)) End Function Friend Function InterpretEndIfDirective(ByRef statement As DirectiveTriviaSyntax) As PreprocessorState If _conditionals.Count = 0 Then statement = Parser.ReportSyntaxError(statement, ERRID.ERR_LbNoMatchingIf) Return Me Else Return WithConditionals(_conditionals.Pop()) End If End Function Friend Function IsEquivalentTo(other As PreprocessorState) As Boolean ' for now, we will only consider two are equivalents when there are only regions but no other directives If Me._conditionals.Count > 0 OrElse Me._symbols.Count > 0 OrElse Me._externalSourceDirective IsNot Nothing OrElse other._conditionals.Count > 0 OrElse other._symbols.Count > 0 OrElse other._externalSourceDirective IsNot Nothing Then Return False End If If Me._regionDirectives.Count <> other._regionDirectives.Count Then Return False End If If Me._haveSeenRegionDirectives <> other._haveSeenRegionDirectives Then Return False End If Return True End Function End Class '//------------------------------------------------------------------------------------------------- '// '// Skip all text until the end of the current conditional section. This will also return the '// span of text that was skipped '// '//------------------------------------------------------------------------------------------------- Private Function SkipConditionalCompilationSection() As CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode) ' // If skipping encounters a nested #if, it is necessary to skip all of it through its ' // #end. NestedConditionalsToSkip keeps track of how many nested #if constructs ' // need skipping. Dim NestedConditionalsToSkip As Integer = 0 ' Accumulate span of text we're skipping. Dim startSkipped As Integer = -1 ' Start location of skipping Dim lengthSkipped As Integer = 0 ' Length of skipped text. While True Dim skippedSpan = Me.SkipToNextConditionalLine() If startSkipped < 0 Then startSkipped = skippedSpan.Start End If lengthSkipped += skippedSpan.Length Dim curToken = GetCurrentToken() Select Case curToken.Kind Case SyntaxKind.HashToken Dim nextKind = Me.PeekToken(1, ScannerState.VB).Kind Dim nextNextToken = Me.PeekToken(2, ScannerState.VB) If NestedConditionalsToSkip = 0 AndAlso ((nextKind = SyntaxKind.EndKeyword AndAlso Not IsContextualKeyword(nextNextToken, SyntaxKind.ExternalSourceKeyword, SyntaxKind.RegionKeyword)) OrElse nextKind = SyntaxKind.EndIfKeyword OrElse nextKind = SyntaxKind.ElseIfKeyword OrElse nextKind = SyntaxKind.ElseKeyword) Then ' // Finding one of these is sufficient to stop skipping. It is then necessary ' // to process the line as a conditional compilation line. The normal ' // parsing logic will do this. Exit While ElseIf nextKind = SyntaxKind.EndIfKeyword OrElse (nextKind = SyntaxKind.EndKeyword AndAlso Not IsContextualKeyword(nextNextToken, SyntaxKind.ExternalSourceKeyword, SyntaxKind.RegionKeyword)) Then NestedConditionalsToSkip -= 1 ElseIf nextKind = SyntaxKind.IfKeyword Then NestedConditionalsToSkip += 1 End If ' if the line concluded the skip block, return from the loop If NestedConditionalsToSkip < 0 Then Debug.Assert(NestedConditionalsToSkip = -1, "preprocessor skip underflow") Exit While End If ' skip over # token lengthSkipped += GetCurrentToken.FullWidth GetNextTokenInState(ScannerState.VB) ' Skip over terminator token to avoid counting it twice because it is already trivia on current token If nextKind = SyntaxKind.StatementTerminatorToken OrElse nextKind = SyntaxKind.ColonToken Then GetNextTokenInState(ScannerState.VB) End If Case SyntaxKind.DateLiteralToken, SyntaxKind.BadToken 'Dev10 #777522 Do not confuse Date literal with an end of conditional block. ' skip over date literal token lengthSkipped += GetCurrentToken.FullWidth GetNextTokenInState(ScannerState.VB) Dim nextKind = GetCurrentToken().Kind ' Skip over terminator token to avoid counting it twice because it is already trivia on current token If nextKind = SyntaxKind.StatementTerminatorToken OrElse nextKind = SyntaxKind.ColonToken Then GetNextTokenInState(ScannerState.VB) End If Case SyntaxKind.EndOfFileToken Exit While Case Else Throw ExceptionUtilities.UnexpectedValue(curToken.Kind) End Select End While If lengthSkipped > 0 Then Return New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode)(Me.GetDisabledTextAt(New TextSpan(startSkipped, lengthSkipped))) Else Return New CodeAnalysis.Syntax.InternalSyntax.SyntaxList(Of VisualBasicSyntaxNode)(Nothing) End If End Function ' // If compilation ends in the middle of a non-skipped conditional section, ' // produce appropriate diagnostics. Friend Function RecoverFromMissingConditionalEnds(eof As PunctuationSyntax, <Out> ByRef notClosedIfDirectives As ArrayBuilder(Of IfDirectiveTriviaSyntax), <Out> ByRef notClosedRegionDirectives As ArrayBuilder(Of RegionDirectiveTriviaSyntax), <Out> ByRef haveRegionDirectives As Boolean, <Out> ByRef notClosedExternalSourceDirective As ExternalSourceDirectiveTriviaSyntax) As PunctuationSyntax notClosedIfDirectives = Nothing notClosedRegionDirectives = Nothing If Me._scannerPreprocessorState.ConditionalStack.Count > 0 Then For Each state In _scannerPreprocessorState.ConditionalStack Dim ifDirective As IfDirectiveTriviaSyntax = state.IfDirective If ifDirective IsNot Nothing Then If notClosedIfDirectives Is Nothing Then notClosedIfDirectives = ArrayBuilder(Of IfDirectiveTriviaSyntax).GetInstance() End If notClosedIfDirectives.Add(ifDirective) End If Next If notClosedIfDirectives Is Nothing Then ' #If directive is not found eof = Parser.ReportSyntaxError(eof, ERRID.ERR_LbExpectedEndIf) End If End If If Me._scannerPreprocessorState.RegionDirectiveStack.Count > 0 Then notClosedRegionDirectives = ArrayBuilder(Of RegionDirectiveTriviaSyntax).GetInstance() notClosedRegionDirectives.AddRange(Me._scannerPreprocessorState.RegionDirectiveStack) End If haveRegionDirectives = Me._scannerPreprocessorState.HaveSeenRegionDirectives notClosedExternalSourceDirective = Me._scannerPreprocessorState.ExternalSourceDirective Return eof End Function End Class End Namespace