Skip to content

Commit e1e18ee

Browse files
authoredOct 18, 2023
Fix analyzer treatment of flow captures of arrays (#93420)
#93259 uncovered an issue around how the analyzer tracks arrays. It hit an analyzer assert while trying to create an l-value flow capture of another flow capture reference, which should never happen as far as I understand. The assert was firing for https://github.com/dotnet/runtime/blob/946c5245aea149d9ece53fd0debc18328c29083b/src/libraries/System.Private.CoreLib/src/System/IO/RandomAccess.Windows.cs#L511 Now tested in TestNullCoalescingAssignment: ```csharp (arr ??= arr2)[0] = typeof (V); ``` The CFG had three flow captures: - capture 0: an l-value flow capture of `arr`. Used later in the branch that assigns `arr2` to `arr`. - capture 1: an r-value flow capture of capture 0. This was checked for null. - capture 2: an l-value flow capture representing `arr ??= arr2`, used to write index 0 of the array. - In the == null branch, this captured the result of an assignment (capture 0 = `arr2`) - In the other branch, it captured "capture 1". This is where the assert was hit. The bug, I believe, is that capture 2 should have been an r-value flow capture instead. Even though it's used for writing to the array, the assignment doesn't modify the array pointer represented by this capture - it dereferences this pointer and modifies the array. This was introduced by my modifications to the l-value detection logic in #90287. This undoes that portion of the change so that capture 2 is now treated as an r-value capture. This simplifies the array element assignment logic so that it no longer can see an assignment where the array is an l-value. Fixes #93420 by adding an explicit check for `IsInitialization` so that we don't hit the related asserts for string interpolation handlers.
1 parent f16e8bd commit e1e18ee

File tree

7 files changed

+238
-124
lines changed

7 files changed

+238
-124
lines changed
 

‎src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LValueFlowCaptureProvider.cs

-6
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,6 @@ static bool IsLValueFlowCapture (IFlowCaptureReferenceOperation flowCaptureRefer
4444
if (assignment?.Target == flowCaptureReference)
4545
return true;
4646

47-
if (flowCaptureReference.Parent is IArrayElementReferenceOperation arrayAlementRef) {
48-
assignment = arrayAlementRef.Parent as IAssignmentOperation;
49-
if (assignment?.Target == arrayAlementRef)
50-
return true;
51-
}
52-
5347
assignment = null;
5448
return flowCaptureReference.IsInLeftOfDeconstructionAssignment (out _);
5549
}

‎src/tools/illink/src/ILLink.RoslynAnalyzer/DataFlow/LocalDataFlowVisitor.cs

+49-63
Original file line numberDiff line numberDiff line change
@@ -235,45 +235,11 @@ TValue ProcessSingleTargetAssignment (IOperation targetOperation, ISimpleAssignm
235235
if (arrayElementRef.Indices.Length != 1)
236236
break;
237237

238-
// Similarly to VisitSimpleAssignment, this needs to handle cases where the array reference
239-
// is a captured variable, even if the target of the assignment (the array element reference) is not.
240-
241-
TValue arrayRef;
242-
TValue index;
243-
TValue value;
244-
if (arrayElementRef.ArrayReference is not IFlowCaptureReferenceOperation captureReference) {
245-
arrayRef = Visit (arrayElementRef.ArrayReference, state);
246-
index = Visit (arrayElementRef.Indices[0], state);
247-
value = Visit (operation.Value, state);
248-
HandleArrayElementWrite (arrayRef, index, value, operation, merge: merge);
249-
return value;
250-
}
251-
252-
index = Visit (arrayElementRef.Indices[0], state);
253-
value = Visit (operation.Value, state);
254-
255-
var capturedReferences = state.Current.CapturedReferences.Get (captureReference.Id);
256-
if (!capturedReferences.HasMultipleValues) {
257-
// Single captured reference. Treat this as an overwriting assignment,
258-
// unless the caller already told us to merge values because this is an
259-
// assignment to one of multiple captured array element references.
260-
var enumerator = capturedReferences.GetEnumerator ();
261-
enumerator.MoveNext ();
262-
var capture = enumerator.Current;
263-
arrayRef = Visit (capture.Reference, state);
264-
HandleArrayElementWrite (arrayRef, index, value, operation, merge: merge);
265-
return value;
266-
}
267238

268-
// The capture id may have captured multiple references, as in:
269-
// (b ? arr1 : arr2)[0] = value;
270-
// We treat this as possible write to each of the captured references,
271-
// which requires merging with the previous values of each.
272-
273-
foreach (var capture in state.Current.CapturedReferences.Get (captureReference.Id)) {
274-
arrayRef = Visit (capture.Reference, state);
275-
HandleArrayElementWrite (arrayRef, index, value, operation, merge: true);
276-
}
239+
TValue arrayRef = Visit (arrayElementRef.ArrayReference, state);
240+
TValue index = Visit (arrayElementRef.Indices[0], state);
241+
TValue value = Visit (operation.Value, state);
242+
HandleArrayElementWrite (arrayRef, index, value, operation, merge: merge);
277243
return value;
278244
}
279245
case IInlineArrayAccessOperation inlineArrayAccess: {
@@ -371,26 +337,31 @@ public override TValue VisitSimpleAssignment (ISimpleAssignmentOperation operati
371337

372338
TValue GetFlowCaptureValue (IFlowCaptureReferenceOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
373339
{
374-
if (!operation.GetValueUsageInfo (OwningSymbol).HasFlag (ValueUsageInfo.Read)) {
375-
// There are known cases where this assert doesn't hold, because LValueFlowCaptureProvider
376-
// produces the wrong result in some cases for flow captures with IsInitialization = true.
377-
// https://github.com/dotnet/linker/issues/2749
378-
// Debug.Assert (IsLValueFlowCapture (operation.Id));
379-
return TopValue;
380-
}
381-
382-
// This assert is incorrect for cases like (b ? arr1 : arr2)[0] = v;
383-
// Here the ValueUsageInfo shows that the value usage is for reading (this is probably wrong!)
384-
// but the value is actually an LValueFlowCapture.
385-
// Let's just disable the assert for now.
386-
// Debug.Assert (IsRValueFlowCapture (operation.Id));
340+
Debug.Assert (!IsLValueFlowCapture (operation.Id),
341+
$"{operation.Syntax.GetLocation ().GetLineSpan ()}");
342+
Debug.Assert (operation.GetValueUsageInfo (OwningSymbol).HasFlag (ValueUsageInfo.Read),
343+
$"{operation.Syntax.GetLocation ().GetLineSpan ()}");
387344

388345
return state.Get (new LocalKey (operation.Id));
389346
}
390347

391348
// Similar to VisitLocalReference
392349
public override TValue VisitFlowCaptureReference (IFlowCaptureReferenceOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
393350
{
351+
if (operation.IsInitialization) {
352+
// This capture reference is a temporary byref. This can happen for string
353+
// interpolation handlers: https://github.com/dotnet/roslyn/issues/57484.
354+
// Should really be treated as creating a new l-value flow capture,
355+
// but this is likely irrelevant for dataflow analysis.
356+
357+
// LValueFlowCaptureProvider doesn't take into account IsInitialization = true,
358+
// so it doesn't properly detect this as an l-value capture.
359+
// Context: https://github.com/dotnet/roslyn/issues/60757
360+
// Debug.Assert (IsLValueFlowCapture (operation.Id));
361+
Debug.Assert (operation.GetValueUsageInfo (OwningSymbol).HasFlag (ValueUsageInfo.Write),
362+
$"{operation.Syntax.GetLocation ().GetLineSpan ()}");
363+
return TopValue;
364+
}
394365
return GetFlowCaptureValue (operation, state);
395366
}
396367

@@ -399,26 +370,41 @@ public override TValue VisitFlowCaptureReference (IFlowCaptureReferenceOperation
399370
// is like a local reference.
400371
public override TValue VisitFlowCapture (IFlowCaptureOperation operation, LocalDataFlowState<TValue, TValueLattice> state)
401372
{
402-
// If the captured value is a property reference, we can't easily tell inside of
403-
// VisitPropertyReference whether it is accessed for reads or writes.
404-
// https://github.com/dotnet/roslyn/issues/25057
405-
// Avoid visiting the captured value unless it is an RValue.
406373
if (IsLValueFlowCapture (operation.Id)) {
374+
// Should never see an l-value flow capture of another flow capture.
375+
Debug.Assert (operation.Value is not IFlowCaptureReferenceOperation);
376+
if (operation.Value is IFlowCaptureReferenceOperation)
377+
return TopValue;
378+
407379
// Note: technically we should save some information about the value for LValue flow captures
408380
// (for example, the object instance of a property reference) and avoid re-computing it when
409381
// assigning to the FlowCaptureReference.
382+
var capturedRef = new CapturedReferenceValue (operation.Value);
410383
var currentState = state.Current;
411-
currentState.CapturedReferences.Set (operation.Id, new CapturedReferenceValue (operation.Value));
384+
currentState.CapturedReferences.Set (operation.Id, capturedRef);
412385
state.Current = currentState;
413-
}
386+
return TopValue;
387+
} else {
388+
TValue capturedValue;
389+
if (operation.Value is IFlowCaptureReferenceOperation captureRef) {
390+
if (IsLValueFlowCapture (captureRef.Id)) {
391+
// If an r-value captures an l-value, we must dereference the l-value
392+
// and copy out the value to capture.
393+
capturedValue = TopValue;
394+
foreach (var capturedReference in state.Current.CapturedReferences.Get (captureRef.Id)) {
395+
var value = Visit (capturedReference.Reference, state);
396+
capturedValue = LocalStateLattice.Lattice.ValueLattice.Meet (capturedValue, value);
397+
}
398+
} else {
399+
capturedValue = state.Get (new LocalKey (captureRef.Id));
400+
}
401+
} else {
402+
capturedValue = Visit (operation.Value, state);
403+
}
414404

415-
if (IsRValueFlowCapture (operation.Id)) {
416-
TValue value = Visit (operation.Value, state);
417-
state.Set (new LocalKey (operation.Id), value);
418-
return value;
405+
state.Set (new LocalKey (operation.Id), capturedValue);
406+
return capturedValue;
419407
}
420-
421-
return TopValue;
422408
}
423409

424410
public override TValue VisitExpressionStatement (IExpressionStatementOperation operation, LocalDataFlowState<TValue, TValueLattice> state)

‎src/tools/illink/test/ILLink.RoslynAnalyzer.Tests/DataFlowTests.cs

+6
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,12 @@ public Task InlineArrayDataflow ()
161161
return RunTest ();
162162
}
163163

164+
[Fact]
165+
public Task InterpolatedStringHandlerDataFlow ()
166+
{
167+
return RunTest ();
168+
}
169+
164170
[Fact]
165171
public Task MakeGenericDataFlow ()
166172
{

‎src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ArrayDataFlow.cs

+118
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33

44
using System;
55
using System.Diagnostics.CodeAnalysis;
6+
using System.Runtime.CompilerServices;
7+
using System.Runtime.InteropServices;
68
using Mono.Linker.Tests.Cases.Expectations.Assertions;
79
using Mono.Linker.Tests.Cases.Expectations.Helpers;
810

@@ -30,6 +32,7 @@ public static void Main ()
3032
TestArraySetElementAndInitializerMultipleElementsMix<TestType> (typeof (TestType));
3133

3234
TestGetElementAtUnknownIndex ();
35+
TestGetMergedArrayElement ();
3336
TestMergedArrayElementWithUnknownIndex (0);
3437

3538
// Array reset - certain operations on array are not tracked fully (or impossible due to unknown inputs)
@@ -47,6 +50,8 @@ public static void Main ()
4750

4851
WriteCapturedArrayElement.Test ();
4952

53+
WriteElementOfCapturedArray.Test ();
54+
5055
ConstantFieldValuesAsIndex.Test ();
5156

5257
HoistedArrayMutation.Test ();
@@ -205,6 +210,23 @@ static void TestGetElementAtUnknownIndex (int i = 0)
205210
arr[i].RequiresPublicFields ();
206211
}
207212

213+
// https://github.com/dotnet/runtime/issues/93416 tracks the discrepancy between
214+
// the analyzer and ILLink/ILCompiler.
215+
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll),
216+
ProducedBy = Tool.Trimmer | Tool.NativeAot)]
217+
[ExpectedWarning ("IL2072", nameof (GetMethods), nameof (DataFlowTypeExtensions.RequiresAll),
218+
ProducedBy = Tool.Analyzer)]
219+
[ExpectedWarning ("IL2072", nameof (GetFields), nameof (DataFlowTypeExtensions.RequiresAll),
220+
ProducedBy = Tool.Analyzer)]
221+
static void TestGetMergedArrayElement (bool b = true)
222+
{
223+
Type[] arr = new Type[] { GetMethods () };
224+
Type[] arr2 = new Type[] { GetFields () };
225+
if (b)
226+
arr = arr2;
227+
arr[0].RequiresAll ();
228+
}
229+
208230
// Trimmer code doesnt handle locals from different branches separetely, therefore merges incorrectly GetMethods with Unknown producing both warnings
209231
[ExpectedWarning ("IL2072", nameof (ArrayDataFlow.GetMethods), ProducedBy = Tool.Trimmer | Tool.NativeAot)]
210232
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))]
@@ -614,6 +636,102 @@ public static void Test ()
614636
}
615637
}
616638

639+
class WriteElementOfCapturedArray
640+
{
641+
[Kept]
642+
[ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (DataFlowTypeExtensions.RequiresAll))]
643+
[ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (DataFlowTypeExtensions.RequiresAll))]
644+
// Analysis hole: https://github.com/dotnet/runtime/issues/90335
645+
// The array element assignment assigns to a temp array created as a copy of
646+
// arr1 or arr2, and writes to it aren't reflected back in arr1/arr2.
647+
static void TestArrayElementAssignment (bool b = true)
648+
{
649+
var arr1 = new Type[] { GetUnknownType () };
650+
var arr2 = new Type[] { GetTypeWithPublicConstructors () };
651+
(b ? arr1 : arr2)[0] = GetWithPublicMethods ();
652+
arr1[0].RequiresAll ();
653+
arr2[0].RequiresAll ();
654+
}
655+
656+
[Kept]
657+
[KeptAttributeAttribute (typeof (InlineArrayAttribute))]
658+
[InlineArray (8)]
659+
public struct InlineTypeArray
660+
{
661+
[Kept]
662+
public Type t;
663+
}
664+
665+
[Kept]
666+
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))]
667+
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))]
668+
static void TestInlineArrayElementReferenceAssignment (bool b = true)
669+
{
670+
var arr1 = new InlineTypeArray ();
671+
arr1[0] = GetUnknownType ();
672+
var arr2 = new InlineTypeArray ();
673+
arr2[0] = GetTypeWithPublicConstructors ();
674+
(b ? ref arr1[0] : ref arr2[0]) = GetTypeWithPublicConstructors ();
675+
arr1[0].RequiresAll ();
676+
arr2[0].RequiresAll ();
677+
}
678+
679+
// Inline array references are not allowed in conditionals, unlike array references.
680+
// static void TestInlineArrayElementAssignment (bool b = true)
681+
// {
682+
// var arr1 = new InlineTypeArray ();
683+
// arr1[0] = GetUnknownType ();
684+
// var arr2 = new InlineTypeArray ();
685+
// arr2[0] = GetTypeWithPublicConstructors ();
686+
// (b ? arr1 : arr2)[0] = GetWithPublicMethods ();
687+
// arr1[0].RequiresAll ();
688+
// arr2[0].RequiresAll ();
689+
// }
690+
691+
[ExpectedWarning ("IL2087", nameof (T), nameof (DataFlowTypeExtensions.RequiresAll))]
692+
[ExpectedWarning ("IL2087", nameof (U), nameof (DataFlowTypeExtensions.RequiresPublicFields))]
693+
// Missing warnings for 'V' possibly assigned to arr or arr2 because write to temp
694+
// array isn't reflected back in the local variables. https://github.com/dotnet/linker/issues/2158
695+
static void TestNullCoalesce<T, U, V> (bool b = false)
696+
{
697+
Type[]? arr = new Type[1] { typeof (T) };
698+
Type[] arr2 = new Type[1] { typeof (U) };
699+
700+
(arr ?? arr2)[0] = typeof (V);
701+
arr[0].RequiresAll ();
702+
arr2[0].RequiresPublicFields ();
703+
}
704+
705+
[ExpectedWarning ("IL2087", nameof (T), nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = Tool.Analyzer)]
706+
[ExpectedWarning ("IL2087", nameof (U), nameof (DataFlowTypeExtensions.RequiresPublicFields))]
707+
// Missing warnings for 'V' possibly assigned to arr or arr2 because write to temp
708+
// array isn't reflected back in the local variables. https://github.com/dotnet/linker/issues/2158
709+
// This also causes an extra analyzer warning for 'U' in 'arr', because the analyzer models the
710+
// possible assignment of arr2 to arr, without overwriting index '0'. And it produces a warning
711+
// for each possible value, unlike ILLink/ILCompiler, which produce an unknown value for a merged
712+
// array value: https://github.com/dotnet/runtime/issues/93416
713+
[ExpectedWarning ("IL2087", nameof (U), nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = Tool.Analyzer)]
714+
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = Tool.Trimmer | Tool.NativeAot)]
715+
static void TestNullCoalescingAssignment<T, U, V> (bool b = true)
716+
{
717+
Type[]? arr = new Type[1] { typeof (T) };
718+
Type[] arr2 = new Type[1] { typeof (U) };
719+
720+
(arr ??= arr2)[0] = typeof (V);
721+
arr[0].RequiresAll ();
722+
arr2[0].RequiresPublicFields ();
723+
}
724+
725+
public static void Test ()
726+
{
727+
TestArrayElementAssignment ();
728+
TestInlineArrayElementReferenceAssignment ();
729+
// TestInlineArrayElementAssignment ();
730+
TestNullCoalesce<int, int, int> ();
731+
TestNullCoalescingAssignment<int, int, int> ();
732+
}
733+
}
734+
617735
class ConstantFieldValuesAsIndex
618736
{
619737
private const sbyte ConstSByte = 1;

‎src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/ByRefDataflow.cs

-55
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@
44
using System;
55
using System.Diagnostics.CodeAnalysis;
66
using System.Reflection;
7-
using System.Runtime.CompilerServices;
8-
using System.Runtime.InteropServices;
97
using Mono.Linker.Tests.Cases.Expectations.Assertions;
108
using Mono.Linker.Tests.Cases.Expectations.Metadata;
119
using Mono.Linker.Tests.Cases.Expectations.Helpers;
@@ -261,56 +259,6 @@ static void TestArrayElementReferenceAssignment (bool b = true)
261259
arr2[0].RequiresAll ();
262260
}
263261

264-
[Kept]
265-
[ExpectedWarning ("IL2072", nameof (GetUnknownType), nameof (DataFlowTypeExtensions.RequiresAll))]
266-
[ExpectedWarning ("IL2072", nameof (GetTypeWithPublicConstructors), nameof (DataFlowTypeExtensions.RequiresAll))]
267-
// ILLink/ILCompiler analysis hole: https://github.com/dotnet/runtime/issues/90335
268-
[ExpectedWarning ("IL2072", nameof (GetTypeWithPublicFields), nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = Tool.Analyzer)]
269-
[ExpectedWarning ("IL2072", nameof (GetTypeWithPublicFields), nameof (DataFlowTypeExtensions.RequiresAll), ProducedBy = Tool.Analyzer)]
270-
static void TestArrayElementAssignment (bool b = true)
271-
{
272-
var arr1 = new Type[] { GetUnknownType () };
273-
var arr2 = new Type[] { GetTypeWithPublicConstructors () };
274-
(b ? arr1 : arr2)[0] = GetTypeWithPublicFields ();
275-
arr1[0].RequiresAll ();
276-
arr2[0].RequiresAll ();
277-
}
278-
279-
[Kept]
280-
[KeptAttributeAttribute (typeof (InlineArrayAttribute))]
281-
[InlineArray (8)]
282-
public struct InlineTypeArray
283-
{
284-
[Kept]
285-
public Type t;
286-
}
287-
288-
[Kept]
289-
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))]
290-
[ExpectedWarning ("IL2062", nameof (DataFlowTypeExtensions.RequiresAll))]
291-
static void TestInlineArrayElementReferenceAssignment (bool b = true)
292-
{
293-
var arr1 = new InlineTypeArray ();
294-
arr1[0] = GetUnknownType ();
295-
var arr2 = new InlineTypeArray ();
296-
arr2[0] = GetTypeWithPublicConstructors ();
297-
(b ? ref arr1[0] : ref arr2[0]) = GetTypeWithPublicFields ();
298-
arr1[0].RequiresAll ();
299-
arr2[0].RequiresAll ();
300-
}
301-
302-
// Inline array references are not allowed in conditionals, unlike array references.
303-
// static void TestInlineArrayElementAssignment (bool b = true)
304-
// {
305-
// var arr1 = new InlineTypeArray ();
306-
// arr1[0] = GetUnknownType ();
307-
// var arr2 = new InlineTypeArray ();
308-
// arr2[0] = GetTypeWithPublicConstructors ();
309-
// (b ? arr1 : arr2)[0] = GetTypeWithPublicFields ();
310-
// arr1[0].RequiresAll ();
311-
// arr2[0].RequiresAll ();
312-
// }
313-
314262
[Kept]
315263
[ExpectedWarning ("IL2074", nameof (_publicMethodsField), nameof (GetUnknownType))]
316264
[ExpectedWarning ("IL2074", nameof (_publicPropertiesField), nameof (GetUnknownType))]
@@ -358,9 +306,6 @@ public static void Test ()
358306
TestParameterAssignment ();
359307
TestLocalAssignment ();
360308
TestArrayElementReferenceAssignment ();
361-
TestArrayElementAssignment ();
362-
TestInlineArrayElementReferenceAssignment ();
363-
// TestInlineArrayElementAssignment ();
364309
TestNullCoalescingAssignment ();
365310
TestNullCoalescingAssignmentComplex ();
366311
TestDataFlowOnRightHandOfAssignment ();

‎src/tools/illink/test/Mono.Linker.Tests.Cases/DataFlow/FieldDataFlow.cs

+30
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ public static void Main ()
3333
instance.WriteUnknownValue ();
3434

3535
WriteCapturedField.Test ();
36+
WriteFieldOfCapturedInstance.Test ();
3637

3738
_ = _annotationOnWrongType;
3839

@@ -192,6 +193,35 @@ public static void Test ()
192193
}
193194
}
194195

196+
class WriteFieldOfCapturedInstance
197+
{
198+
class ClassWithAnnotatedField
199+
{
200+
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.All)]
201+
public Type field;
202+
}
203+
204+
[ExpectedWarning ("IL2074", nameof (GetUnknownType), nameof (ClassWithAnnotatedField.field))]
205+
static void TestNullCoalesce ()
206+
{
207+
ClassWithAnnotatedField? instance = null;
208+
(instance ?? new ClassWithAnnotatedField ()).field = GetUnknownType ();
209+
}
210+
211+
[ExpectedWarning ("IL2074", nameof (GetUnknownType), nameof (ClassWithAnnotatedField.field))]
212+
static void TestNullCoalescingAssignment ()
213+
{
214+
ClassWithAnnotatedField? instance = null;
215+
(instance ??= new ClassWithAnnotatedField ()).field = GetUnknownType ();
216+
}
217+
218+
public static void Test ()
219+
{
220+
TestNullCoalesce ();
221+
TestNullCoalescingAssignment ();
222+
}
223+
}
224+
195225
class AccessReturnedInstanceField
196226
{
197227
[DynamicallyAccessedMembers (DynamicallyAccessedMemberTypes.PublicMethods)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// Copyright (c) .NET Foundation and contributors. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using System.Diagnostics.CodeAnalysis;
7+
using System.Runtime.CompilerServices;
8+
using System.Runtime.InteropServices;
9+
using Mono.Linker.Tests.Cases.Expectations.Assertions;
10+
using Mono.Linker.Tests.Cases.Expectations.Helpers;
11+
using Mono.Linker.Tests.Cases.Expectations.Metadata;
12+
13+
namespace Mono.Linker.Tests.Cases.DataFlow
14+
{
15+
[ExpectedNoWarnings]
16+
[SkipKeptItemsValidation]
17+
[Define ("DEBUG")]
18+
public class InterpolatedStringHandlerDataFlow
19+
{
20+
public static void Main ()
21+
{
22+
Test ();
23+
}
24+
25+
static void Test(bool b = true) {
26+
// Creates a control-flow graph for the analyzer that has an
27+
// IFlowCaptureReferenceOperation that represents a capture
28+
// because it is used as an out param (so has IsInitialization = true).
29+
// See https://github.com/dotnet/roslyn/issues/57484 for context.
30+
// This test ensures the analyzer has coverage for cases
31+
// where IsInitialization = true.
32+
Debug.Assert (b, $"Debug interpolated string handler {b}");
33+
}
34+
}
35+
}

0 commit comments

Comments
 (0)
Please sign in to comment.