Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
89b1beb
WIP
JuanCarlosGI Jul 22, 2019
5de1f59
Detect enumerator allocations
cdmihai Jul 22, 2019
a64853b
Boxing completed
JuanCarlosGI Jul 23, 2019
5fae7c2
Add dotMemory tests to ImplicitCast and ImplicitBoxing tests
JuanCarlosGI Jul 24, 2019
0956a9a
Fix test namespaces
JuanCarlosGI Jul 24, 2019
9808eca
Implement tuple implicit cast
JuanCarlosGI Jul 25, 2019
6149e01
Detect NoHiddenAllocation attribute
cdmihai Jul 25, 2019
7e2448d
Add attribute based analysis activation
cdmihai Jul 25, 2019
87215dc
Change test name
JuanCarlosGI Jul 26, 2019
da31028
Implement closure allocation and delegate creation analyzers.
SergeyTeplyakov Jul 26, 2019
3b7e940
add test case
cdmihai Jul 26, 2019
643ca76
Refactor allocation analysis activation
cdmihai Jul 26, 2019
ce00021
Extension methods, params, user-defined casts and linq
JuanCarlosGI Jul 26, 2019
e057ca5
Merge changes
JuanCarlosGI Jul 26, 2019
ead4159
Fix calls to detect if allocation should be analyzed
JuanCarlosGI Jul 26, 2019
a397c94
Test that allocation is activated for any attribute variant
cdmihai Jul 26, 2019
3c38371
Switch to netstandard2.0
SergeyTeplyakov Jul 26, 2019
62389f4
Fix issue with false warning on extension methods for structs.
SergeyTeplyakov Jul 26, 2019
9881e31
Fixes
SergeyTeplyakov Jul 26, 2019
c0f2bf7
Add Recursive option to NoHiddenAllocations attribute
cdmihai Jul 29, 2019
2142ac7
fix bad rebase
cdmihai Jul 29, 2019
af9bfbb
Fix broken tests
cdmihai Jul 29, 2019
aa5d9df
Do not trigger recursive allocation analyzer for library code
cdmihai Jul 30, 2019
2a7a3fb
Update attribute
cdmihai Jul 30, 2019
9173b22
Minor fixes.
SergeyTeplyakov Aug 14, 2019
f454c37
Merge with the remote
SergeyTeplyakov Aug 14, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/ErrorProne.NET.Cli/ErrorProne.NET.Cli.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net46</TargetFramework>
<TargetFramework>net472</TargetFramework>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>

Expand All @@ -13,8 +13,8 @@

<ItemGroup>
<PackageReference Include="Microsoft.Build.Locator" Version="1.0.31" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="2.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="2.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis" Version="3.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.MSBuild" Version="3.1.0" />
<PackageReference Include="Microsoft.Extensions.CommandLineUtils" Version="1.1.1" />
<PackageReference Include="SQLitePCLRaw.bundle_green" Version="1.1.11" />
</ItemGroup>
Expand Down
48 changes: 48 additions & 0 deletions src/ErrorProne.NET.Core/EnumerableExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using System.Collections.Generic;

namespace ErrorProne.NET.Core
{
public static class EnumerableExtensions
{
public static T MinByOrDefault<T, TKey>(this IEnumerable<T> items, Func<T, TKey> keySelector, IComparer<TKey> keyComparer = null)
{
keyComparer = keyComparer ?? Comparer<TKey>.Default;
T maxItem = default;
TKey maxKey = default;
bool isFirst = true;

foreach (var item in items)
{
var currentKey = keySelector(item);
if (isFirst || keyComparer.Compare(currentKey, maxKey) < 0)
{
isFirst = false;
maxItem = item;
maxKey = currentKey;
}
}

return maxItem;
}

public static Dictionary<TKey, List<TValue>> GroupToDictionary<TKey, TValue>(this IEnumerable<(TKey, TValue)> sequence)
{
Dictionary<TKey, List<TValue>> result = new Dictionary<TKey, List<TValue>>();

foreach (var (key, value) in sequence)
{
if (result.TryGetValue(key, out var list))
{
list.Add(value);
}
else
{
result.Add(key, new List<TValue>() {value});
}
}

return result;
}
}
}
21 changes: 16 additions & 5 deletions src/ErrorProne.NET.Core/ErrorProne.NET.Core.csproj
Original file line number Diff line number Diff line change
@@ -1,14 +1,25 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
<LangVersion>preview</LangVersion>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.1.11" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="2.9.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.0" PrivateAssets="all" />
<!--<PackageReference Include="ErrorProne.NET.CoreAnalyzers" Version="0.1.1.11" PrivateAssets="all" />-->
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="3.1.0" />
<PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="2.9.3" PrivateAssets="all" />
<PackageReference Include="RuntimeContracts" Version="0.1.7.2" />
</ItemGroup>


<PropertyGroup>
<InternalsAssemblyNames>Microsoft.CodeAnalysis;Microsoft.CodeAnalysis.CSharp</InternalsAssemblyNames>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="IgnoresAccessChecksToGenerator" Version="0.4.0" PrivateAssets="All" />
</ItemGroup>
</Project>
34 changes: 34 additions & 0 deletions src/ErrorProne.NET.Core/ForEachAnalysisHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.Operations;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Text;

namespace ErrorProne.NET.Core
{
// TODO: rename to unsafe?
public static class ForEachAnalysisHelper
{
public static IMethodSymbol GetEnumeratorMethod(this IForEachLoopOperation foreachLoop)
{
var loop = (BaseForEachLoopOperation)foreachLoop;

return loop.Info.GetEnumeratorMethod;
}

public static ITypeSymbol GetElementType(this IForEachLoopOperation foreachLoop)
{
var loop = (BaseForEachLoopOperation)foreachLoop;

return loop.Info.ElementType;
}

public static Conversion? GetConversionInfo(this IForEachLoopOperation foreachLoop)
{
var loop = (BaseForEachLoopOperation)foreachLoop;
return loop.Info.ElementConversion as Conversion?;
}
}
}
72 changes: 72 additions & 0 deletions src/ErrorProne.NET.Core/LocalScopeProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using System.Diagnostics;
using System.Linq;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;

#nullable enable

namespace ErrorProne.NET.Core
{
public static class LocalScopeProvider
{
public static SyntaxNode? GetScopeForDisplayClass(this ISymbol symbol)
{
// This method uses internal Roslyn API

if (symbol is SourceLocalSymbol localSymbol)
{
// For loop is special: a local declared in for-loop is scoped to the loop based on
// the language rules, but at "runtime" it actually kind of defined in the enclosing scope.
// It means that if an anonymous method captures a for variable, a display class is instantiated
// at the beginning of a parent scope, but not inside the for loop.

if (localSymbol.ScopeBinder is ForLoopBinder fb)
{
return fb.Next.ScopeDesignator;
}

return localSymbol.ScopeDesignatorOpt;
}

if (symbol is IParameterSymbol)
{
var declaredIn = symbol.ContainingSymbol;
var (blockBody, arrowBody) = declaredIn.GetBodies();
return (SyntaxNode)blockBody ?? arrowBody;
}

return null;
}

internal static (BlockSyntax blockBody, ArrowExpressionClauseSyntax arrowBody) GetBodies(this ISymbol methodSymbol)
{
var syntax = methodSymbol.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax();

{
switch (syntax)
{
case BaseMethodDeclarationSyntax method:
return (method.Body, method.ExpressionBody);

case AccessorDeclarationSyntax accessor:
return (accessor.Body, accessor.ExpressionBody);

case ArrowExpressionClauseSyntax arrowExpression:
Debug.Assert(arrowExpression.Parent.Kind() == SyntaxKind.PropertyDeclaration ||
arrowExpression.Parent.Kind() == SyntaxKind.IndexerDeclaration ||
methodSymbol is SynthesizedClosureMethod);
return (null, arrowExpression);

case BlockSyntax block:
Debug.Assert(methodSymbol is SynthesizedClosureMethod);
return (block, null);

default:
return (null, null);
}
}
}
}
}
39 changes: 38 additions & 1 deletion src/ErrorProne.NET.Core/SymbolExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

#nullable enable

namespace ErrorProne.NET.Core
{
/// <nodoc />
Expand Down Expand Up @@ -42,7 +44,42 @@ public static IEnumerable<ISymbol> GetAllUsedSymbols(Compilation compilation, Sy
}
}

public static bool TryGetMethodSyntax(this IMethodSymbol method, out MethodDeclarationSyntax result)
public static ITypeSymbol? GetSymbolType(this ISymbol symbol)
{
if (symbol is ILocalSymbol localSymbol)
{
return localSymbol.Type;
}

if (symbol is IFieldSymbol fieldSymbol)
{
return fieldSymbol.Type;
}

if (symbol is IPropertySymbol propertySymbol)
{
return propertySymbol.Type;
}

if (symbol is IParameterSymbol parameterSymbol)
{
return parameterSymbol.Type;
}

if (symbol is IAliasSymbol aliasSymbol)
{
return aliasSymbol.Target as ITypeSymbol;
}

if (symbol is IMethodSymbol ms)
{
return ms.ReturnType;
}

return symbol as ITypeSymbol;
}

public static bool TryGetMethodSyntax(this IMethodSymbol method, out MethodDeclarationSyntax? result)
{
result = method.DeclaringSyntaxReferences.FirstOrDefault()?.GetSyntax() as MethodDeclarationSyntax;
return result != null;
Expand Down
16 changes: 16 additions & 0 deletions src/ErrorProne.NET.Core/ValueTupleHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using System.Linq;

namespace ErrorProne.NET.Core
{
public static class ValueTupleHelper
{
public static ITypeSymbol[] GetTupleTypes(this ITypeSymbol tupleType)
{
var tuple = (TupleTypeSymbol)tupleType;

return tuple.TupleElementTypesWithAnnotations.Select(t => (ITypeSymbol)t.Type).ToArray();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<AssemblyName>ErrorProne.Net.CoreAnalyzers.CodeFixes</AssemblyName>
<RootNamespace>ErrorProne.NET</RootNamespace>
<IncludeBuildOutput>false</IncludeBuildOutput>
Expand All @@ -11,7 +10,7 @@
</PropertyGroup>

<PropertyGroup>
<TargetFramework>netstandard1.3</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>

<PropertyGroup>
Expand Down Expand Up @@ -53,6 +52,8 @@
<ItemGroup>
<!-- Analyzer packages should not have any dependencies. -->
<PackageReference Update="@(PackageReference)" PrivateAssets="all" />
<PackageReference Update="Microsoft.CodeAnalysis" Version="3.1.0" />
<PackageReference Update="Microsoft.CodeAnalysis.Analyzers" Version="2.9.3" />
<ProjectReference Update="@(ProjectReference)" PrivateAssets="all" />
</ItemGroup>

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
using System;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Diagnostics;
using Microsoft.CodeAnalysis.Testing;
using RoslynNUnitTestRunner;

namespace ErrorProne.NET.CoreAnalyzers.Tests.Allocations
{

static class AllocationTestHelper
{
public static void VerifyCode<TAnalyzer>(string code, bool injectAssemblyLevelConfigurationAttribute = true) where TAnalyzer : DiagnosticAnalyzer, new()
{
// enable all the allocation analyzers by adding an assembly level attribute
VerifyCodeAsync<TAnalyzer>(code, injectAssemblyLevelConfigurationAttribute).GetAwaiter().GetResult();
}

public static Task VerifyCodeAsync<TAnalyzer>(string code, bool injectAssemblyLevelConfigurationAttribute = true) where TAnalyzer : DiagnosticAnalyzer, new()
{
// enable all the allocation analyzers by adding an assembly level attribute
return VerifyCodeImpl<TAnalyzer>(code, injectAssemblyLevelConfigurationAttribute);
}

private static Task VerifyCodeImpl<TAnalyzer>(string code, bool injectAssemblyLevelConfigurationAttribute = false) where TAnalyzer : DiagnosticAnalyzer, new()
{
var test = new CSharpCodeFixVerifier<TAnalyzer, EmptyCodeFixProvider>.Test
{
TestState =
{
Sources =
{
code,
},
},
}.WithoutGeneratedCodeVerification().WithHiddenAllocationsAttributeDeclaration();

if (injectAssemblyLevelConfigurationAttribute)
{
test = test.WithAssemblyLevelHiddenAllocationsAttribute();
}

return test.RunAsync();
}
}

struct Struct
{
public void Method()
{
}

public static void StaticMethod()
{
}
}

struct StructWithOverrides
{
public override string ToString() => string.Empty;
public override int GetHashCode() => 42;
public override bool Equals(object other) => true;
}

struct ComparableStruct : IComparable
{
public int CompareTo(object obj) => 0;
}

[Flags]
enum E
{
V1 = 1,
V2 = 1 << 1
}
}
Loading