/
LocalRewriter_FixedStatement.cs
560 lines (483 loc) · 26.3 KB
/
LocalRewriter_FixedStatement.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
// 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.
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Diagnostics;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.PooledObjects;
using Roslyn.Utilities;
namespace Microsoft.CodeAnalysis.CSharp
{
internal sealed partial class LocalRewriter
{
public override BoundNode VisitFixedStatement(BoundFixedStatement node)
{
ImmutableArray<BoundLocalDeclaration> localDecls = node.Declarations.LocalDeclarations;
int numFixedLocals = localDecls.Length;
var localBuilder = ArrayBuilder<LocalSymbol>.GetInstance(node.Locals.Length);
localBuilder.AddRange(node.Locals);
var statementBuilder = ArrayBuilder<BoundStatement>.GetInstance(numFixedLocals + 1 + 1); //+1 for body, +1 for hidden seq point
var cleanup = new BoundStatement[numFixedLocals];
for (int i = 0; i < numFixedLocals; i++)
{
BoundLocalDeclaration localDecl = localDecls[i];
LocalSymbol pinnedTemp;
statementBuilder.Add(InitializeFixedStatementLocal(localDecl, _factory, out pinnedTemp));
localBuilder.Add(pinnedTemp);
// NOTE: Dev10 nulls out the locals in declaration order (as opposed to "popping" them in reverse order).
if (pinnedTemp.RefKind == RefKind.None)
{
// temp = null;
cleanup[i] = _factory.Assignment(_factory.Local(pinnedTemp), _factory.Null(pinnedTemp.Type));
}
else
{
Debug.Assert(!pinnedTemp.Type.IsManagedTypeNoUseSiteDiagnostics);
// temp = ref *default(T*);
cleanup[i] = _factory.Assignment(_factory.Local(pinnedTemp), new BoundPointerIndirectionOperator(
_factory.Syntax,
_factory.Default(new PointerTypeSymbol(pinnedTemp.TypeWithAnnotations)),
pinnedTemp.Type),
isRef: true);
}
}
BoundStatement? rewrittenBody = VisitStatement(node.Body);
Debug.Assert(rewrittenBody is { });
statementBuilder.Add(rewrittenBody);
statementBuilder.Add(_factory.HiddenSequencePoint());
Debug.Assert(statementBuilder.Count == numFixedLocals + 1 + 1);
// In principle, the cleanup code (i.e. nulling out the pinned variables) is always
// in a finally block. However, we can optimize finally away (keeping the cleanup
// code) in cases where both of the following are true:
// 1) there are no branches out of the fixed statement; and
// 2) the fixed statement is not in a try block (syntactic or synthesized).
if (IsInTryBlock(node) || HasGotoOut(rewrittenBody))
{
return _factory.Block(
localBuilder.ToImmutableAndFree(),
new BoundTryStatement(
_factory.Syntax,
_factory.Block(statementBuilder.ToImmutableAndFree()),
ImmutableArray<BoundCatchBlock>.Empty,
_factory.Block(cleanup)));
}
else
{
statementBuilder.AddRange(cleanup);
return _factory.Block(localBuilder.ToImmutableAndFree(), statementBuilder.ToImmutableAndFree());
}
}
/// <summary>
/// Basically, what we need to know is, if an exception occurred within the fixed statement, would
/// additional code in the current method be executed before its stack frame was popped?
/// </summary>
private static bool IsInTryBlock(BoundFixedStatement boundFixed)
{
SyntaxNode? node = boundFixed.Syntax.Parent;
Debug.Assert(node is { });
while (node != null)
{
switch (node.Kind())
{
case SyntaxKind.TryStatement:
// NOTE: if we started in the catch or finally of this try statement,
// we will have bypassed this node.
return true;
case SyntaxKind.UsingStatement:
// ACASEY: In treating using statements as try-finally's, we're following
// Dev11. The practical explanation for Dev11's behavior is that using
// statements have already been lowered by the time the check is performed.
// A more thoughtful explanation is that user code could run between the
// raising of an exception and the unwinding of the stack (via Dispose())
// and that user code would likely appreciate the reduced memory pressure
// of having the fixed local unpinned.
// NOTE: As in Dev11, we're not emitting a try-finally if the fixed
// statement is nested within a lock statement. Practically, dev11
// probably lowers locks after fixed statement, and so, does not see
// the try-finally. More thoughtfully, no user code will run in the
// finally statement, so it's not necessary.
// BREAK: Takes into account whether an outer fixed statement will be
// lowered into a try-finally block and responds accordingly. This is
// unnecessary since nothing will ever be allocated in the finally
// block of a lowered fixed statement, so memory pressure is not an
// issue. Note that only nested fixed statements where the outer (but
// not the inner) fixed statement has an unmatched goto, but is not
// contained in a try-finally, will be affected. e.g.
// fixed (...) {
// fixed (...) { }
// goto L1: ;
// }
return true;
case SyntaxKind.ForEachStatement:
case SyntaxKind.ForEachVariableStatement:
// We're being conservative here - there's actually only
// a try block if the enumerator is disposable, but we
// can't tell that from the syntax. Dev11 checks in the
// lowered tree, so it is more precise.
return true;
case SyntaxKind.SimpleLambdaExpression:
case SyntaxKind.ParenthesizedLambdaExpression:
case SyntaxKind.AnonymousMethodExpression:
// Stop looking.
return false;
case SyntaxKind.CatchClause:
// If we're in the catch of a try-catch-finally, then
// we're still in the scope of the try-finally handler.
Debug.Assert(node.Parent is TryStatementSyntax);
if (((TryStatementSyntax)node.Parent).Finally != null)
{
return true;
}
goto case SyntaxKind.FinallyClause;
case SyntaxKind.FinallyClause:
// Skip past the enclosing try to avoid a false positive.
node = node.Parent;
Debug.Assert(node is { } && node.Kind() == SyntaxKind.TryStatement);
node = node.Parent;
break;
default:
if (node is MemberDeclarationSyntax)
{
// Stop looking.
return false;
}
node = node.Parent;
break;
}
}
return false;
}
/// <summary>
/// If two (or more) fixed statements are nested, then we want to avoid having the outer
/// fixed statement re-traverse the lowered bound tree of the inner one. We accomplish
/// this by having each fixed statement cache a set of unmatched gotos that can be
/// reused by any containing fixed statements.
/// </summary>
private Dictionary<BoundNode, HashSet<LabelSymbol>>? _lazyUnmatchedLabelCache;
/// <summary>
/// Look for gotos without corresponding labels in the lowered body of a fixed statement.
/// </summary>
/// <remarks>
/// Assumes continue, break, etc have already been rewritten to gotos.
/// </remarks>
private bool HasGotoOut(BoundNode node)
{
if (_lazyUnmatchedLabelCache == null)
{
_lazyUnmatchedLabelCache = new Dictionary<BoundNode, HashSet<LabelSymbol>>();
}
HashSet<LabelSymbol> unmatched = UnmatchedGotoFinder.Find(node, _lazyUnmatchedLabelCache, RecursionDepth);
_lazyUnmatchedLabelCache.Add(node, unmatched);
return unmatched != null && unmatched.Count > 0;
}
public override BoundNode VisitFixedLocalCollectionInitializer(BoundFixedLocalCollectionInitializer node)
{
throw ExceptionUtilities.Unreachable; //Should be handled by VisitFixedStatement
}
private BoundStatement InitializeFixedStatementLocal(
BoundLocalDeclaration localDecl,
SyntheticBoundNodeFactory factory,
out LocalSymbol pinnedTemp)
{
BoundExpression? initializer = localDecl.InitializerOpt;
Debug.Assert(!ReferenceEquals(initializer, null));
LocalSymbol localSymbol = localDecl.LocalSymbol;
var fixedCollectionInitializer = (BoundFixedLocalCollectionInitializer)initializer;
if (fixedCollectionInitializer.GetPinnableOpt is { })
{
return InitializeFixedStatementGetPinnable(localDecl, localSymbol, fixedCollectionInitializer, factory, out pinnedTemp);
}
else if (fixedCollectionInitializer.Expression.Type is { SpecialType: SpecialType.System_String })
{
return InitializeFixedStatementStringLocal(localDecl, localSymbol, fixedCollectionInitializer, factory, out pinnedTemp);
}
else if (fixedCollectionInitializer.Expression.Type is { TypeKind: TypeKind.Array })
{
return InitializeFixedStatementArrayLocal(localDecl, localSymbol, fixedCollectionInitializer, factory, out pinnedTemp);
}
else
{
return InitializeFixedStatementRegularLocal(localDecl, localSymbol, fixedCollectionInitializer, factory, out pinnedTemp);
}
}
/// <summary>
/// <![CDATA[
/// fixed(int* ptr = &v){ ... } == becomes ===>
///
/// pinned ref int pinnedTemp = ref v; // pinning managed ref
/// int* ptr = (int*)&pinnedTemp; // unsafe cast to unmanaged ptr
/// . . .
/// ]]>
/// </summary>
private BoundStatement InitializeFixedStatementRegularLocal(
BoundLocalDeclaration localDecl,
LocalSymbol localSymbol,
BoundFixedLocalCollectionInitializer fixedInitializer,
SyntheticBoundNodeFactory factory,
out LocalSymbol pinnedTemp)
{
TypeSymbol localType = localSymbol.Type;
BoundExpression initializerExpr = VisitExpression(fixedInitializer.Expression);
Debug.Assert(initializerExpr.Type is { TypeKind: TypeKind.Pointer });
// initializer expr should be either an address(&) of something or a fixed field access.
// either should lower into addressof
Debug.Assert(initializerExpr.Kind == BoundKind.AddressOfOperator);
TypeSymbol initializerType = ((PointerTypeSymbol)initializerExpr.Type).PointedAtType;
// initializer expressions are bound/lowered right into addressof operators here
// that is a bit too far
// we need to pin the underlying field, and only then take the address.
initializerExpr = ((BoundAddressOfOperator)initializerExpr).Operand;
// intervening parens may have been skipped by the binder; find the declarator
VariableDeclaratorSyntax? declarator = fixedInitializer.Syntax.FirstAncestorOrSelf<VariableDeclaratorSyntax>();
Debug.Assert(declarator != null);
pinnedTemp = factory.SynthesizedLocal(
initializerType,
syntax: declarator,
isPinned: true,
//NOTE: different from the array and string cases
// RefReadOnly to allow referring to readonly variables. (technically we only "read" through the temp anyways)
refKind: RefKind.RefReadOnly,
kind: SynthesizedLocalKind.FixedReference);
// NOTE: we pin the reference, not the pointer.
Debug.Assert(pinnedTemp.IsPinned);
Debug.Assert(!localSymbol.IsPinned);
// pinnedTemp = ref v;
BoundStatement pinnedTempInit = factory.Assignment(factory.Local(pinnedTemp), initializerExpr, isRef: true);
// &pinnedTemp
var addr = new BoundAddressOfOperator(
factory.Syntax,
factory.Local(pinnedTemp),
type: fixedInitializer.ElementPointerType);
// (int*)&pinnedTemp
var pointerValue = factory.Convert(
localType,
addr,
fixedInitializer.ElementPointerTypeConversion);
// ptr = (int*)&pinnedTemp;
BoundStatement localInit = InstrumentLocalDeclarationIfNecessary(localDecl, localSymbol,
factory.Assignment(factory.Local(localSymbol), pointerValue));
return factory.Block(pinnedTempInit, localInit);
}
/// <summary>
/// <![CDATA[
/// fixed(int* ptr = &v){ ... } == becomes ===>
///
/// pinned ref int pinnedTemp = ref v; // pinning managed ref
/// int* ptr = (int*)&pinnedTemp; // unsafe cast to unmanaged ptr
/// . . .
/// ]]>
/// </summary>
private BoundStatement InitializeFixedStatementGetPinnable(
BoundLocalDeclaration localDecl,
LocalSymbol localSymbol,
BoundFixedLocalCollectionInitializer fixedInitializer,
SyntheticBoundNodeFactory factory,
out LocalSymbol pinnedTemp)
{
TypeSymbol localType = localSymbol.Type;
BoundExpression initializerExpr = VisitExpression(fixedInitializer.Expression);
Debug.Assert(initializerExpr.Type is { });
var initializerType = initializerExpr.Type;
var initializerSyntax = initializerExpr.Syntax;
var getPinnableMethod = fixedInitializer.GetPinnableOpt;
Debug.Assert(getPinnableMethod is { });
// intervening parens may have been skipped by the binder; find the declarator
VariableDeclaratorSyntax? declarator = fixedInitializer.Syntax.FirstAncestorOrSelf<VariableDeclaratorSyntax>();
Debug.Assert(declarator != null);
// pinned ref int pinnedTemp
pinnedTemp = factory.SynthesizedLocal(
getPinnableMethod.ReturnType,
syntax: declarator,
isPinned: true,
//NOTE: different from the array and string cases
// RefReadOnly to allow referring to readonly variables. (technically we only "read" through the temp anyways)
refKind: RefKind.RefReadOnly,
kind: SynthesizedLocalKind.FixedReference);
BoundExpression callReceiver;
int currentConditionalAccessID = 0;
bool needNullCheck = !initializerType.IsValueType;
if (needNullCheck)
{
currentConditionalAccessID = ++_currentConditionalAccessID;
callReceiver = new BoundConditionalReceiver(
initializerSyntax,
currentConditionalAccessID,
initializerType);
}
else
{
callReceiver = initializerExpr;
}
// .GetPinnable()
var getPinnableCall = getPinnableMethod.IsStatic ?
factory.Call(null, getPinnableMethod, callReceiver) :
factory.Call(callReceiver, getPinnableMethod);
// temp =ref .GetPinnable()
var tempAssignment = factory.AssignmentExpression(
factory.Local(pinnedTemp),
getPinnableCall,
isRef: true);
// &pinnedTemp
var addr = new BoundAddressOfOperator(
factory.Syntax,
factory.Local(pinnedTemp),
type: fixedInitializer.ElementPointerType);
// (int*)&pinnedTemp
var pointerValue = factory.Convert(
localType,
addr,
fixedInitializer.ElementPointerTypeConversion);
// {pinnedTemp =ref .GetPinnable(), (int*)&pinnedTemp}
BoundExpression pinAndGetPtr = factory.Sequence(
locals: ImmutableArray<LocalSymbol>.Empty,
sideEffects: ImmutableArray.Create<BoundExpression>(tempAssignment),
result: pointerValue);
if (needNullCheck)
{
// initializer?.{temp =ref .GetPinnable(), (int*)&pinnedTemp} ?? default;
pinAndGetPtr = new BoundLoweredConditionalAccess(
initializerSyntax,
initializerExpr,
hasValueMethodOpt: null,
whenNotNull: pinAndGetPtr,
whenNullOpt: null, // just return default(T*)
currentConditionalAccessID,
localType);
}
// ptr = initializer?.{temp =ref .GetPinnable(), (int*)&pinnedTemp} ?? default;
BoundStatement localInit = InstrumentLocalDeclarationIfNecessary(localDecl, localSymbol, factory.Assignment(factory.Local(localSymbol), pinAndGetPtr));
return localInit;
}
/// <summary>
/// fixed(char* ptr = stringVar){ ... } == becomes ===>
///
/// pinned string pinnedTemp = stringVar; // pinning managed ref
/// char* ptr = (char*)pinnedTemp; // unsafe cast to unmanaged ptr
/// if (pinnedTemp != null) ptr += OffsetToStringData();
/// . . .
/// </summary>
private BoundStatement InitializeFixedStatementStringLocal(
BoundLocalDeclaration localDecl,
LocalSymbol localSymbol,
BoundFixedLocalCollectionInitializer fixedInitializer,
SyntheticBoundNodeFactory factory,
out LocalSymbol pinnedTemp)
{
TypeSymbol localType = localSymbol.Type;
BoundExpression initializerExpr = VisitExpression(fixedInitializer.Expression);
TypeSymbol? initializerType = initializerExpr.Type;
Debug.Assert(initializerType is { });
// intervening parens may have been skipped by the binder; find the declarator
VariableDeclaratorSyntax? declarator = fixedInitializer.Syntax.FirstAncestorOrSelf<VariableDeclaratorSyntax>();
Debug.Assert(declarator != null);
pinnedTemp = factory.SynthesizedLocal(
initializerType,
syntax: declarator,
isPinned: true,
kind: SynthesizedLocalKind.FixedReference);
// NOTE: we pin the string, not the pointer.
Debug.Assert(pinnedTemp.IsPinned);
Debug.Assert(!localSymbol.IsPinned);
BoundStatement stringTempInit = factory.Assignment(factory.Local(pinnedTemp), initializerExpr);
// (char*)pinnedTemp;
var addr = factory.Convert(
fixedInitializer.ElementPointerType,
factory.Local(pinnedTemp),
Conversion.PinnedObjectToPointer);
var convertedStringTemp = factory.Convert(
localType,
addr,
fixedInitializer.ElementPointerTypeConversion);
BoundStatement localInit = InstrumentLocalDeclarationIfNecessary(localDecl, localSymbol,
factory.Assignment(factory.Local(localSymbol), convertedStringTemp));
BoundExpression notNullCheck = MakeNullCheck(factory.Syntax, factory.Local(localSymbol), BinaryOperatorKind.NotEqual);
BoundExpression helperCall;
MethodSymbol offsetMethod;
if (TryGetWellKnownTypeMember(fixedInitializer.Syntax, WellKnownMember.System_Runtime_CompilerServices_RuntimeHelpers__get_OffsetToStringData, out offsetMethod))
{
helperCall = factory.Call(receiver: null, method: offsetMethod);
}
else
{
helperCall = new BoundBadExpression(fixedInitializer.Syntax, LookupResultKind.NotInvocable, ImmutableArray<Symbol?>.Empty, ImmutableArray<BoundExpression>.Empty, ErrorTypeSymbol.UnknownResultType);
}
BoundExpression addition = factory.Binary(BinaryOperatorKind.PointerAndIntAddition, localType, factory.Local(localSymbol), helperCall);
BoundStatement conditionalAdd = factory.If(notNullCheck, factory.Assignment(factory.Local(localSymbol), addition));
return factory.Block(stringTempInit, localInit, conditionalAdd);
}
/// <summary>
/// <![CDATA[
/// fixed(int* ptr = arr){ ... } == becomes ===>
///
/// pinned int[] pinnedTemp = arr; // pinning managed ref
/// int* ptr = pinnedTemp != null && pinnedTemp.Length != 0 ?
/// (int*)&pinnedTemp[0] : // unsafe cast to unmanaged ptr
/// 0;
/// . . .
/// ]]>
/// </summary>
private BoundStatement InitializeFixedStatementArrayLocal(
BoundLocalDeclaration localDecl,
LocalSymbol localSymbol,
BoundFixedLocalCollectionInitializer fixedInitializer,
SyntheticBoundNodeFactory factory,
out LocalSymbol pinnedTemp)
{
TypeSymbol localType = localSymbol.Type;
BoundExpression initializerExpr = VisitExpression(fixedInitializer.Expression);
TypeSymbol? initializerType = initializerExpr.Type;
Debug.Assert(initializerType is { });
pinnedTemp = factory.SynthesizedLocal(initializerType, isPinned: true);
ArrayTypeSymbol arrayType = (ArrayTypeSymbol)pinnedTemp.Type;
TypeWithAnnotations arrayElementType = arrayType.ElementTypeWithAnnotations;
// NOTE: we pin the array, not the pointer.
Debug.Assert(pinnedTemp.IsPinned);
Debug.Assert(!localSymbol.IsPinned);
//(pinnedTemp = array)
BoundExpression arrayTempInit = factory.AssignmentExpression(factory.Local(pinnedTemp), initializerExpr);
//(pinnedTemp = array) != null
BoundExpression notNullCheck = MakeNullCheck(factory.Syntax, arrayTempInit, BinaryOperatorKind.NotEqual);
BoundExpression lengthCall;
if (arrayType.IsSZArray)
{
lengthCall = factory.ArrayLength(factory.Local(pinnedTemp));
}
else
{
MethodSymbol lengthMethod;
if (TryGetWellKnownTypeMember(fixedInitializer.Syntax, WellKnownMember.System_Array__get_Length, out lengthMethod))
{
lengthCall = factory.Call(factory.Local(pinnedTemp), lengthMethod);
}
else
{
lengthCall = new BoundBadExpression(fixedInitializer.Syntax, LookupResultKind.NotInvocable, ImmutableArray<Symbol?>.Empty, ImmutableArray.Create<BoundExpression>(factory.Local(pinnedTemp)), ErrorTypeSymbol.UnknownResultType);
}
}
// NOTE: dev10 comment says ">", but code actually checks "!="
//temp.Length != 0
BoundExpression lengthCheck = factory.Binary(BinaryOperatorKind.IntNotEqual, factory.SpecialType(SpecialType.System_Boolean), lengthCall, factory.Literal(0));
//((temp = array) != null && temp.Length != 0)
BoundExpression condition = factory.Binary(BinaryOperatorKind.LogicalBoolAnd, factory.SpecialType(SpecialType.System_Boolean), notNullCheck, lengthCheck);
//temp[0]
BoundExpression firstElement = factory.ArrayAccessFirstElement(factory.Local(pinnedTemp));
// NOTE: this is a fixed statement address-of in that it's the initial value of the pointer.
//&temp[0]
BoundExpression firstElementAddress = new BoundAddressOfOperator(factory.Syntax, firstElement, type: new PointerTypeSymbol(arrayElementType));
BoundExpression convertedFirstElementAddress = factory.Convert(
localType,
firstElementAddress,
fixedInitializer.ElementPointerTypeConversion);
//loc = &temp[0]
BoundExpression consequenceAssignment = factory.AssignmentExpression(factory.Local(localSymbol), convertedFirstElementAddress);
//loc = null
BoundExpression alternativeAssignment = factory.AssignmentExpression(factory.Local(localSymbol), factory.Null(localType));
//(((temp = array) != null && temp.Length != 0) ? loc = &temp[0] : loc = null)
BoundStatement localInit = factory.ExpressionStatement(
new BoundConditionalOperator(factory.Syntax, false, condition, consequenceAssignment, alternativeAssignment, ConstantValue.NotAvailable, localType, wasTargetTyped: false, localType));
return InstrumentLocalDeclarationIfNecessary(localDecl, localSymbol, localInit);
}
}
}