File: DoNotCopyValueTests.cs
Web Access
Project: src\src\RoslynAnalyzers\Roslyn.Diagnostics.Analyzers\UnitTests\Roslyn.Diagnostics.Analyzers.UnitTests.csproj (Roslyn.Diagnostics.Analyzers.UnitTests)
// 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.
 
#nullable enable
 
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Testing;
using Xunit;
using VerifyCS = Test.Utilities.CSharpSecurityCodeFixVerifier<
    Roslyn.Diagnostics.CSharp.Analyzers.CSharpDoNotCopyValue,
    Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>;
using VerifyVB = Test.Utilities.VisualBasicSecurityCodeFixVerifier<
    Roslyn.Diagnostics.VisualBasic.Analyzers.VisualBasicDoNotCopyValue,
    Microsoft.CodeAnalysis.Testing.EmptyCodeFixProvider>;
 
namespace Roslyn.Diagnostics.Analyzers.UnitTests
{
    public class DoNotCopyValueTests
    {
        [Fact]
        public Task TestSliceOfStringAsync()
            => new VerifyCS.Test
            {
                ReferenceAssemblies = ReferenceAssemblies.NetCore.NetCoreApp31,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp9,
                TestCode = """
                using System.Runtime.InteropServices;
 
                class C
                {
                    void M()
                    {
                        var local = ""[..];
                        local = ""[..];
                    }
                }
                """,
            }.RunAsync();
 
        [Fact]
        public async Task TestAcquireFromReturnByValueAsync()
        {
            await VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void M()
                    {
                        var local = GCHandle.Alloc(new object());
                    }
                }
                """);
 
            await VerifyVB.VerifyAnalyzerAsync("""
                Imports System.Runtime.InteropServices
 
                Class C
                    Sub M()
                        Dim local = GCHandle.Alloc(New Object())
                    End Sub
                End Class
                """);
        }
 
        [Theory]
        [InlineData("Func<ValueTask<GCHandle>>", "Func(Of ValueTask(Of GCHandle))")]
        [InlineData("Func<ConfiguredValueTaskAwaitable<GCHandle>>", "Func(Of ConfiguredValueTaskAwaitable(Of GCHandle))")]
        [InlineData("Func<Task<GCHandle>>", "Func(Of Task(Of GCHandle))")]
        [InlineData("Func<ConfiguredTaskAwaitable<GCHandle>>", "Func(Of ConfiguredTaskAwaitable(Of GCHandle))")]
        public async Task TestAcquireFromAwaitInvocationAsync(string csharpInvokeType, string visualBasicInvokeType)
        {
            await VerifyCS.VerifyAnalyzerAsync($$"""
                using System;
                using System.Runtime.CompilerServices;
                using System.Runtime.InteropServices;
                using System.Threading.Tasks;
 
                class C
                {
                    async Task M({{csharpInvokeType}} d)
                    {
                        var local = await d();
                        local = await d();
                    }
                }
                """);
 
            await VerifyVB.VerifyAnalyzerAsync($"""
                Imports System
                Imports System.Runtime.CompilerServices
                Imports System.Runtime.InteropServices
                Imports System.Threading.Tasks
 
                Class C
                    Async Function M(d as {visualBasicInvokeType}) As Task
                        Dim local = Await d()
                        local = Await d()
                    End Function
                End Class
                """);
        }
 
        [Theory]
        [InlineData("ValueTask<GCHandle>", "ValueTask(Of GCHandle)")]
        [InlineData("ConfiguredValueTaskAwaitable<GCHandle>", "ConfiguredValueTaskAwaitable(Of GCHandle)")]
        public async Task TestAcquireFromAwaitAsync(string csharpAwaitableType, string visualBasicAwaitableType)
        {
            await VerifyCS.VerifyAnalyzerAsync($$"""
                using System.Runtime.CompilerServices;
                using System.Runtime.InteropServices;
                using System.Threading.Tasks;
 
                class C
                {
                    async Task M({{csharpAwaitableType}} task)
                    {
                        var local = await task;
                    }
                }
                """);
 
            await VerifyVB.VerifyAnalyzerAsync($"""
                Imports System.Runtime.CompilerServices
                Imports System.Runtime.InteropServices
                Imports System.Threading.Tasks
 
                Class C
                    Async Function M(task as {visualBasicAwaitableType}) As Task
                        Dim local = Await task
                    End Function
                End Class
                """);
        }
 
        [Theory]
        [InlineData("Task<GCHandle>", "Task(Of GCHandle)")]
        [InlineData("ConfiguredTaskAwaitable<GCHandle>", "ConfiguredTaskAwaitable(Of GCHandle)")]
        public async Task TestFailedAcquireFromUnsupportedAwaitAsync(string csharpAwaitableType, string visualBasicAwaitableType)
        {
            await VerifyCS.VerifyAnalyzerAsync($$"""
                using System.Runtime.CompilerServices;
                using System.Runtime.InteropServices;
                using System.Threading.Tasks;
 
                class C
                {
                    async Task M({{csharpAwaitableType}} task)
                    {
                        var local {|#0:= await task|};
                    }
                }
                """,
                // /0/Test0.cs(10,19): warning RS0042: Cannot assign a value from a reference to non-copyable type 'System.Runtime.InteropServices.GCHandle'
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoAssignValueFromReferenceRule).WithLocation(0).WithArguments("System.Runtime.InteropServices.GCHandle"));
 
            await VerifyVB.VerifyAnalyzerAsync($$"""
                Imports System.Runtime.CompilerServices
                Imports System.Runtime.InteropServices
                Imports System.Threading.Tasks
 
                Class C
                    Async Function M(task as {{visualBasicAwaitableType}}) As Task
                        Dim local {|#0:= Await task|}
                    End Function
                End Class
                """,
                // /0/Test0.vb(8,19): warning RS0042: Cannot assign a value from a reference to non-copyable type 'System.Runtime.InteropServices.GCHandle'
                VerifyVB.Diagnostic(AbstractDoNotCopyValue.NoAssignValueFromReferenceRule).WithLocation(0).WithArguments("System.Runtime.InteropServices.GCHandle"));
        }
 
        [Theory]
        [InlineData("field", "field")]
        [InlineData("(field)", null)]
        [InlineData("this.field", "Me.field")]
        [InlineData("(this).field", null)]
        [InlineData("((C)this).field", "DirectCast(Me, C).field")]
        public async Task TestAcquireIntoFieldFromReturnByValueAsync(string csharpFieldReference, string? visualBasicFieldReference)
        {
            await VerifyCS.VerifyAnalyzerAsync($$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle field;
 
                    void M(bool condition)
                    {
                        {{csharpFieldReference}} = GCHandle.Alloc(new object());
                        {{csharpFieldReference}} = condition ? GCHandle.Alloc(new object()) : GCHandle.Alloc(new object());
                    }
                }
                """);
 
            if (visualBasicFieldReference is object)
            {
                await VerifyVB.VerifyAnalyzerAsync($"""
                    Imports System.Runtime.InteropServices
 
                    Class C
                        Dim field As GCHandle
 
                        Sub M(condition As Boolean)
                            {visualBasicFieldReference} = GCHandle.Alloc(New Object())
                            {visualBasicFieldReference} = If(condition, GCHandle.Alloc(New Object()), GCHandle.Alloc(New Object()))
                        End Sub
                    End Class
                    """);
            }
        }
 
        [Theory]
        [InlineData("field", "field")]
        [InlineData("(field)", null)]
        [InlineData("this.field", "Me.field")]
        [InlineData("(this).field", null)]
        [InlineData("((C)this).field", "DirectCast(Me, C).field")]
        public async Task TestAcquireIntoArrayFieldFromReturnByValueAsync(string csharpFieldReference, string? visualBasicFieldReference)
        {
            await VerifyCS.VerifyAnalyzerAsync($$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle[] field;
 
                    void M()
                    {
                        {{csharpFieldReference}}[0] = GCHandle.Alloc(new object());
                    }
                }
                """);
 
            if (visualBasicFieldReference is object)
            {
                await VerifyVB.VerifyAnalyzerAsync($"""
                    Imports System.Runtime.InteropServices
 
                    Class C
                        Dim field As GCHandle()
 
                        Sub M()
                            {visualBasicFieldReference}(0) = GCHandle.Alloc(New Object())
                        End Sub
                    End Class
                    """);
            }
        }
 
        [Fact]
        public Task TestDoNotAcquireFromReturnByReferenceAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void M()
                    {
                        var local {|#0:= GetRef()|};
                    }
 
                    ref GCHandle GetRef() => throw null;
                }
                """,
                // /0/Test0.cs(8,19): warning RS0042: Cannot assign a value from a reference to non-copyable type 'System.Runtime.InteropServices.GCHandle'
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoAssignValueFromReferenceRule).WithLocation(0).WithArguments("System.Runtime.InteropServices.GCHandle"));
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/77925")]
        public Task TestPassToInstancePropertyGetterAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle field;
                    readonly GCHandle readonlyfield;
 
                    void M()
                    {
                        var local = new GCHandle();
                        ref var reflocal = ref local;
                        ref readonly var refreadonlylocal = ref local;
 
                        // Call each proprety twice to ensure the analyzer didn't silently treat one like a move
                        _ = field.Target;
                        _ = field.Target;
                        _ = local.Target;
                        _ = local.Target;
                        _ = reflocal.Target;
                        _ = reflocal.Target;
 
                        _ = {|#0:readonlyfield|}.Target;
                        _ = {|#1:refreadonlylocal|}.Target;
                    }
                }
                """,
                // /0/Test0.cs(23,13): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'FieldReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(0).WithArguments("System.Runtime.InteropServices.GCHandle", "FieldReference"),
                // /0/Test0.cs(17,13): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'LocalReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(1).WithArguments("System.Runtime.InteropServices.GCHandle", "LocalReference"));
 
        [Fact(Skip = "https://github.com/dotnet/roslyn/issues/77925")]
        public Task TestPassToInstanceMethodAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle field;
                    readonly GCHandle readonlyfield;
 
                    void M()
                    {
                        var local = new GCHandle();
                        ref var reflocal = ref local;
                        ref readonly var refreadonlylocal = ref local;
 
                        // Call each method twice to ensure the analyzer didn't silently treat one like a move
                        _ = field.AddrOfPinnedObject();
                        _ = field.AddrOfPinnedObject();
                        _ = local.AddrOfPinnedObject();
                        _ = local.AddrOfPinnedObject();
                        _ = reflocal.AddrOfPinnedObject();
                        _ = reflocal.AddrOfPinnedObject();
 
                        _ = {|#0:readonlyfield|}.AddrOfPinnedObject();
                        _ = {|#1:refreadonlylocal|}.AddrOfPinnedObject();
                    }
                }
                """,
                // /0/Test0.cs(23,13): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'FieldReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(0).WithArguments("System.Runtime.InteropServices.GCHandle", "FieldReference"),
                // /0/Test0.cs(17,13): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'LocalReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(1).WithArguments("System.Runtime.InteropServices.GCHandle", "LocalReference"));
 
        [Fact]
        public Task TestPassToExtensionMethodAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle field;
                    readonly GCHandle readonlyfield;
 
                    void M()
                    {
                        var local = new GCHandle();
                        ref var reflocal = ref local;
                        ref readonly var refreadonlylocal = ref local;
 
                        // Success cases. Call each method twice to ensure the analyzer didn't silently treat one like a move.
                        field.XRef();
                        field.XRef();
                        local.XRef();
                        local.XRef();
                        reflocal.XRef();
                        reflocal.XRef();
 
                        readonlyfield.XIn();
                        readonlyfield.XIn();
                        reflocal.XIn();
                        reflocal.XIn();
                        local.XIn();
                        local.XIn();
 
                        // Failure cases.
                        {|#0:{|CS0192:readonlyfield|}|}.XRef();
                        {|#1:{|CS1510:refreadonlylocal|}|}.XRef();
                        {|#2:field|}.X();
                        {|#3:readonlyfield|}.X();
                        {|#4:local|}.X();
                        {|#5:reflocal|}.X();
                        {|#6:refreadonlylocal|}.X();
                    }
                }
 
                static class E
                {
                    public static void X(this GCHandle handle) { }
                    public static void XRef(this ref GCHandle handle) { }
                    public static void XIn(this in GCHandle handle) { }
                }
                """,
                // /0/Test0.cs(31,9): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'FieldReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(0).WithArguments("System.Runtime.InteropServices.GCHandle", "FieldReference"),
                // /0/Test0.cs(32,9): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'LocalReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(1).WithArguments("System.Runtime.InteropServices.GCHandle", "LocalReference"),
                // /0/Test0.cs(33,9): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'FieldReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(2).WithArguments("System.Runtime.InteropServices.GCHandle", "FieldReference"),
                // /0/Test0.cs(34,9): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'FieldReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(3).WithArguments("System.Runtime.InteropServices.GCHandle", "FieldReference"),
                // /0/Test0.cs(35,9): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'LocalReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(4).WithArguments("System.Runtime.InteropServices.GCHandle", "LocalReference"),
                // /0/Test0.cs(36,9): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'LocalReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(5).WithArguments("System.Runtime.InteropServices.GCHandle", "LocalReference"),
                // /0/Test0.cs(37,9): warning RS0042: Unsupported use of non-copyable type 'System.Runtime.InteropServices.GCHandle' in 'LocalReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(6).WithArguments("System.Runtime.InteropServices.GCHandle", "LocalReference"));
 
        [Theory]
        [InlineData("throw null")]
        [InlineData("(true ? throw null : default(GCHandle))")]
        [InlineData("(false ? new GCHandle() : throw null)")]
        public Task TestConversionFromThrowNullAsync(string throwExpression)
            => VerifyCS.VerifyAnalyzerAsync($$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle Get() => {{throwExpression}};
                }
                """);
 
        [Fact]
        public async Task TestPassByReferenceAsync()
        {
            await VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Get(ref GCHandle handle) => Get(ref handle);
                }
                """);
 
            await VerifyVB.VerifyAnalyzerAsync("""
                Imports System.Runtime.InteropServices
 
                Class C
                    Sub Method(ByRef handle As GCHandle)
                        Method(handle)
                    End Sub
                End Class
                """);
        }
 
        [Fact]
        public Task TestPassByReadOnlyReferenceAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Get(in GCHandle handle) => Get(in handle);
                }
                """);
 
        [Fact]
        public Task TestAssignToMemberAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    CannotCopy _field;
 
                    void Method(CannotCopy parameter)
                    {
                        CannotCopy local = new CannotCopy();
 
                        _field.Field = 0;
                        parameter.Field = 0;
                        local.Field = 0;
 
                        _field.Property = 0;
                        parameter.Property = 0;
                        local.Property = 0;
                    }
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                    public int Field;
                    public int Property { get; set; }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """);
 
        [Fact]
        public Task ReturnLocalByValueAsync()
            => new VerifyCS.Test
            {
                TestCode = """
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle Method()
                    {
                        GCHandle handle = default;
                        return handle;
                    }
                }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task TestReturnMemberAsync()
            => new VerifyCS.Test
            {
                TestCode = """
                using System.Runtime.InteropServices;
 
                class C
                {
                    CannotCopy _field;
                    readonly CannotCopy _readonlyField;
 
                    int ReturnFieldMemberField()
                    {
                        return _field.Field;
                    }
 
                    int ReturnReadonlyFieldMemberField()
                    {
                        return _readonlyField.Field;
                    }
 
                    int ReturnParameterMemberField(CannotCopy parameter)
                    {
                        return parameter.Field;
                    }
 
                    int ReturnArrayParameterMemberField(CannotCopy[] parameter)
                    {
                        return parameter[0].Field;
                    }
 
                    int ReturnLocalMemberField()
                    {
                        CannotCopy local = new CannotCopy();
                        return local.Field;
                    }
 
                    int ReturnFieldMemberProperty()
                    {
                        return _field.Property;
                    }
 
                    int ReturnReadonlyFieldMemberProperty()
                    {
                        return {|#0:_readonlyField|}.Property;
                    }
 
                    int ReturnParameterMemberProperty(CannotCopy parameter)
                    {
                        return parameter.Property;
                    }
 
                    int ReturnArrayParameterMemberProperty(CannotCopy[] parameter)
                    {
                        return parameter[0].Property;
                    }
 
                    int ReturnLocalMemberProperty()
                    {
                        CannotCopy local = new CannotCopy();
                        return local.Property;
                    }
 
                    int ReturnFieldMemberReadonlyProperty()
                    {
                        return _field.ReadonlyProperty;
                    }
 
                    int ReturnReadonlyFieldMemberReadonlyProperty()
                    {
                        return _readonlyField.ReadonlyProperty;
                    }
 
                    int ReturnParameterMemberReadonlyProperty(CannotCopy parameter)
                    {
                        return parameter.ReadonlyProperty;
                    }
 
                    int ReturnArrayParameterMemberReadonlyProperty(CannotCopy[] parameter)
                    {
                        return parameter[0].ReadonlyProperty;
                    }
 
                    int ReturnLocalMemberReadonlyProperty()
                    {
                        CannotCopy local = new CannotCopy();
                        return local.ReadonlyProperty;
                    }
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                    public int Field;
                    public int Property { get { return 0; } }
                    public int ReadonlyProperty { readonly get { return 0; } set { } }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                ExpectedDiagnostics =
                {
                    // The only reported diagnostic occurs for the invocation of a non-readonly getter of a readonly
                    // non-copyable field.
                    //
                    // /0/Test0.cs(42,16): warning RS0042: Unsupported use of non-copyable type 'CannotCopy' in 'FieldReference' operation
                    VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(0).WithArguments("CannotCopy", "FieldReference"),
                },
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task NonReadonlyMemberPropertiesAsync()
            => new VerifyCS.Test
            {
                TestCode = """
                using System.Runtime.InteropServices;
 
                [NonCopyable]
                struct CannotCopy
                {
                    public int First { get { return 0; } }
                    public int Second { get { return First; } }
                    public int Third => First;
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task NonReadonlyMemberMethodsAsync()
            => new VerifyCS.Test
            {
                TestCode = """
                using System.Runtime.InteropServices;
 
                [NonCopyable]
                struct CannotCopy
                {
                    public int First() { return 0; }
                    public int Second() { return First(); }
                    public int Third() => First();
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task AllowObjectInitializerAsync()
            => new VerifyCS.Test
            {
                TestCode = """
                using System.Runtime.InteropServices;
 
                class C
                {
                    CannotCopy Method()
                    {
                        return new CannotCopy() { First = 0, Second = 1 };
                    }
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                    public int First { get; set; }
                    public int Second { get; set; }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task AllowCustomForeachEnumeratorAsync()
            => new VerifyCS.Test
            {
                TestCode = """
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method()
                    {
                        var cannotCopy = new CannotCopy();
                        foreach (var obj in cannotCopy)
                        {
                        }
                    }
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                    public Enumerator GetEnumerator() => throw null;
 
                    public struct Enumerator
                    {
                        public object Current => throw null;
                        public bool MoveNext() => throw null;
                    }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Theory]
        [CombinatorialData]
        public async Task AllowCustomForeachEnumeratorParameterReferenceAsync(
            [CombinatorialValues("", "ref", "in")] string parameterModifiers,
            [CombinatorialValues("", "readonly")] string getEnumeratorModifiers)
        {
            var expected = (parameterModifiers, getEnumeratorModifiers) switch
            {
                // /0/Test0.cs(8,29): warning RS0042: Unsupported use of non-copyable type 'CannotCopy' in 'ParameterReference' operation
                ("in", "") => [VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(0).WithArguments("CannotCopy", "ParameterReference")],
 
                _ => DiagnosticResult.EmptyDiagnosticResults,
            };
 
            var test = new VerifyCS.Test
            {
                TestCode = $$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method({{parameterModifiers}} CannotCopy cannotCopy)
                    {
                        foreach (var obj in {|#0:cannotCopy|})
                        {
                        }
                    }
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                    public {{getEnumeratorModifiers}} Enumerator GetEnumerator() => throw null;
 
                    public struct Enumerator
                    {
                        public object Current => throw null;
                        public bool MoveNext() => throw null;
                    }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            };
 
            test.ExpectedDiagnostics.AddRange(expected);
            await test.RunAsync();
        }
 
        [Fact]
        public Task AllowCustomForeachEnumeratorDisposableObject1Async()
            => new VerifyCS.Test
            {
                TestCode = """
                using System;
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method()
                    {
                        using var cannotCopy = new CannotCopy();
                        foreach (var obj in cannotCopy)
                        {
                        }
                    }
                }
 
                [NonCopyable]
                struct CannotCopy : IDisposable
                {
                    public void Dispose() => throw null;
                    public Enumerator GetEnumerator() => throw null;
 
                    public struct Enumerator
                    {
                        public object Current => throw null;
                        public bool MoveNext() => throw null;
                    }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task AllowCustomForeachEnumeratorDisposableObject2Async()
            => new VerifyCS.Test
            {
                TestCode = """
                using System;
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method()
                    {
                        using (var cannotCopy = new CannotCopy())
                        {
                            foreach (var obj in cannotCopy)
                            {
                            }
                        }
                    }
                }
 
                [NonCopyable]
                struct CannotCopy : IDisposable
                {
                    public void Dispose() => throw null;
                    public Enumerator GetEnumerator() => throw null;
 
                    public struct Enumerator
                    {
                        public object Current => throw null;
                        public bool MoveNext() => throw null;
                    }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Theory]
        [InlineData("new CannotCopy()")]
        [InlineData("default(CannotCopy)")]
        [InlineData("CannotCopy.Create()")]
        [InlineData("CannotCopy.Empty")]
        public Task AllowDisposableObjectAsync(string creation)
            => new VerifyCS.Test
            {
                TestCode = $$"""
                using System;
                using System.Runtime.InteropServices;
 
                class C
                {
                    void UsingStatement()
                    {
                        using ({{creation}})
                        {
                        }
                    }
 
                    void UsingStatementWithVariable()
                    {
                        using (var cannotCopy = {{creation}})
                        {
                        }
                    }
 
                    void UsingStatementWithDiscard()
                    {
                        using (_ = {{creation}})
                        {
                        }
                    }
 
                    void UsingDeclarationStatement()
                    {
                        using var cannotCopy = {{creation}};
                    }
                }
 
                [NonCopyable]
                struct CannotCopy : IDisposable
                {
                    public static CannotCopy Empty => throw null;
                    public static CannotCopy Create() => throw null;
 
                    public void Dispose() => throw null;
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task AllowCustomForeachReadonlyEnumeratorAsync()
            => new VerifyCS.Test
            {
                TestCode = """
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method()
                    {
                        var cannotCopy = new CannotCopy();
                        foreach (var obj in cannotCopy)
                        {
                        }
                    }
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                    public readonly Enumerator GetEnumerator() => throw null;
 
                    public struct Enumerator
                    {
                        public object Current => throw null;
                        public bool MoveNext() => throw null;
                    }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Theory]
        [InlineData("")]
        [InlineData("ref")]
        [InlineData("in")]
        public Task AllowNameOfParameterReferenceAsync(string parameterModifiers)
            => new VerifyCS.Test
            {
                TestCode = $$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method({{parameterModifiers}} CannotCopy value)
                    {
                        _ = nameof(CannotCopy);
                        _ = nameof(value);
                        _ = nameof(value.ToString);
                    }
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Theory]
        [InlineData("ref")]
        [InlineData("in")]
        public Task AllowUnsafeAsRefParameterReferenceAsync(string parameterModifiers)
            => new VerifyCS.Test
            {
                TestCode = $$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    ref CannotCopy Method({{parameterModifiers}} CannotCopy cannotCopy)
                    {
                        return ref AsRef(in cannotCopy);
                    }
 
                    ref T AsRef<T>(in T value)
                        => throw null;
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Theory]
        [CombinatorialData]
        public Task AllowUnsafeAsRefParameterReference2Async(
            [CombinatorialValues("ref", "in", "ref readonly")] string parameterModifiers,
            [CombinatorialValues("in", "scoped in", "ref readonly", "scoped ref readonly")] string asRefParameterModifiers)
            => new VerifyCS.Test
            {
                TestCode = $$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    ref CannotCopy Method({{parameterModifiers}} CannotCopy cannotCopy)
                    {
                        return ref AsRef(in cannotCopy);
                    }
 
                    ref T AsRef<T>({{asRefParameterModifiers}} T value)
                        => throw null;
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp12,
            }.RunAsync();
 
        [Theory]
        [InlineData("ref")]
        [InlineData("in")]
        public async Task StoreUnsafeAsRefParameterReferenceToLocalAsync(string parameterModifiers)
        {
            var localModifiers = parameterModifiers switch
            {
                "in" => "ref readonly",
                _ => parameterModifiers,
            };
            await new VerifyCS.Test
            {
                TestCode = $$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method({{parameterModifiers}} CannotCopy cannotCopy)
                    {
                        {{localModifiers}} var local = ref AsRef(in cannotCopy);
 
                        local = ref AsRef(in cannotCopy);
                    }
 
                    ref T AsRef<T>(in T value)
                        => throw null;
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
        }
 
        [Fact]
        public Task CannotStoreRefReturnByValueAsync()
            => new VerifyCS.Test
            {
                TestCode = $$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    void Method(in CannotCopy cannotCopy)
                    {
                        // Test with initializer
                        var local {|#0:= AsRef(in cannotCopy)|};
 
                        // Test with assignment to local
                        local = {|#1:AsRef(in cannotCopy)|};
 
                        // Implicit and explicit discard is acceptable
                        AsRef(in cannotCopy);
                        _ = AsRef(in cannotCopy);
                    }
 
                    ref T AsRef<T>(in T value)
                        => throw null;
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                ExpectedDiagnostics =
                {
                    // /0/Test0.cs(8,19): warning RS0042: Cannot assign a value from a reference to non-copyable type 'CannotCopy'
                    VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoAssignValueFromReferenceRule).WithLocation(0).WithArguments("CannotCopy"),
                    // /0/Test0.cs(11,17): warning RS0042: Cannot assign a value from a reference to non-copyable type 'CannotCopy'
                    VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoAssignValueFromReferenceRule).WithLocation(1).WithArguments("CannotCopy"),
                },
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task CannotReturnRefReturnByValueAsync()
            => new VerifyCS.Test
            {
                TestCode = $$"""
                using System.Runtime.InteropServices;
 
                class C
                {
                    CannotCopy Method(in CannotCopy cannotCopy)
                    {
                        return {|#0:AsRef(in cannotCopy)|};
                    }
 
                    ref T AsRef<T>(in T value)
                        => throw null;
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                ExpectedDiagnostics =
                {
                    // /0/Test0.cs(8,16): warning RS0042: Cannot return a value from a reference to non-copyable type 'CannotCopy'
                    VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoReturnValueFromReferenceRule).WithLocation(0).WithArguments("CannotCopy"),
                },
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp8,
            }.RunAsync();
 
        [Fact]
        public Task TestNonCopyableAttributeAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    CanCopy _canCopy;
                    CannotCopy _cannotCopy;
 
                    void Method(object value)
                    {
                        Method(_canCopy);
                        Method({|#0:_cannotCopy|});
                    }
                }
 
                struct CanCopy
                {
                }
 
                [NonCopyable]
                struct CannotCopy
                {
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                // /0/Test0.cs(12,16): warning RS0042: Unsupported use of non-copyable type 'CannotCopy' in 'FieldReference' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(0).WithArguments("CannotCopy", "FieldReference"));
 
        [Fact]
        public Task DoNotWrapNonCopyableTypeInNullableTAsync()
            => VerifyCS.VerifyAnalyzerAsync("""
                using System.Runtime.InteropServices;
 
                class C
                {
                    GCHandle? field {|#0:= null|};
                }
                """,
                // /0/Test0.cs(6,21): warning RS0042: Do not wrap non-copyable type 'System.Runtime.InteropServices.GCHandle?' in 'FieldInitializer' operation
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.AvoidNullableWrapperRule).WithLocation(0).WithArguments("System.Runtime.InteropServices.GCHandle?", "FieldInitializer"));
 
        [Fact]
        public async Task DoNotDefineNonCopyableFieldInCopyableTypeAsync()
        {
            await VerifyCS.VerifyAnalyzerAsync("""
                class C1
                {
                    CanCopy field1;
                    CannotCopy field2;
                }
 
                struct C2
                {
                    CanCopy field1;
                    CannotCopy {|#0:field2|};
                }
 
                [NonCopyable]
                struct C3
                {
                    CanCopy field1;
                    CannotCopy field2;
                }
 
                struct CanCopy { }
                [NonCopyable] struct CannotCopy { }
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                // /0/Test0.cs(11,16): warning RS0042: Copyable field 'C2.field2' cannot have non-copyable type 'CannotCopy'
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoFieldOfCopyableTypeRule).WithLocation(0).WithArguments("CannotCopy", "C2.field2"));
 
            await VerifyVB.VerifyAnalyzerAsync("""
                Class C1
                    Private field1 As CanCopy
                    Private field2 As CannotCopy
                End Class
 
                Structure C2
                    Private field1 As CanCopy
                    Private {|#0:field2|} As CannotCopy
                End Structure
 
                <NonCopyable>
                Structure C3
                    Private field1 As CanCopy
                    Private field2 As CannotCopy
                End Structure
 
                Structure CanCopy : End Structure
                <NonCopyable> Structure CannotCopy : End Structure
                Public NotInheritable Class NonCopyableAttribute : Inherits System.Attribute : End Class
                """,
                // /0/Test0.vb(9,13): warning RS0042: Copyable field 'Private field2 As CannotCopy' cannot have non-copyable type 'CannotCopy'
                VerifyVB.Diagnostic(AbstractDoNotCopyValue.NoFieldOfCopyableTypeRule).WithLocation(0).WithArguments("CannotCopy", "Private field2 As CannotCopy"));
        }
 
        [Fact]
        public async Task DoNotDefineNonCopyableAutoPropertyAsync()
        {
            await VerifyCS.VerifyAnalyzerAsync("""
                class C1
                {
                    CanCopy Property1 { get; set; }
                    CanCopy Property2 { get => throw null; set => throw null; }
                    CannotCopy {|#0:Property3|} { get; set; }
                    CannotCopy Property4 { get => throw null; set => throw null; }
                }
 
                struct C2
                {
                    CanCopy Property1 { get; set; }
                    CanCopy Property2 { get => throw null; set => throw null; }
                    CannotCopy {|#1:Property3|} { get; set; }
                    CannotCopy Property4 { get => throw null; set => throw null; }
                }
 
                struct CanCopy { }
                [NonCopyable] struct CannotCopy { }
                internal sealed class NonCopyableAttribute : System.Attribute { }
                """,
                // /0/Test0.cs(6,16): warning RS0042: Auto-property 'C1.Property3' cannot have non-copyable type 'CannotCopy'
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoAutoPropertyRule).WithLocation(0).WithArguments("CannotCopy", "C1.Property3"),
                // /0/Test0.cs(14,16): warning RS0042: Auto-property 'C2.Property3' cannot have non-copyable type 'CannotCopy'
                VerifyCS.Diagnostic(AbstractDoNotCopyValue.NoAutoPropertyRule).WithLocation(1).WithArguments("CannotCopy", "C2.Property3"));
 
            await VerifyVB.VerifyAnalyzerAsync("""
                Class C1
                    Private Property Property1 As CanCopy
 
                    Private Property Property2 As CanCopy
                        Get
                            Throw DirectCast(Nothing, System.Exception)
                        End Get
                        Set
                            Throw DirectCast(Nothing, System.Exception)
                        End Set
                    End Property
 
                    Private Property {|#0:Property3|} As CannotCopy
 
                    Private Property Property4 As CannotCopy
                        Get
                            Throw DirectCast(Nothing, System.Exception)
                        End Get
                        Set
                            Throw DirectCast(Nothing, System.Exception)
                        End Set
                    End Property
                End Class
 
                Structure C2
                    Private Property Property1 As CanCopy
 
                    Private Property Property2 As CanCopy
                        Get
                            Throw DirectCast(Nothing, System.Exception)
                        End Get
                        Set
                            Throw DirectCast(Nothing, System.Exception)
                        End Set
                    End Property
 
                    Private Property {|#1:Property3|} As CannotCopy
 
                    Private Property Property4 As CannotCopy
                        Get
                            Throw DirectCast(Nothing, System.Exception)
                        End Get
                        Set
                            Throw DirectCast(Nothing, System.Exception)
                        End Set
                    End Property
                End Structure
 
                Structure CanCopy : End Structure
                <NonCopyable> Structure CannotCopy : End Structure
                Public NotInheritable Class NonCopyableAttribute : Inherits System.Attribute : End Class
                """,
                // /0/Test0.vb(14,22): warning RS0042: Auto-property 'Private Property Property3 As CannotCopy' cannot have non-copyable type 'CannotCopy'
                VerifyVB.Diagnostic(AbstractDoNotCopyValue.NoAutoPropertyRule).WithLocation(0).WithArguments("CannotCopy", "Private Property Property3 As CannotCopy"),
                // /0/Test0.vb(38,22): warning RS0042: Auto-property 'Private Property Property3 As CannotCopy' cannot have non-copyable type 'CannotCopy'
                VerifyVB.Diagnostic(AbstractDoNotCopyValue.NoAutoPropertyRule).WithLocation(1).WithArguments("CannotCopy", "Private Property Property3 As CannotCopy"));
        }
 
        [Fact]
        public Task AllowCopyFromCollectionExpression()
            => new VerifyCS.Test
            {
                TestCode = """
                using System;
                using System.Collections;
                using System.Collections.Generic;
                using System.Runtime.CompilerServices;
 
                [NonCopyable]
                [CollectionBuilder(typeof(MyCollection), nameof(Create))]
                partial struct MyCollection : IEnumerable<int>
                {
                    public IEnumerator<int> GetEnumerator() => null;
                    IEnumerator IEnumerable.GetEnumerator() => null;
 
                    public static MyCollection Create(ReadOnlySpan<int> r) => throw null;
                }
 
                class C
                {
                    void M()
                    {
                        MyCollection m = [1, 2, 3];
                        m = [];
                    }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
 
                namespace System.Runtime.CompilerServices
                {
                    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
                    internal sealed class CollectionBuilderAttribute : Attribute
                    {
                        public CollectionBuilderAttribute(Type builderType, string methodName)
                        {
                            BuilderType = builderType;
                            MethodName = methodName;
                        }
 
                        public Type BuilderType { get; }
 
                        public string MethodName { get; }
                    }
                }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp12,
            }.RunAsync();
 
        [Fact]
        public Task DoNotAllowCopyInCollectionExpressionElement()
            => new VerifyCS.Test
            {
                TestCode = """
                using System;
                using System.Collections;
                using System.Collections.Generic;
                using System.Runtime.CompilerServices;
 
                [NonCopyable]
                struct S
                {
                }
 
                [CollectionBuilder(typeof(MyCollection), nameof(Create))]
                partial struct MyCollection : IEnumerable<S>
                {
                    public IEnumerator<S> GetEnumerator() => throw null;
                    IEnumerator IEnumerable.GetEnumerator() => throw null;
 
                    public static MyCollection Create(ReadOnlySpan<S> r) => throw null;
                }
 
                class C
                {
                    void M()
                    {
                        S s = new();
                        MyCollection m = [{|#0:s|}, new S()];
                    }
                }
 
                internal sealed class NonCopyableAttribute : System.Attribute { }
 
                namespace System.Runtime.CompilerServices
                {
                    [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, Inherited = false)]
                    internal sealed class CollectionBuilderAttribute : Attribute
                    {
                        public CollectionBuilderAttribute(Type builderType, string methodName)
                        {
                            BuilderType = builderType;
                            MethodName = methodName;
                        }
 
                        public Type BuilderType { get; }
 
                        public string MethodName { get; }
                    }
                }
                """,
                LanguageVersion = Microsoft.CodeAnalysis.CSharp.LanguageVersion.CSharp12,
                ExpectedDiagnostics = {
                    // /0/Test0.cs(25,27): warning RS0042: Unsupported use of non-copyable type 'S' in 'LocalReference' operation
                    VerifyCS.Diagnostic(AbstractDoNotCopyValue.UnsupportedUseRule).WithLocation(0).WithArguments("S", "LocalReference")
                }
            }.RunAsync();
    }
}