|
' 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.
Imports System.Collections.Generic
Imports System.Collections.Immutable
Imports System.Diagnostics
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Namespace Microsoft.CodeAnalysis.VisualBasic
''' <summary>
''' A region analysis walker that records reads and writes of all variables, both inside and outside the region.
''' </summary>
Friend Class ReadWriteWalker
Inherits AbstractRegionDataFlowPass
Friend Overloads Shared Sub Analyze(info As FlowAnalysisInfo, region As FlowAnalysisRegionInfo,
ByRef readInside As IEnumerable(Of Symbol),
ByRef writtenInside As IEnumerable(Of Symbol),
ByRef readOutside As IEnumerable(Of Symbol),
ByRef writtenOutside As IEnumerable(Of Symbol),
ByRef captured As IEnumerable(Of Symbol),
ByRef capturedInside As IEnumerable(Of Symbol),
ByRef capturedOutside As IEnumerable(Of Symbol))
Dim walker = New ReadWriteWalker(info, region)
Try
If walker.Analyze() Then
readInside = walker._readInside
writtenInside = walker._writtenInside
readOutside = walker._readOutside
writtenOutside = walker._writtenOutside
captured = walker._captured
capturedInside = walker._capturedInside
capturedOutside = walker._capturedOutside
Else
readInside = Enumerable.Empty(Of Symbol)()
writtenInside = readInside
readOutside = readInside
writtenOutside = readInside
captured = readInside
capturedInside = readInside
capturedOutside = readInside
End If
Finally
walker.Free()
End Try
End Sub
Private ReadOnly _readInside As HashSet(Of Symbol) = New HashSet(Of Symbol)()
Private ReadOnly _writtenInside As HashSet(Of Symbol) = New HashSet(Of Symbol)()
Private ReadOnly _readOutside As HashSet(Of Symbol) = New HashSet(Of Symbol)()
Private ReadOnly _writtenOutside As HashSet(Of Symbol) = New HashSet(Of Symbol)()
Private ReadOnly _captured As HashSet(Of Symbol) = New HashSet(Of Symbol)()
Private ReadOnly _capturedInside As HashSet(Of Symbol) = New HashSet(Of Symbol)()
Private ReadOnly _capturedOutside As HashSet(Of Symbol) = New HashSet(Of Symbol)()
Private _currentMethodOrLambda As Symbol
Private _currentQueryLambda As BoundQueryLambda
Protected Overrides Sub NoteRead(variable As Symbol)
If IsCompilerGeneratedTempLocal(variable) Then
MyBase.NoteRead(variable)
Else
Select Case Me._regionPlace
Case RegionPlace.Before, RegionPlace.After
_readOutside.Add(variable)
Case RegionPlace.Inside
_readInside.Add(variable)
Case Else
Throw ExceptionUtilities.UnexpectedValue(Me._regionPlace)
End Select
MyBase.NoteRead(variable)
CheckCaptured(variable)
End If
End Sub
Protected Overrides Sub NoteWrite(variable As Symbol, value As BoundExpression)
If IsCompilerGeneratedTempLocal(variable) Then
MyBase.NoteWrite(variable, value)
Else
Select Case Me._regionPlace
Case RegionPlace.Before, RegionPlace.After
_writtenOutside.Add(variable)
Case RegionPlace.Inside
_writtenInside.Add(variable)
Case Else
Throw ExceptionUtilities.UnexpectedValue(Me._regionPlace)
End Select
MyBase.NoteWrite(variable, value)
CheckCaptured(variable)
End If
End Sub
' range variables are only returned in the captured set if inside the region
Private Sub NoteCaptured(variable As Symbol)
If _regionPlace = RegionPlace.Inside Then
_capturedInside.Add(variable)
_captured.Add(variable)
ElseIf variable.Kind <> SymbolKind.RangeVariable Then
_capturedOutside.Add(variable)
_captured.Add(variable)
End If
End Sub
Protected Overrides Sub NoteRead(fieldAccess As BoundFieldAccess)
MyBase.NoteRead(fieldAccess)
If (Me._regionPlace <> RegionPlace.Inside AndAlso fieldAccess.Syntax.Span.Contains(_region)) Then NoteReceiverRead(fieldAccess)
End Sub
Protected Overrides Sub NoteWrite(node As BoundExpression, value As BoundExpression)
MyBase.NoteWrite(node, value)
If node.Kind = BoundKind.FieldAccess Then NoteReceiverWritten(CType(node, BoundFieldAccess))
End Sub
Private Sub NoteReceiverRead(fieldAccess As BoundFieldAccess)
NoteReceiverReadOrWritten(fieldAccess, Me._readInside)
End Sub
Private Sub NoteReceiverWritten(fieldAccess As BoundFieldAccess)
NoteReceiverReadOrWritten(fieldAccess, Me._writtenInside)
End Sub
Private Sub NoteReceiverReadOrWritten(fieldAccess As BoundFieldAccess, readOrWritten As HashSet(Of Symbol))
If fieldAccess.FieldSymbol.IsShared Then Return
If fieldAccess.FieldSymbol.ContainingType.IsReferenceType Then Return
Dim receiver = fieldAccess.ReceiverOpt
If receiver Is Nothing Then Return
Dim receiverSyntax = receiver.Syntax
If receiverSyntax Is Nothing Then Return
Select Case receiver.Kind
Case BoundKind.Local
If _region.Contains(receiverSyntax.Span) Then readOrWritten.Add(CType(receiver, BoundLocal).LocalSymbol)
Case BoundKind.MeReference
If _region.Contains(receiverSyntax.Span) Then readOrWritten.Add(Me.MeParameter)
Case BoundKind.MyBaseReference
If _region.Contains(receiverSyntax.Span) Then readOrWritten.Add(Me.MeParameter)
Case BoundKind.Parameter
If _region.Contains(receiverSyntax.Span) Then readOrWritten.Add(CType(receiver, BoundParameter).ParameterSymbol)
Case BoundKind.RangeVariable
If _region.Contains(receiverSyntax.Span) Then readOrWritten.Add(CType(receiver, BoundRangeVariable).RangeVariable)
Case BoundKind.FieldAccess
If receiver.Type.IsStructureType AndAlso receiverSyntax.Span.OverlapsWith(_region) Then NoteReceiverReadOrWritten(CType(receiver, BoundFieldAccess), readOrWritten)
End Select
End Sub
Private Shared Function IsCompilerGeneratedTempLocal(variable As Symbol) As Boolean
Return TypeOf (variable) Is SynthesizedLocal
End Function
Private Sub CheckCaptured(variable As Symbol)
' Query range variables are read-only, even if they are captured at the end, they are
' effectively captured ByValue, so IDE probably doesn't have to know about the capture
' at all.
Select Case variable.Kind
Case SymbolKind.Local
Dim local = DirectCast(variable, LocalSymbol)
If Not local.IsConst AndAlso Me._currentMethodOrLambda <> local.ContainingSymbol Then
Me.NoteCaptured(local)
End If
Case SymbolKind.Parameter
Dim param = DirectCast(variable, ParameterSymbol)
If Me._currentMethodOrLambda <> param.ContainingSymbol Then
Me.NoteCaptured(param)
End If
Case SymbolKind.RangeVariable
Dim range = DirectCast(variable, RangeVariableSymbol)
If Me._currentMethodOrLambda <> range.ContainingSymbol AndAlso
Me._currentQueryLambda IsNot Nothing AndAlso ' might be Nothing in error scenarios
(Me._currentMethodOrLambda <> Me._currentQueryLambda.LambdaSymbol OrElse
Not Me._currentQueryLambda.RangeVariables.Contains(range)) Then
Me.NoteCaptured(range) ' Range variables only captured if in region
End If
End Select
End Sub
Public Overrides Function VisitLambda(node As BoundLambda) As BoundNode
Dim previousMethod = Me._currentMethodOrLambda
Me._currentMethodOrLambda = node.LambdaSymbol
Dim result = MyBase.VisitLambda(node)
Me._currentMethodOrLambda = previousMethod
Return result
End Function
Public Overrides Function VisitQueryLambda(node As BoundQueryLambda) As BoundNode
Dim previousMethod = Me._currentMethodOrLambda
Me._currentMethodOrLambda = node.LambdaSymbol
Dim previousQueryLambda = Me._currentQueryLambda
Me._currentQueryLambda = node
Dim result = MyBase.VisitQueryLambda(node)
Me._currentMethodOrLambda = previousMethod
Me._currentQueryLambda = previousQueryLambda
Return result
End Function
Public Overrides Function VisitQueryableSource(node As BoundQueryableSource) As BoundNode
If Not node.WasCompilerGenerated AndAlso node.RangeVariableOpt IsNot Nothing Then
' Adding range variables into a scope, note write for them.
Debug.Assert(node.RangeVariables.Length = 1)
NoteWrite(node.RangeVariableOpt, Nothing)
End If
VisitRvalue(node.Source)
Return Nothing
End Function
Public Overrides Function VisitRangeVariableAssignment(node As BoundRangeVariableAssignment) As BoundNode
If Not node.WasCompilerGenerated Then
NoteWrite(node.RangeVariable, Nothing)
End If
VisitRvalue(node.Value)
Return Nothing
End Function
Private Overloads Function Analyze() As Boolean
Return Scan()
End Function
Private Sub New(info As FlowAnalysisInfo, region As FlowAnalysisRegionInfo)
MyBase.New(info, region)
_currentMethodOrLambda = symbol
End Sub
End Class
End Namespace
|