From 9c62d647ffa0a1dd68bee25c378e07607d0cae49 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Nov 2021 15:49:14 +0100 Subject: [PATCH 1/3] Include NonCopyableAnalyzer --- Directory.Build.props | 8 +- pythonnet.sln | 21 +- src/noncopyable_analyzer/LICENSE | 21 ++ src/noncopyable_analyzer/NonCopyable.csproj | 27 ++ .../NonCopyableAnalyzer.cs | 252 ++++++++++++++++++ src/noncopyable_analyzer/TypeExtensions.cs | 90 +++++++ 6 files changed, 414 insertions(+), 5 deletions(-) create mode 100644 src/noncopyable_analyzer/LICENSE create mode 100644 src/noncopyable_analyzer/NonCopyable.csproj create mode 100644 src/noncopyable_analyzer/NonCopyableAnalyzer.cs create mode 100644 src/noncopyable_analyzer/TypeExtensions.cs diff --git a/Directory.Build.props b/Directory.Build.props index e0cd93ede..17bbe6dff 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,9 +13,9 @@ all runtime; build; native; contentfiles; analyzers - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + false + Analyzer + diff --git a/pythonnet.sln b/pythonnet.sln index 4ca4fb285..3151454c3 100644 --- a/pythonnet.sln +++ b/pythonnet.sln @@ -1,4 +1,4 @@ -Microsoft Visual Studio Solution File, Format Version 12.00 +Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.30717.126 MinimumVisualStudioVersion = 15.0.26124.0 @@ -46,6 +46,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Build", "Build", "{142A6752 Directory.Build.props = Directory.Build.props EndProjectSection EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{D4963EF0-46CD-43AF-939D-BA47C1B091D1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NonCopyable", "src\noncopyable_analyzer\NonCopyable.csproj", "{CA041F36-A4C2-4B18-9501-F670FDED87F4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -152,6 +156,18 @@ Global {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x64.Build.0 = Release|Any CPU {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.ActiveCfg = Release|Any CPU {35CBBDEB-FC07-4D04-9D3E-F88FC180110B}.Release|x86.Build.0 = Release|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x64.ActiveCfg = Debug|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x64.Build.0 = Debug|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x86.ActiveCfg = Debug|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Debug|x86.Build.0 = Debug|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|Any CPU.Build.0 = Release|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x64.ActiveCfg = Release|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x64.Build.0 = Release|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x86.ActiveCfg = Release|Any CPU + {CA041F36-A4C2-4B18-9501-F670FDED87F4}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -159,4 +175,7 @@ Global GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {C8845072-C642-4858-8627-27E862AD21BB} EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CA041F36-A4C2-4B18-9501-F670FDED87F4} = {D4963EF0-46CD-43AF-939D-BA47C1B091D1} + EndGlobalSection EndGlobal diff --git a/src/noncopyable_analyzer/LICENSE b/src/noncopyable_analyzer/LICENSE new file mode 100644 index 000000000..76b0b9f58 --- /dev/null +++ b/src/noncopyable_analyzer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2017 Nobuyuki Iwanaga + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/src/noncopyable_analyzer/NonCopyable.csproj b/src/noncopyable_analyzer/NonCopyable.csproj new file mode 100644 index 000000000..ef8890d2b --- /dev/null +++ b/src/noncopyable_analyzer/NonCopyable.csproj @@ -0,0 +1,27 @@ + + + + netstandard2.0 + false + True + latest + + + + PythonNet.NonCopyableAnalyzer + Nobuyuki Iwanaga + https://github.com/ufcpp/NonCopyableAnalyzer/blob/master/LICENSE + https://github.com/ufcpp/NonCopyableAnalyzer + false + Analyzer for Non-copyable struct + Fixed false positive on conversion operators with in argument. + NonCopyable, analyzers + true + + + + + + + + diff --git a/src/noncopyable_analyzer/NonCopyableAnalyzer.cs b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs new file mode 100644 index 000000000..4526f4b7e --- /dev/null +++ b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs @@ -0,0 +1,252 @@ +using System.Collections.Immutable; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Diagnostics; +using Microsoft.CodeAnalysis.Operations; + +namespace NonCopyable +{ + [DiagnosticAnalyzer(LanguageNames.CSharp)] + public class NonCopyableAnalyzer : DiagnosticAnalyzer + { + private static DiagnosticDescriptor CreateRule(int num, string type) + => new DiagnosticDescriptor("NoCopy" + num.ToString("00"), "non-copyable", "🚫 " + type + ". '{0}' is non-copyable.", "Correction", DiagnosticSeverity.Error, isEnabledByDefault: true); + + private static DiagnosticDescriptor FieldDeclarationRule = CreateRule(1, "field declaration"); + private static DiagnosticDescriptor InitializerRule = CreateRule(2, "initializer"); + private static DiagnosticDescriptor AssignmentRule = CreateRule(3, "assignment"); + private static DiagnosticDescriptor ArgumentRule = CreateRule(4, "argument"); + private static DiagnosticDescriptor ReturnRule = CreateRule(5, "return"); + private static DiagnosticDescriptor ConversionRule = CreateRule(6, "conversion"); + private static DiagnosticDescriptor PatternRule = CreateRule(7, "pattern matching"); + private static DiagnosticDescriptor TupleRule = CreateRule(8, "tuple"); + private static DiagnosticDescriptor MemberRule = CreateRule(9, "member reference"); + private static DiagnosticDescriptor ReadOnlyInvokeRule = CreateRule(10, "readonly invoke"); + private static DiagnosticDescriptor GenericConstraintRule = CreateRule(11, "generic constraint"); + private static DiagnosticDescriptor DelegateRule = CreateRule(12, "delegate"); + + public override ImmutableArray SupportedDiagnostics { get; } = ImmutableArray.Create(FieldDeclarationRule, InitializerRule, AssignmentRule, ArgumentRule, ReturnRule, ConversionRule, PatternRule, TupleRule, MemberRule, ReadOnlyInvokeRule, GenericConstraintRule, DelegateRule); + + public override void Initialize(AnalysisContext context) + { + context.RegisterCompilationStartAction(csc => + { + csc.RegisterOperationAction(oc => + { + var op = (ISymbolInitializerOperation)oc.Operation; + CheckCopyability(oc, op.Value, InitializerRule); + }, OperationKind.FieldInitializer, + OperationKind.ParameterInitializer, + OperationKind.PropertyInitializer, + OperationKind.VariableInitializer); + + csc.RegisterOperationAction(oc => + { + // including member initializer + // including collection element initializer + var op = (ISimpleAssignmentOperation)oc.Operation; + if (op.IsRef) return; + CheckCopyability(oc, op.Value, AssignmentRule); + }, OperationKind.SimpleAssignment); + + csc.RegisterOperationAction(oc => + { + // including non-ref extension method invocation + var op = (IArgumentOperation)oc.Operation; + if (op.Parameter.RefKind != RefKind.None) return; + CheckCopyability(oc, op.Value, ArgumentRule); + }, OperationKind.Argument); + + csc.RegisterOperationAction(oc => + { + var op = (IReturnOperation)oc.Operation; + if (op.ReturnedValue == null) return; + CheckCopyability(oc, op.ReturnedValue, ReturnRule); + }, OperationKind.Return, + OperationKind.YieldReturn); + + csc.RegisterOperationAction(oc => + { + var op = (IConversionOperation)oc.Operation; + var v = op.Operand; + if (v.Kind == OperationKind.DefaultValue) return; + var t = v.Type; + if (!t.IsNonCopyable()) return; + + if (op.OperatorMethod != null && op.OperatorMethod.Parameters.Length == 1) + { + var parameter = op.OperatorMethod.Parameters[0]; + if (parameter.RefKind != RefKind.None) return; + } + + if (op.Parent is IForEachLoopOperation && + op == ((IForEachLoopOperation)op.Parent).Collection && + op.Conversion.IsIdentity) + { + return; + } + + oc.ReportDiagnostic(Diagnostic.Create(ConversionRule, v.Syntax.GetLocation(), t.Name)); + }, OperationKind.Conversion); + + csc.RegisterOperationAction(oc => + { + var op = (IArrayInitializerOperation)oc.Operation; + + if (!((IArrayTypeSymbol)((IArrayInitializerOperation)op.Parent).Type).ElementType.IsNonCopyable()) return; + + foreach (var v in op.ElementValues) + { + CheckCopyability(oc, v, InitializerRule); + } + }, OperationKind.ArrayInitializer); + + csc.RegisterOperationAction(oc => + { + var op = (ICollectionElementInitializerOperation)oc.Operation; + + if (!HasNonCopyableParameter(op.AddMethod)) return; + + foreach (var a in op.Arguments) + { + CheckCopyability(oc, a, InitializerRule); + } + }, OperationKind.CollectionElementInitializer); + + csc.RegisterOperationAction(oc => + { + var op = (IDeclarationPatternOperation)oc.Operation; + var t = ((ILocalSymbol)op.DeclaredSymbol).Type; + if (!t.IsNonCopyable()) return; + oc.ReportDiagnostic(Diagnostic.Create(PatternRule, op.Syntax.GetLocation(), t.Name)); + }, OperationKind.DeclarationPattern); + + csc.RegisterOperationAction(oc => + { + var op = (ITupleOperation)oc.Operation; + + // exclude ParenthesizedVariableDesignationSyntax + if (op.Syntax.Kind() != SyntaxKind.TupleExpression) return; + + foreach (var v in op.Elements) + { + CheckCopyability(oc, v, TupleRule); + } + }, OperationKind.Tuple); + + csc.RegisterOperationAction(oc => + { + // instance property/event should not be referenced with in parameter/ref readonly local/readonly field + var op = (IMemberReferenceOperation)oc.Operation; + CheckInstanceReadonly(oc, op.Instance, MemberRule); + }, OperationKind.PropertyReference, + OperationKind.EventReference); + + csc.RegisterOperationAction(oc => + { + // instance method should not be invoked with in parameter/ref readonly local/readonly field + var op = (IInvocationOperation)oc.Operation; + + CheckGenericConstraints(oc, op, GenericConstraintRule); + CheckInstanceReadonly(oc, op.Instance, ReadOnlyInvokeRule); + + }, OperationKind.Invocation); + + csc.RegisterOperationAction(oc => + { + // delagate creation + var op = (IMemberReferenceOperation)oc.Operation; + if (op.Instance == null) return; + if (!op.Instance.Type.IsNonCopyable()) return; + oc.ReportDiagnostic(Diagnostic.Create(DelegateRule, op.Instance.Syntax.GetLocation(), op.Instance.Type.Name)); + }, OperationKind.MethodReference); + + csc.RegisterSymbolAction(sac => + { + var f = (IFieldSymbol)sac.Symbol; + if (f.IsStatic) return; + if (!f.Type.IsNonCopyable()) return; + if (f.ContainingType.IsReferenceType) return; + if (f.ContainingType.IsNonCopyable()) return; + sac.ReportDiagnostic(Diagnostic.Create(FieldDeclarationRule, f.DeclaringSyntaxReferences[0].GetSyntax().GetLocation(), f.Type.Name)); + }, SymbolKind.Field); + }); + + // not supported yet: + // OperationKind.CompoundAssignment, + // OperationKind.UnaryOperator, + // OperationKind.BinaryOperator, + } + + private static void CheckGenericConstraints(in OperationAnalysisContext oc, IInvocationOperation op, DiagnosticDescriptor rule) + { + var m = op.TargetMethod; + + if (m.IsGenericMethod) + { + var parameters = m.TypeParameters; + var arguments = m.TypeArguments; + for (int i = 0; i < parameters.Length; i++) + { + var p = parameters[i]; + var a = arguments[i]; + + if (a.IsNonCopyable() && !p.IsNonCopyable()) + oc.ReportDiagnostic(Diagnostic.Create(rule, op.Syntax.GetLocation(), a.Name)); + } + } + } + + private static void CheckInstanceReadonly(in OperationAnalysisContext oc, IOperation instance, DiagnosticDescriptor rule) + { + if (instance == null) return; + + var t = instance.Type; + if (!t.IsNonCopyable()) return; + + if (IsInstanceReadonly(instance)) + { + oc.ReportDiagnostic(Diagnostic.Create(rule, instance.Syntax.GetLocation(), t.Name)); + } + } + + private static bool IsInstanceReadonly(IOperation instance) + { + bool isReadOnly = false; + switch (instance) + { + case IFieldReferenceOperation r: + isReadOnly = r.Field.IsReadOnly; + break; + case ILocalReferenceOperation r: + isReadOnly = r.Local.RefKind == RefKind.In; + break; + case IParameterReferenceOperation r: + isReadOnly = r.Parameter.RefKind == RefKind.In; + break; + } + + return isReadOnly; + } + + private static bool HasNonCopyableParameter(IMethodSymbol m) + { + foreach (var p in m.Parameters) + { + if(p.RefKind == RefKind.None) + { + if (p.Type.IsNonCopyable()) return true; + } + } + return false; + } + + private static void CheckCopyability(in OperationAnalysisContext oc, IOperation v, DiagnosticDescriptor rule) + { + var t = v.Type; + if (!t.IsNonCopyable()) return; + if (v.CanCopy()) return; + oc.ReportDiagnostic(Diagnostic.Create(rule, v.Syntax.GetLocation(), t.Name)); + } + } +} diff --git a/src/noncopyable_analyzer/TypeExtensions.cs b/src/noncopyable_analyzer/TypeExtensions.cs new file mode 100644 index 000000000..743375653 --- /dev/null +++ b/src/noncopyable_analyzer/TypeExtensions.cs @@ -0,0 +1,90 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.Operations; + +namespace NonCopyable +{ + public static class TypeExtensions + { + public static bool IsNonCopyable(this ITypeSymbol t) + { + if (t == null) return false; + if (t.TypeKind != TypeKind.Struct && t.TypeKind != TypeKind.TypeParameter) return false; + + if (HasNonCopyableAttribute(t)) return true; + + if (t.TypeKind == TypeKind.Struct) + { + foreach (var ifType in t.AllInterfaces) + { + if (HasNonCopyableAttribute(ifType)) return true; + } + } + else + { + foreach (var constraint in ((ITypeParameterSymbol)t).ConstraintTypes) + { + if (HasNonCopyableAttribute(constraint)) return true; + } + + } + + return false; + } + + private static bool HasNonCopyableAttribute(ITypeSymbol t) + { + foreach (var a in t.GetAttributes()) + { + var str = a.AttributeClass.Name; + if (str.EndsWith("NonCopyable") || str.EndsWith("NonCopyableAttribute")) return true; + } + + return false; + } + + private static SyntaxList GetAttributes(this SyntaxNode syntax) + { + switch (syntax) + { + case StructDeclarationSyntax s: + return s.AttributeLists; + case TypeParameterSyntax s: + return s.AttributeLists; + default: + return default; + } + } + + /// + /// test whether op is copyable or not even when it is a non-copyable instance. + /// + public static bool CanCopy(this IOperation op) + { + var k = op.Kind; + + if (k == OperationKind.Conversion) + { + var operandKind = ((IConversionOperation)op).Operand.Kind; + // default literal (invalid if LangVersion < 7.1) + if (operandKind == OperationKind.DefaultValue || operandKind == OperationKind.Invalid) return true; + } + + if (k == OperationKind.LocalReference || k == OperationKind.FieldReference || k == OperationKind.PropertyReference || k == OperationKind.ArrayElementReference) + { + //need help: how to get ref-ness from IOperation? + var parent = op.Syntax.Parent.Kind(); + if (parent == SyntaxKind.RefExpression) return true; + } + + return k == OperationKind.ObjectCreation + || k == OperationKind.DefaultValue + || k == OperationKind.Literal + || k == OperationKind.Invocation; + + //todo: should return value be OK? + //todo: move semantics + } + } +} From 888692956daf8d9baa37f9b9ea7184c731069f24 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sat, 13 Nov 2021 16:35:36 +0100 Subject: [PATCH 2/3] Fix analyzer for array creation handling and deactivate return handling --- src/noncopyable_analyzer/NonCopyableAnalyzer.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/noncopyable_analyzer/NonCopyableAnalyzer.cs b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs index 4526f4b7e..0d80576f5 100644 --- a/src/noncopyable_analyzer/NonCopyableAnalyzer.cs +++ b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs @@ -57,13 +57,13 @@ public override void Initialize(AnalysisContext context) CheckCopyability(oc, op.Value, ArgumentRule); }, OperationKind.Argument); - csc.RegisterOperationAction(oc => - { - var op = (IReturnOperation)oc.Operation; - if (op.ReturnedValue == null) return; - CheckCopyability(oc, op.ReturnedValue, ReturnRule); - }, OperationKind.Return, - OperationKind.YieldReturn); + // csc.RegisterOperationAction(oc => + // { + // var op = (IReturnOperation)oc.Operation; + // if (op.ReturnedValue == null) return; + // CheckCopyability(oc, op.ReturnedValue, ReturnRule); + // }, OperationKind.Return, + // OperationKind.YieldReturn); csc.RegisterOperationAction(oc => { @@ -93,7 +93,7 @@ public override void Initialize(AnalysisContext context) { var op = (IArrayInitializerOperation)oc.Operation; - if (!((IArrayTypeSymbol)((IArrayInitializerOperation)op.Parent).Type).ElementType.IsNonCopyable()) return; + if (!((IArrayTypeSymbol)((IArrayCreationOperation)op.Parent).Type).ElementType.IsNonCopyable()) return; foreach (var v in op.ElementValues) { From 5f9648273f3742c2b4210ef4c229037d8a834355 Mon Sep 17 00:00:00 2001 From: Benedikt Reinartz Date: Sun, 14 Nov 2021 10:34:24 +0100 Subject: [PATCH 3/3] Update to lostmsu's fork --- .../NonCopyableAnalyzer.cs | 58 ++++++++++--------- src/noncopyable_analyzer/TypeExtensions.cs | 13 ++++- 2 files changed, 43 insertions(+), 28 deletions(-) diff --git a/src/noncopyable_analyzer/NonCopyableAnalyzer.cs b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs index 0d80576f5..ffbbbacb4 100644 --- a/src/noncopyable_analyzer/NonCopyableAnalyzer.cs +++ b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs @@ -57,13 +57,13 @@ public override void Initialize(AnalysisContext context) CheckCopyability(oc, op.Value, ArgumentRule); }, OperationKind.Argument); - // csc.RegisterOperationAction(oc => - // { - // var op = (IReturnOperation)oc.Operation; - // if (op.ReturnedValue == null) return; - // CheckCopyability(oc, op.ReturnedValue, ReturnRule); - // }, OperationKind.Return, - // OperationKind.YieldReturn); + csc.RegisterOperationAction(oc => + { + var op = (IReturnOperation)oc.Operation; + if (op.ReturnedValue == null) return; + CheckCopyability(oc, op.ReturnedValue, ReturnRule); + }, OperationKind.Return, + OperationKind.YieldReturn); csc.RegisterOperationAction(oc => { @@ -83,10 +83,10 @@ public override void Initialize(AnalysisContext context) op == ((IForEachLoopOperation)op.Parent).Collection && op.Conversion.IsIdentity) { - return; + return; } - oc.ReportDiagnostic(Diagnostic.Create(ConversionRule, v.Syntax.GetLocation(), t.Name)); + oc.ReportDiagnostic(Error(v.Syntax, ConversionRule, t.Name)); }, OperationKind.Conversion); csc.RegisterOperationAction(oc => @@ -101,24 +101,12 @@ public override void Initialize(AnalysisContext context) } }, OperationKind.ArrayInitializer); - csc.RegisterOperationAction(oc => - { - var op = (ICollectionElementInitializerOperation)oc.Operation; - - if (!HasNonCopyableParameter(op.AddMethod)) return; - - foreach (var a in op.Arguments) - { - CheckCopyability(oc, a, InitializerRule); - } - }, OperationKind.CollectionElementInitializer); - csc.RegisterOperationAction(oc => { var op = (IDeclarationPatternOperation)oc.Operation; var t = ((ILocalSymbol)op.DeclaredSymbol).Type; if (!t.IsNonCopyable()) return; - oc.ReportDiagnostic(Diagnostic.Create(PatternRule, op.Syntax.GetLocation(), t.Name)); + oc.ReportDiagnostic(Error(op.Syntax, PatternRule, t.Name)); }, OperationKind.DeclarationPattern); csc.RegisterOperationAction(oc => @@ -152,13 +140,24 @@ public override void Initialize(AnalysisContext context) }, OperationKind.Invocation); + csc.RegisterOperationAction(oc => { + var op = (IDynamicInvocationOperation)oc.Operation; + + foreach(var arg in op.Arguments) { + if (!arg.Type.IsNonCopyable()) continue; + + oc.ReportDiagnostic(Error(arg.Syntax, GenericConstraintRule)); + } + + }, OperationKind.DynamicInvocation); + csc.RegisterOperationAction(oc => { // delagate creation var op = (IMemberReferenceOperation)oc.Operation; if (op.Instance == null) return; if (!op.Instance.Type.IsNonCopyable()) return; - oc.ReportDiagnostic(Diagnostic.Create(DelegateRule, op.Instance.Syntax.GetLocation(), op.Instance.Type.Name)); + oc.ReportDiagnostic(Error(op.Instance.Syntax, DelegateRule, op.Instance.Type.Name)); }, OperationKind.MethodReference); csc.RegisterSymbolAction(sac => @@ -168,7 +167,7 @@ public override void Initialize(AnalysisContext context) if (!f.Type.IsNonCopyable()) return; if (f.ContainingType.IsReferenceType) return; if (f.ContainingType.IsNonCopyable()) return; - sac.ReportDiagnostic(Diagnostic.Create(FieldDeclarationRule, f.DeclaringSyntaxReferences[0].GetSyntax().GetLocation(), f.Type.Name)); + sac.ReportDiagnostic(Error(f.DeclaringSyntaxReferences[0].GetSyntax(), FieldDeclarationRule, f.Type.Name)); }, SymbolKind.Field); }); @@ -192,7 +191,7 @@ private static void CheckGenericConstraints(in OperationAnalysisContext oc, IInv var a = arguments[i]; if (a.IsNonCopyable() && !p.IsNonCopyable()) - oc.ReportDiagnostic(Diagnostic.Create(rule, op.Syntax.GetLocation(), a.Name)); + oc.ReportDiagnostic(Error(op.Syntax, rule, a.Name)); } } } @@ -206,10 +205,15 @@ private static void CheckInstanceReadonly(in OperationAnalysisContext oc, IOpera if (IsInstanceReadonly(instance)) { - oc.ReportDiagnostic(Diagnostic.Create(rule, instance.Syntax.GetLocation(), t.Name)); + oc.ReportDiagnostic(Error(instance.Syntax, rule, t.Name)); } } + private static Diagnostic Error(SyntaxNode at, DiagnosticDescriptor rule, string name = null) + => name is null + ? Diagnostic.Create(rule, at.GetLocation()) + : Diagnostic.Create(rule, at.GetLocation(), name); + private static bool IsInstanceReadonly(IOperation instance) { bool isReadOnly = false; @@ -246,7 +250,7 @@ private static void CheckCopyability(in OperationAnalysisContext oc, IOperation var t = v.Type; if (!t.IsNonCopyable()) return; if (v.CanCopy()) return; - oc.ReportDiagnostic(Diagnostic.Create(rule, v.Syntax.GetLocation(), t.Name)); + oc.ReportDiagnostic(Error(v.Syntax, rule, t.Name)); } } } diff --git a/src/noncopyable_analyzer/TypeExtensions.cs b/src/noncopyable_analyzer/TypeExtensions.cs index 743375653..a1570ad68 100644 --- a/src/noncopyable_analyzer/TypeExtensions.cs +++ b/src/noncopyable_analyzer/TypeExtensions.cs @@ -78,13 +78,24 @@ public static bool CanCopy(this IOperation op) if (parent == SyntaxKind.RefExpression) return true; } + if (k == OperationKind.Conditional) + { + var cond = (IConditionalOperation)op; + return cond.WhenFalse.CanCopy() && cond.WhenFalse.CanCopy(); + } + return k == OperationKind.ObjectCreation || k == OperationKind.DefaultValue || k == OperationKind.Literal - || k == OperationKind.Invocation; + || k == OperationKind.Invocation + // workaround for https://github.com/dotnet/roslyn/issues/49751 + || !IsValid(k) && op.Syntax is InvocationExpressionSyntax; //todo: should return value be OK? //todo: move semantics } + + static bool IsValid(OperationKind kind) + => kind != OperationKind.None && kind != OperationKind.Invalid; } }