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..ffbbbacb4 --- /dev/null +++ b/src/noncopyable_analyzer/NonCopyableAnalyzer.cs @@ -0,0 +1,256 @@ +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(Error(v.Syntax, ConversionRule, t.Name)); + }, OperationKind.Conversion); + + csc.RegisterOperationAction(oc => + { + var op = (IArrayInitializerOperation)oc.Operation; + + if (!((IArrayTypeSymbol)((IArrayCreationOperation)op.Parent).Type).ElementType.IsNonCopyable()) return; + + foreach (var v in op.ElementValues) + { + CheckCopyability(oc, v, InitializerRule); + } + }, OperationKind.ArrayInitializer); + + csc.RegisterOperationAction(oc => + { + var op = (IDeclarationPatternOperation)oc.Operation; + var t = ((ILocalSymbol)op.DeclaredSymbol).Type; + if (!t.IsNonCopyable()) return; + oc.ReportDiagnostic(Error(op.Syntax, PatternRule, 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 => { + 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(Error(op.Instance.Syntax, DelegateRule, 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(Error(f.DeclaringSyntaxReferences[0].GetSyntax(), FieldDeclarationRule, 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(Error(op.Syntax, rule, 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(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; + 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(Error(v.Syntax, rule, t.Name)); + } + } +} diff --git a/src/noncopyable_analyzer/TypeExtensions.cs b/src/noncopyable_analyzer/TypeExtensions.cs new file mode 100644 index 000000000..a1570ad68 --- /dev/null +++ b/src/noncopyable_analyzer/TypeExtensions.cs @@ -0,0 +1,101 @@ +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; + } + + 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 + // 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; + } +} pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy