|
' 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.Immutable
Imports System.Diagnostics
Imports System.Runtime.InteropServices
Imports Microsoft.CodeAnalysis.PooledObjects
Imports Microsoft.CodeAnalysis.Text
Imports Microsoft.CodeAnalysis.VisualBasic.Symbols
Imports Microsoft.CodeAnalysis.VisualBasic.Syntax
Imports TypeKind = Microsoft.CodeAnalysis.TypeKind
Namespace Microsoft.CodeAnalysis.VisualBasic
Partial Friend NotInheritable Class LocalRewriter
''' <summary>
''' A using statement of the form:
''' using Expression
''' list_of_statements
''' end using
'''
''' will be rewritten into:
'''
''' temp = Expression
''' Try
''' list_of_statements
''' Finally
''' If Temp IsNot Nothing Then
''' CType(temp, IDisposable).Dispose()
''' End If
''' End Try
'''
''' when the resource is a using locally declared variable no temporary is generated but the variable is read-only
''' A using statement of the form:
''' Using v As New MyDispose()
''' list_of_statements
''' End Using
'''
''' is rewritten to:
'''
''' Dim v As New MyDispose()
''' Try
''' list_of_statements
''' Finally
''' If v IsNot Nothing Then
''' CType(v, IDisposable).Dispose()
''' End If
''' End Try
'''
''' A using with multiple variable resources are equivalent to a nested using statement.
''' So a using statement of the form:
''' Using v1 As New MyDispose(), v2 As myDispose = New MyDispose()
''' list_of_statements
''' end using
'''
''' is rewritten to:
''' Dim v1 As New MyDispose
''' Try
''' Dim v2 As MyDispose = new MyDispose()
''' Try
''' list_of_statements
''' Finally
''' If v2 IsNot Nothing Then
''' CType(v2, IDisposable).Dispose()
''' End If
''' End Try
''' Finally
''' If v1 IsNot Nothing Then
''' CType(v1, IDisposable).Dispose()
''' End If
''' end try
'''</summary>
Public Overrides Function VisitUsingStatement(node As BoundUsingStatement) As BoundNode
Dim saveState As UnstructuredExceptionHandlingContext = LeaveUnstructuredExceptionHandlingContext(node)
Dim blockSyntax = DirectCast(node.Syntax, UsingBlockSyntax)
' rewrite the original using body only once here.
Dim currentBody = DirectCast(Visit(node.Body), BoundBlock)
Dim locals As ImmutableArray(Of LocalSymbol) = node.Locals
Dim placeholderInfo As ValueTuple(Of BoundRValuePlaceholder, BoundExpression, BoundExpression)
' the initialization expressions (variable declaration & expression case) will be rewritten in
' "RewriteToInnerTryConstructForVariableDeclaration" to avoid code duplication
If Not node.ResourceList.IsDefault Then
' Case "Using <variable declarations>"
' the try statements will be nested. To avoid re-rewriting we're iterating through the resource list in reverse
For declarationIndex = node.ResourceList.Length - 1 To 0 Step -1
Dim localDeclaration = node.ResourceList(declarationIndex)
If localDeclaration.Kind = BoundKind.LocalDeclaration Then
Dim localVariableDeclaration = DirectCast(localDeclaration, BoundLocalDeclaration)
placeholderInfo = node.UsingInfo.PlaceholderInfo(localVariableDeclaration.LocalSymbol.Type)
currentBody = RewriteSingleUsingToTryFinally(node,
declarationIndex,
localDeclaration.Syntax,
localVariableDeclaration.LocalSymbol,
localVariableDeclaration.InitializerOpt,
placeholderInfo,
currentBody)
Else
Dim localAsNewDeclaration = DirectCast(localDeclaration, BoundAsNewLocalDeclarations)
Dim variableCount = localAsNewDeclaration.LocalDeclarations.Length
placeholderInfo = node.UsingInfo.PlaceholderInfo(localAsNewDeclaration.LocalDeclarations.First.LocalSymbol.Type)
For initializedVariableIndex = localAsNewDeclaration.LocalDeclarations.Length - 1 To 0 Step -1
currentBody = RewriteSingleUsingToTryFinally(node,
declarationIndex,
localDeclaration.Syntax,
localAsNewDeclaration.LocalDeclarations(initializedVariableIndex).LocalSymbol,
localAsNewDeclaration.Initializer,
placeholderInfo,
currentBody)
Next
End If
Next
Else
' Case "Using <expression>"
Debug.Assert(node.ResourceExpressionOpt IsNot Nothing)
Dim initializationExpression = node.ResourceExpressionOpt
placeholderInfo = node.UsingInfo.PlaceholderInfo(initializationExpression.Type)
Dim tempResourceSymbol As LocalSymbol = New SynthesizedLocal(Me._currentMethodOrLambda,
initializationExpression.Type,
SynthesizedLocalKind.Using,
blockSyntax.UsingStatement)
currentBody = RewriteSingleUsingToTryFinally(node,
resourceIndex:=0, ' There is only one resource - the expression
resourceSyntax:=blockSyntax,
tempResourceSymbol,
initializationExpression,
placeholderInfo,
currentBody)
locals = locals.Add(tempResourceSymbol)
End If
RestoreUnstructuredExceptionHandlingContext(node, saveState)
Dim statements As ImmutableArray(Of BoundStatement) = currentBody.Statements
If ShouldGenerateUnstructuredExceptionHandlingResumeCode(node) Then
statements = RegisterUnstructuredExceptionHandlingResumeTarget(node.Syntax, canThrow:=True).Concat(statements)
End If
currentBody = New BoundBlock(node.Syntax,
currentBody.StatementListSyntax,
locals,
statements)
Dim prologue As BoundStatement = Nothing
If Instrument(node) Then
' create a sequence point that contains the whole using statement as the first reachable sequence point
' of the using statement. The resource variables are not yet in scope.
prologue = _instrumenterOpt.CreateUsingStatementPrologue(node)
End If
If prologue IsNot Nothing Then
Return New BoundStatementList(node.UsingInfo.UsingStatementSyntax, ImmutableArray.Create(Of BoundStatement)(prologue, currentBody))
Else
Return New BoundStatementList(node.UsingInfo.UsingStatementSyntax, ImmutableArray.Create(Of BoundStatement)(currentBody))
End If
End Function
''' <summary>
''' Creates a TryFinally Statement for the given resource.
'''
''' This method creates the following for the arguments:
''' <localSymbol> = <initializationExpression>
''' Try
''' <currentBody>
''' Finally
''' If <disposeCondition> Then
''' <disposeConversion>.Dispose()
''' End If
''' End Try
'''
''' Note: this is used for both kinds of using statements (resource locals and resource expressions).
'''
''' </summary>
''' <returns>The new bound block containing the assignment of the initialization and the try/finally statement with
''' the passed body.</returns>
Private Function RewriteSingleUsingToTryFinally(
node As BoundUsingStatement,
resourceIndex As Integer,
resourceSyntax As SyntaxNode,
localSymbol As LocalSymbol,
initializationExpression As BoundExpression,
ByRef placeholderInfo As ValueTuple(Of BoundRValuePlaceholder, BoundExpression, BoundExpression),
currentBody As BoundBlock
) As BoundBlock
Dim resourceType = localSymbol.Type
Dim boundResourceLocal As BoundLocal = New BoundLocal(resourceSyntax, localSymbol, isLValue:=True, type:=resourceType)
Dim resourcePlaceholder As BoundRValuePlaceholder = placeholderInfo.Item1
Dim disposeConversion As BoundExpression = placeholderInfo.Item2
Dim disposeCondition As BoundExpression = placeholderInfo.Item3
AddPlaceholderReplacement(resourcePlaceholder, boundResourceLocal.MakeRValue())
' add a sequence point to stop on the "End Using" statement
' because there are a lot of hidden sequence points between the dispose call and the "end using" in Roslyn
' (caused by emitting try catch), we need to add a sequence point after each call with the syntax of the end using
' to match the Dev10 debugging experience.
Dim newBody = DirectCast(Concat(currentBody, SyntheticBoundNodeFactory.HiddenSequencePoint()), BoundBlock)
' assign initialization to variable
Dim boundResourceInitializationAssignment As BoundStatement = New BoundAssignmentOperator(resourceSyntax,
boundResourceLocal,
VisitAndGenerateObjectCloneIfNeeded(initializationExpression, suppressObjectClone:=True),
suppressObjectClone:=True,
type:=resourceType).ToStatement
Dim instrument As Boolean = Me.Instrument(node)
If instrument Then
boundResourceInitializationAssignment = _instrumenterOpt.InstrumentUsingStatementResourceCapture(node, resourceIndex, boundResourceInitializationAssignment)
End If
' create if statement with dispose call
Dim disposeCall = GenerateDisposeCallForForeachAndUsing(resourceSyntax, boundResourceLocal,
VisitExpressionNode(disposeCondition), True,
VisitExpressionNode(disposeConversion))
Dim disposePrologue As BoundStatement = Nothing
If instrument Then
' The block should start with a sequence point that points to the "End Using" statement. This is required in order to
' highlight the end using when someone step next after the last statement of the original body and in case an exception
' was thrown.
disposePrologue = _instrumenterOpt.CreateUsingStatementDisposePrologue(node)
End If
Dim finallyStatements As ImmutableArray(Of BoundStatement)
If disposePrologue IsNot Nothing Then
finallyStatements = ImmutableArray.Create(disposePrologue, disposeCall)
Else
finallyStatements = ImmutableArray.Create(disposeCall)
End If
' create finally block from the dispose call
Dim finallyBlock = New BoundBlock(resourceSyntax,
statementListSyntax:=Nothing,
locals:=ImmutableArray(Of LocalSymbol).Empty,
finallyStatements)
' rewrite try/finally block
Dim tryFinally = RewriteTryStatement(resourceSyntax, newBody, ImmutableArray(Of BoundCatchBlock).Empty, finallyBlock, exitLabelOpt:=Nothing)
newBody = New BoundBlock(resourceSyntax,
statementListSyntax:=Nothing,
locals:=ImmutableArray(Of LocalSymbol).Empty,
ImmutableArray.Create(boundResourceInitializationAssignment, tryFinally))
RemovePlaceholderReplacement(resourcePlaceholder)
Return newBody
End Function
End Class
End Namespace
|