Skip to content

Commit

Permalink
KnownReference: Add support for testing for referenced libraries (#6726)
Browse files Browse the repository at this point in the history
  • Loading branch information
martin-strecker-sonarsource committed Feb 14, 2023
1 parent a2d988a commit c4258e5
Show file tree
Hide file tree
Showing 7 changed files with 426 additions and 14 deletions.
Expand Up @@ -73,7 +73,7 @@ protected override void Initialize(SonarAnalysisContext context)
private static bool IsDisposeMethodCalled(InvocationExpressionSyntax invocation, SemanticModel semanticModel, LanguageVersion languageVersion) =>
semanticModel.GetSymbolInfo(invocation).Symbol is IMethodSymbol methodSymbol
&& KnownMethods.IsIDisposableDispose(methodSymbol)
&& semanticModel.Compilation.GetTypeMethod(SpecialType.System_IDisposable, DisposeMethodName) is { } disposeMethodSignature
&& semanticModel.Compilation.SpecialTypeMethod(SpecialType.System_IDisposable, DisposeMethodName) is { } disposeMethodSignature
&& (methodSymbol.Equals(methodSymbol.ContainingType.FindImplementationForInterfaceMember(disposeMethodSignature))
|| methodSymbol.ContainingType.IsDisposableRefStruct(languageVersion));

Expand Down
Expand Up @@ -43,7 +43,7 @@ public sealed class DisposeNotImplementingDispose : SonarDiagnosticAnalyzer
return;
}
var disposeMethod = c.Compilation.GetTypeMethod(SpecialType.System_IDisposable, "Dispose");
var disposeMethod = c.Compilation.SpecialTypeMethod(SpecialType.System_IDisposable, "Dispose");
if (disposeMethod == null)
{
return;
Expand Down
Expand Up @@ -24,4 +24,15 @@ internal static class CompilationExtensions
{
public static INamedTypeSymbol GetTypeByMetadataName(this Compilation compilation, KnownType knownType) =>
compilation.GetTypeByMetadataName(knownType.FullName);

public static IMethodSymbol SpecialTypeMethod(this Compilation compilation, SpecialType type, string methodName) =>
(IMethodSymbol)compilation.GetSpecialType(type).GetMembers(methodName).SingleOrDefault();

public static bool IsNetFrameworkTarget(this Compilation compilation) =>
// There's no direct way of checking compilation target framework yet (09/2020).
// See https://github.com/dotnet/roslyn/issues/3798
compilation.ObjectType.ContainingAssembly.Name == "mscorlib";

public static bool References(this Compilation compilation, KnownAssembly assembly)
=> assembly.IsReferencedBy(compilation);
}
Expand Up @@ -18,18 +18,13 @@
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Helpers
namespace SonarAnalyzer.Extensions;

internal static class KnownAssemblyExtensions
{
public static class CompilationExtensions
{
public static IMethodSymbol GetTypeMethod(this Compilation compilation, SpecialType type, string methodName) =>
(IMethodSymbol)compilation.GetSpecialType(type)
.GetMembers(methodName)
.SingleOrDefault();
internal static Func<AssemblyIdentity, bool> And(this Func<AssemblyIdentity, bool> @this, Func<AssemblyIdentity, bool> predicate)
=> identity => @this(identity) && predicate(identity);

public static bool IsNetFrameworkTarget(this Compilation compilation) =>
// There's no direct way of checking compilation target framework yet (09/2020).
// See https://github.com/dotnet/roslyn/issues/3798
compilation.ObjectType.ContainingAssembly.Name == "mscorlib";
}
internal static Func<AssemblyIdentity, bool> Or(this Func<AssemblyIdentity, bool> @this, Func<AssemblyIdentity, bool> predicate)
=> identity => @this(identity) || predicate(identity);
}
@@ -0,0 +1,74 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

namespace SonarAnalyzer.Helpers;

public sealed partial class KnownAssembly
{
private const StringComparison AssemblyNameComparission = StringComparison.OrdinalIgnoreCase;

internal static class Predicates
{
internal static Func<AssemblyIdentity, bool> NameIs(string name) =>
x => x.Name.Equals(name, AssemblyNameComparission);

internal static Func<AssemblyIdentity, bool> StartsWith(string name) =>
x => x.Name.StartsWith(name, AssemblyNameComparission);

internal static Func<AssemblyIdentity, bool> EndsWith(string name) =>
x => x.Name.EndsWith(name, AssemblyNameComparission);

internal static Func<AssemblyIdentity, bool> Contains(string name) =>
x => x.Name.IndexOf(name, 0, AssemblyNameComparission) >= 0;

internal static Func<AssemblyIdentity, bool> VersionLowerThen(string version) =>
VersionLowerThen(Version.Parse(version));

internal static Func<AssemblyIdentity, bool> VersionLowerThen(Version version) =>
x => x.Version < version;

internal static Func<AssemblyIdentity, bool> VersionGreaterOrEqual(string version) =>
VersionGreaterOrEqual(Version.Parse(version));

internal static Func<AssemblyIdentity, bool> VersionGreaterOrEqual(Version version) =>
x => x.Version >= version;

internal static Func<AssemblyIdentity, bool> VersionBetween(string from, string to) =>
VersionBetween(Version.Parse(from), Version.Parse(to));

internal static Func<AssemblyIdentity, bool> VersionBetween(Version from, Version to) =>
x => x.Version >= from && x.Version <= to;

internal static Func<AssemblyIdentity, bool> OptionalPublicKeyTokenIs(string key) =>
x => !x.HasPublicKey || PublicKeyEqualHex(x, key);

internal static Func<AssemblyIdentity, bool> PublicKeyTokenIs(string key) =>
x => x.HasPublicKey && PublicKeyEqualHex(x, key);

private static bool PublicKeyEqualHex(AssemblyIdentity identity, string hexString)
{
var normalizedHexString = hexString.Replace("-", string.Empty);
return ArraysEqual(identity.PublicKeyToken.ToArray(), normalizedHexString) || ArraysEqual(identity.PublicKey.ToArray(), normalizedHexString);

static bool ArraysEqual(byte[] key, string hexString) =>
BitConverter.ToString(key).Replace("-", string.Empty).Equals(hexString, StringComparison.OrdinalIgnoreCase);
}
}
}
48 changes: 48 additions & 0 deletions analyzers/src/SonarAnalyzer.Common/Helpers/KnownAssembly.cs
@@ -0,0 +1,48 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/

using static SonarAnalyzer.Helpers.KnownAssembly.Predicates;

namespace SonarAnalyzer.Helpers;

public sealed partial class KnownAssembly
{
private readonly Func<IEnumerable<AssemblyIdentity>, bool> predicate;

public static KnownAssembly XUnit_Assert { get; } = new(
And(NameIs("xunit.assert").Or(NameIs("xunit").And(VersionLowerThen("2.0"))),
PublicKeyTokenIs("8d05b1bb7a6fdb6c")));

internal KnownAssembly(Func<AssemblyIdentity, bool> predicate, params Func<AssemblyIdentity, bool>[] or)
: this(predicate is null || or.Any(x => x is null)
? throw new ArgumentNullException(nameof(predicate), "All predicates must be non-null.")
: identities => identities.Any(identitiy => predicate(identitiy) || or.Any(orPredicate => orPredicate(identitiy))))
{
}

internal KnownAssembly(Func<IEnumerable<AssemblyIdentity>, bool> predicate) =>
this.predicate = predicate ?? throw new ArgumentNullException(nameof(predicate));

public bool IsReferencedBy(Compilation compilation) =>
predicate(compilation.ReferencedAssemblyNames);

internal static Func<AssemblyIdentity, bool> And(Func<AssemblyIdentity, bool> left, Func<AssemblyIdentity, bool> right) =>
KnownAssemblyExtensions.And(left, right);
}

0 comments on commit c4258e5

Please sign in to comment.