├── .gitattributes ├── .gitignore ├── ClrHeapAllocationAnalyzer.sln ├── ClrHeapAllocationsAnalyzer.Test ├── AllocationAnalyzerTests.cs ├── AssertEx.cs ├── AvoidAllocationWithArrayEmptyCodeFixTests.cs ├── CallSiteImplicitAllocationAnalyzerTests.cs ├── ClrHeapAllocationsAnalyzer.Test.csproj ├── ConcatenationAllocationAnalyzerTests.cs ├── DiagnosticEqualityComparer.cs ├── DisplayClassAllocationAnalyzerTests.cs ├── EnumeratorAllocationAnalyzerTests.cs ├── ExplicitAllocationAnalyzerTests.cs ├── IgnoreTests.cs ├── Properties │ └── AssemblyInfo.cs ├── StackOverflowAnswerTests.cs ├── TypeConversionAllocationAnalyzerTests.cs └── app.config ├── ClrHeapAllocationsAnalyzer.Vsix ├── ClrHeapAllocationAnalyzer.Vsix.csproj ├── LICENSE.txt └── source.extension.vsixmanifest ├── ClrHeapAllocationsAnalyzer ├── AllocationAnalyzer.cs ├── AllocationRules.cs ├── AvoidAllocationWithArrayEmptyCodeFix.cs ├── CallSiteImplicitAllocationAnalyzer.cs ├── ClrHeapAllocationAnalyzer.csproj ├── ConcatenationAllocationAnalyzer.cs ├── DisplayClassAllocationAnalyzer.cs ├── EnumeratorAllocationAnalyzer.cs ├── ExplicitAllocationAnalyzer.cs ├── HeapAllocationAnalyzerEventSource.cs ├── SyntaxHelper.cs └── TypeConversionAllocationAnalyzer.cs ├── LICENSE └── README.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Roslyn cache directories 20 | *.ide/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | #NUNIT 27 | *.VisualState.xml 28 | TestResult.xml 29 | 30 | # Build Results of an ATL Project 31 | [Dd]ebugPS/ 32 | [Rr]eleasePS/ 33 | dlldata.c 34 | 35 | *_i.c 36 | *_p.c 37 | *_i.h 38 | *.ilk 39 | *.meta 40 | *.obj 41 | *.pch 42 | *.pdb 43 | *.pgc 44 | *.pgd 45 | *.rsp 46 | *.sbr 47 | *.tlb 48 | *.tli 49 | *.tlh 50 | *.tmp 51 | *.tmp_proj 52 | *.log 53 | *.vspscc 54 | *.vssscc 55 | .builds 56 | *.pidb 57 | *.svclog 58 | *.scc 59 | 60 | # Chutzpah Test files 61 | _Chutzpah* 62 | 63 | # Visual C++ cache files 64 | ipch/ 65 | *.aps 66 | *.ncb 67 | *.opensdf 68 | *.sdf 69 | *.cachefile 70 | 71 | # Visual Studio profiler 72 | *.psess 73 | *.vsp 74 | *.vspx 75 | 76 | # TFS 2012 Local Workspace 77 | $tf/ 78 | 79 | # Guidance Automation Toolkit 80 | *.gpState 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper*/ 84 | *.[Rr]e[Ss]harper 85 | *.DotSettings.user 86 | 87 | # JustCode is a .NET coding addin-in 88 | .JustCode 89 | 90 | # TeamCity is a build add-in 91 | _TeamCity* 92 | 93 | # DotCover is a Code Coverage Tool 94 | *.dotCover 95 | 96 | # NCrunch 97 | _NCrunch_* 98 | .*crunch*.local.xml 99 | 100 | # MightyMoose 101 | *.mm.* 102 | AutoTest.Net/ 103 | 104 | # Web workbench (sass) 105 | .sass-cache/ 106 | 107 | # Installshield output folder 108 | [Ee]xpress/ 109 | 110 | # DocProject is a documentation generator add-in 111 | DocProject/buildhelp/ 112 | DocProject/Help/*.HxT 113 | DocProject/Help/*.HxC 114 | DocProject/Help/*.hhc 115 | DocProject/Help/*.hhk 116 | DocProject/Help/*.hhp 117 | DocProject/Help/Html2 118 | DocProject/Help/html 119 | 120 | # Click-Once directory 121 | publish/ 122 | 123 | # Publish Web Output 124 | *.[Pp]ublish.xml 125 | *.azurePubxml 126 | ## TODO: Comment the next line if you want to checkin your 127 | ## web deploy settings but do note that will include unencrypted 128 | ## passwords 129 | #*.pubxml 130 | 131 | # NuGet Packages Directory 132 | packages/* 133 | ## TODO: If the tool you use requires repositories.config 134 | ## uncomment the next line 135 | #!packages/repositories.config 136 | 137 | # Enable "build/" folder in the NuGet Packages folder since 138 | # NuGet packages use it for MSBuild targets. 139 | # This line needs to be after the ignore of the build folder 140 | # (and the packages folder if the line above has been uncommented) 141 | !packages/build/ 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | 163 | # RIA/Silverlight projects 164 | Generated_Code/ 165 | 166 | # Backup & report files from converting an old project file 167 | # to a newer Visual Studio version. Backup files are not needed, 168 | # because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | # LightSwitch generated files 187 | GeneratedArtifacts/ 188 | _Pvt_Extensions/ 189 | ModelManifest.xml 190 | *.ide-wal 191 | *.lock 192 | *.ide 193 | *.ide-shm 194 | *.dtbcache 195 | 196 | .vs/ -------------------------------------------------------------------------------- /ClrHeapAllocationAnalyzer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.28711.60 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClrHeapAllocationsAnalyzer.Test", "ClrHeapAllocationsAnalyzer.Test\ClrHeapAllocationsAnalyzer.Test.csproj", "{9F29A769-B1F0-41E0-8944-B81EDF8C96E8}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClrHeapAllocationAnalyzer.Vsix", "ClrHeapAllocationsAnalyzer.Vsix\ClrHeapAllocationAnalyzer.Vsix.csproj", "{B1412F49-1ABA-4698-B928-451FDA8DAF85}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ClrHeapAllocationAnalyzer", "ClrHeapAllocationsAnalyzer\ClrHeapAllocationAnalyzer.csproj", "{EAF46154-6613-4CD6-BE70-9015FF94F9CF}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {9F29A769-B1F0-41E0-8944-B81EDF8C96E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {9F29A769-B1F0-41E0-8944-B81EDF8C96E8}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {9F29A769-B1F0-41E0-8944-B81EDF8C96E8}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {9F29A769-B1F0-41E0-8944-B81EDF8C96E8}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {B1412F49-1ABA-4698-B928-451FDA8DAF85}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {B1412F49-1ABA-4698-B928-451FDA8DAF85}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {B1412F49-1ABA-4698-B928-451FDA8DAF85}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {B1412F49-1ABA-4698-B928-451FDA8DAF85}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {EAF46154-6613-4CD6-BE70-9015FF94F9CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {EAF46154-6613-4CD6-BE70-9015FF94F9CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {EAF46154-6613-4CD6-BE70-9015FF94F9CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {EAF46154-6613-4CD6-BE70-9015FF94F9CF}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {BB51F5E3-5139-47E0-B4D1-FE856D7EE287} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/AllocationAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.Diagnostics; 8 | using Microsoft.VisualStudio.TestTools.UnitTesting; 9 | 10 | namespace ClrHeapAllocationAnalyzer.Test 11 | { 12 | public abstract class AllocationAnalyzerTests 13 | { 14 | protected static readonly List references = new List 15 | { 16 | MetadataReference.CreateFromFile(typeof(int).Assembly.Location), 17 | MetadataReference.CreateFromFile(typeof(Console).Assembly.Location), 18 | MetadataReference.CreateFromFile(typeof(Enumerable).Assembly.Location), 19 | MetadataReference.CreateFromFile(typeof(System.CodeDom.Compiler.GeneratedCodeAttribute).Assembly.Location), 20 | MetadataReference.CreateFromFile(typeof(IList<>).Assembly.Location) 21 | }; 22 | 23 | protected IList GetExpectedDescendants(IEnumerable nodes, ImmutableArray expected) 24 | { 25 | var descendants = new List(); 26 | foreach (var node in nodes) 27 | { 28 | if (expected.Any(e => e == node.Kind())) 29 | { 30 | descendants.Add(node); 31 | continue; 32 | } 33 | 34 | foreach (var child in node.ChildNodes()) 35 | { 36 | if (expected.Any(e => e == child.Kind())) 37 | { 38 | descendants.Add(child); 39 | continue; 40 | } 41 | 42 | if (child.ChildNodes().Count() > 0) 43 | descendants.AddRange(GetExpectedDescendants(child.ChildNodes(), expected)); 44 | } 45 | } 46 | return descendants; 47 | } 48 | 49 | protected Info ProcessCode(DiagnosticAnalyzer analyzer, string sampleProgram, 50 | ImmutableArray expected, bool allowBuildErrors = false, string filePath = "") 51 | { 52 | var options = new CSharpParseOptions(kind: SourceCodeKind.Script); 53 | var tree = CSharpSyntaxTree.ParseText(sampleProgram, options, filePath); 54 | var compilation = CSharpCompilation.Create("Test", new[] { tree }, references); 55 | 56 | var diagnostics = compilation.GetDiagnostics(); 57 | if (diagnostics.Count(d => d.Severity == DiagnosticSeverity.Error) > 0) 58 | { 59 | var msg = "There were Errors in the sample code\n"; 60 | if (allowBuildErrors == false) 61 | Assert.Fail(msg + string.Join("\n", diagnostics)); 62 | else 63 | Console.WriteLine(msg + string.Join("\n", diagnostics)); 64 | } 65 | 66 | var semanticModel = compilation.GetSemanticModel(tree); 67 | var matches = GetExpectedDescendants(tree.GetRoot().ChildNodes(), expected); 68 | 69 | // Run the code tree through the analyzer and record the allocations it reports 70 | var compilationWithAnalyzers = compilation.WithAnalyzers(ImmutableArray.Create(analyzer)); 71 | var allocations = compilationWithAnalyzers.GetAnalyzerDiagnosticsAsync().GetAwaiter().GetResult().Distinct(DiagnosticEqualityComparer.Instance).ToList(); 72 | 73 | return new Info 74 | { 75 | Options = options, 76 | Tree = tree, 77 | Compilation = compilation, 78 | Diagnostics = diagnostics, 79 | SemanticModel = semanticModel, 80 | Matches = matches, 81 | Allocations = allocations, 82 | }; 83 | } 84 | 85 | protected class Info 86 | { 87 | public CSharpParseOptions Options { get; set; } 88 | public SyntaxTree Tree { get; set; } 89 | public CSharpCompilation Compilation { get; set; } 90 | public ImmutableArray Diagnostics { get; set; } 91 | public SemanticModel SemanticModel { get; set; } 92 | public IList Matches { get; set; } 93 | public List Allocations { get; set; } 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/AssertEx.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | 6 | namespace ClrHeapAllocationAnalyzer.Test 7 | { 8 | public static class AssertEx 9 | { 10 | public static void ContainsDiagnostic(List diagnostics, string id, int line, int character) 11 | { 12 | var msg = string.Format("\r\nExpected {0} at ({1},{2}), i.e. line {1}, at character position {2})\r\nDiagnostics:\r\n{3}\r\n", 13 | id, line, character, string.Join("\r\n", diagnostics)); 14 | var reportingOccurenceCount = CountReportedDiagnostics(diagnostics, id, line, character); 15 | Assert.AreEqual(1, reportingOccurenceCount, message: msg); 16 | } 17 | 18 | public static void ContainsNoDiagnostic(List diagnostics, string id, int line, int character) 19 | { 20 | var msg = string.Format("\r\nExpected no {0} at ({1},{2}), i.e. line {1}, at character position {2})\r\nDiagnostics:\r\n{3}\r\n", 21 | id, line, character, string.Join("\r\n", diagnostics)); 22 | var reportingOccurenceCount = CountReportedDiagnostics(diagnostics, id, line, character); 23 | Assert.AreEqual(0, reportingOccurenceCount, message: msg); 24 | } 25 | 26 | private static int CountReportedDiagnostics(IReadOnlyCollection diagnostics, string id, int line, int character) 27 | { 28 | return diagnostics.Where(d => d.Id == id).Count(d => 29 | { 30 | var startLinePosition = d.Location.GetLineSpan().StartLinePosition; 31 | return startLinePosition.Line + 1 == line && startLinePosition.Character + 1 == character; 32 | }); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/AvoidAllocationWithArrayEmptyCodeFixTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using ClrHeapAllocationAnalyzer; 4 | using Microsoft.CodeAnalysis; 5 | using Microsoft.CodeAnalysis.CodeFixes; 6 | using Microsoft.CodeAnalysis.Diagnostics; 7 | using Microsoft.VisualStudio.TestTools.UnitTesting; 8 | using RoslynTestKit; 9 | 10 | namespace ClrHeapAllocationsAnalyzer.Test 11 | { 12 | [TestClass] 13 | public class AvoidAllocationWithArrayEmptyCodeFixTests: CodeFixTestFixture 14 | { 15 | protected override string LanguageName => LanguageNames.CSharp; 16 | protected override CodeFixProvider CreateProvider() => new AvoidAllocationWithArrayEmptyCodeFix(); 17 | 18 | protected override IReadOnlyCollection CreateAdditionalAnalyzers() => new DiagnosticAnalyzer[] 19 | { 20 | new ExplicitAllocationAnalyzer(), 21 | }; 22 | 23 | [TestMethod] 24 | public void should_replace_empty_list_creation_with_array_empty_when_return_from_method_ienumerable() 25 | { 26 | var before = @" 27 | using System.Collections.Generic; 28 | 29 | namespace SampleNamespace 30 | { 31 | class SampleClass 32 | { 33 | public IEnumerable DoSomething() 34 | { 35 | return [|new List()|]; 36 | } 37 | } 38 | }"; 39 | var after = @" 40 | using System.Collections.Generic; 41 | 42 | namespace SampleNamespace 43 | { 44 | class SampleClass 45 | { 46 | public IEnumerable DoSomething() 47 | { 48 | return Array.Empty(); 49 | } 50 | } 51 | }"; 52 | 53 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 54 | } 55 | 56 | [TestMethod] 57 | public void should_replace_empty_list_creation_with_array_empty_when_return_from_method_ireadonly_list() 58 | { 59 | var before = @" 60 | using System.Collections.Generic; 61 | 62 | namespace SampleNamespace 63 | { 64 | class SampleClass 65 | { 66 | public IReadOnlyList DoSomething() 67 | { 68 | return [|new List()|]; 69 | } 70 | } 71 | }"; 72 | var after = @" 73 | using System.Collections.Generic; 74 | 75 | namespace SampleNamespace 76 | { 77 | class SampleClass 78 | { 79 | public IReadOnlyList DoSomething() 80 | { 81 | return Array.Empty(); 82 | } 83 | } 84 | }"; 85 | 86 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 87 | } 88 | 89 | [TestMethod] 90 | public void should_replace_empty_list_creation_with_array_empty_when_return_from_method_ireadonly_collection() 91 | { 92 | var before = @" 93 | using System.Collections.Generic; 94 | 95 | namespace SampleNamespace 96 | { 97 | class SampleClass 98 | { 99 | public IReadOnlyCollection DoSomething() 100 | { 101 | return [|new List()|]; 102 | } 103 | } 104 | }"; 105 | var after = @" 106 | using System.Collections.Generic; 107 | 108 | namespace SampleNamespace 109 | { 110 | class SampleClass 111 | { 112 | public IReadOnlyCollection DoSomething() 113 | { 114 | return Array.Empty(); 115 | } 116 | } 117 | }"; 118 | 119 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 120 | } 121 | 122 | [TestMethod] 123 | public void should_replace_empty_list_creation_with_array_empty_when_return_from_method_array() 124 | { 125 | var before = @" 126 | using System.Collections.Generic; 127 | 128 | namespace SampleNamespace 129 | { 130 | class SampleClass 131 | { 132 | public int[] DoSomething() 133 | { 134 | return [|new int[0]|]; 135 | } 136 | } 137 | }"; 138 | var after = @" 139 | using System.Collections.Generic; 140 | 141 | namespace SampleNamespace 142 | { 143 | class SampleClass 144 | { 145 | public int[] DoSomething() 146 | { 147 | return Array.Empty(); 148 | } 149 | } 150 | }"; 151 | 152 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewArrayRule.Id, 0); 153 | } 154 | 155 | [TestMethod] 156 | public void should_replace_empty_list_creation_with_array_empty_for_arrow_expression() 157 | { 158 | var before = @" 159 | using System.Collections.Generic; 160 | 161 | namespace SampleNamespace 162 | { 163 | class SampleClass 164 | { 165 | public IEnumerable DoSomething => [|new List()|]; 166 | } 167 | }"; 168 | var after = @" 169 | using System.Collections.Generic; 170 | 171 | namespace SampleNamespace 172 | { 173 | class SampleClass 174 | { 175 | public IEnumerable DoSomething => Array.Empty(); 176 | } 177 | }"; 178 | 179 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 180 | } 181 | 182 | [TestMethod] 183 | public void should_replace_empty_list_creation_with_array_empty_for_readonly_property() 184 | { 185 | var before = @" 186 | using System.Collections.Generic; 187 | 188 | namespace SampleNamespace 189 | { 190 | class SampleClass 191 | { 192 | public IEnumerable DoSomething { get {return [|new List()|];}} 193 | } 194 | }"; 195 | var after = @" 196 | using System.Collections.Generic; 197 | 198 | namespace SampleNamespace 199 | { 200 | class SampleClass 201 | { 202 | public IEnumerable DoSomething { get {return Array.Empty();}} 203 | } 204 | }"; 205 | 206 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 207 | } 208 | 209 | [TestMethod] 210 | public void should_replace_empty_list_with_creation_with_predefined_size_with_array_empty() 211 | { 212 | var before = @" 213 | using System.Collections.Generic; 214 | 215 | namespace SampleNamespace 216 | { 217 | class SampleClass 218 | { 219 | public IEnumerable DoSomething() 220 | { 221 | return [|new List(10)|]; 222 | } 223 | } 224 | }"; 225 | var after = @" 226 | using System.Collections.Generic; 227 | 228 | namespace SampleNamespace 229 | { 230 | class SampleClass 231 | { 232 | public IEnumerable DoSomething() 233 | { 234 | return Array.Empty(); 235 | } 236 | } 237 | }"; 238 | 239 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 240 | } 241 | 242 | [TestMethod] 243 | public void should_not_propose_code_fix_when_non_empty_list_created() 244 | { 245 | var before = @" 246 | using System.Collections.Generic; 247 | 248 | namespace SampleNamespace 249 | { 250 | class SampleClass 251 | { 252 | public IEnumerable DoSomething() 253 | { 254 | return [|new List(){1, 2}|]; 255 | } 256 | } 257 | }"; 258 | 259 | NoCodeFix(before, ExplicitAllocationAnalyzer.NewObjectRule.Id); 260 | } 261 | 262 | [TestMethod] 263 | public void should_not_propose_code_fix_when_return_type_inherit_form_enumerable() 264 | { 265 | var before = @" 266 | using System.Collections.Generic; 267 | 268 | namespace SampleNamespace 269 | { 270 | class SampleClass 271 | { 272 | public List DoSomething() 273 | { 274 | return [|new List()|]; 275 | } 276 | } 277 | }"; 278 | 279 | NoCodeFix(before, ExplicitAllocationAnalyzer.NewObjectRule.Id); 280 | } 281 | 282 | [TestMethod] 283 | public void should_not_propose_code_fix_when_for_collection_creation_using_copy_constructor() 284 | { 285 | 286 | var before = @" 287 | using System.Collections.Generic; 288 | using System.Collections.ObjectModel; 289 | 290 | namespace SampleNamespace 291 | { 292 | class SampleClass 293 | { 294 | public IEnumerable DoSomething() 295 | { 296 | var innerList = new List(){1, 2}; 297 | return [|new ReadOnlyCollection(innerList)|]; 298 | } 299 | } 300 | }"; 301 | 302 | NoCodeFix(before, ExplicitAllocationAnalyzer.NewObjectRule.Id); 303 | } 304 | 305 | [TestMethod] 306 | public void should_replace_empty_collection_creation_with_array_empty() 307 | { 308 | var before = @" 309 | using System.Collections.Generic; 310 | using System.Collections.ObjectModel; 311 | 312 | namespace SampleNamespace 313 | { 314 | class SampleClass 315 | { 316 | public IEnumerable DoSomething() 317 | { 318 | return [|new Collection()|]; 319 | } 320 | } 321 | }"; 322 | var after = @" 323 | using System.Collections.Generic; 324 | using System.Collections.ObjectModel; 325 | 326 | namespace SampleNamespace 327 | { 328 | class SampleClass 329 | { 330 | public IEnumerable DoSomething() 331 | { 332 | return Array.Empty(); 333 | } 334 | } 335 | }"; 336 | 337 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 338 | } 339 | 340 | [TestMethod] 341 | public void should_replace_empty_array_creation_with_array_empty() 342 | { 343 | var before = @" 344 | using System.Collections.Generic; 345 | 346 | namespace SampleNamespace 347 | { 348 | class SampleClass 349 | { 350 | public IEnumerable DoSomething() 351 | { 352 | return [|new int[0]|]; 353 | } 354 | } 355 | }"; 356 | var after = @" 357 | using System.Collections.Generic; 358 | 359 | namespace SampleNamespace 360 | { 361 | class SampleClass 362 | { 363 | public IEnumerable DoSomething() 364 | { 365 | return Array.Empty(); 366 | } 367 | } 368 | }"; 369 | 370 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewArrayRule.Id, 0); 371 | } 372 | 373 | [TestMethod] 374 | public void should_not_propose_code_fix_when_non_empty_array_creation() 375 | { 376 | var before = @" 377 | using System.Collections.Generic; 378 | 379 | namespace SampleNamespace 380 | { 381 | class SampleClass 382 | { 383 | public IEnumerable DoSomething() 384 | { 385 | return [|new int[]{1, 2}|]; 386 | } 387 | } 388 | }"; 389 | NoCodeFix(before, ExplicitAllocationAnalyzer.NewArrayRule.Id); 390 | } 391 | 392 | [TestMethod] 393 | public void should_replace_empty_array_creation_with_init_block_with_array_empty() 394 | { 395 | var before = @" 396 | using System.Collections.Generic; 397 | 398 | namespace SampleNamespace 399 | { 400 | class SampleClass 401 | { 402 | public IEnumerable DoSomething() 403 | { 404 | return [|new int[] { }|]; 405 | } 406 | } 407 | }"; 408 | var after = @" 409 | using System.Collections.Generic; 410 | 411 | namespace SampleNamespace 412 | { 413 | class SampleClass 414 | { 415 | public IEnumerable DoSomething() 416 | { 417 | return Array.Empty(); 418 | } 419 | } 420 | }"; 421 | 422 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewArrayRule.Id, 0); 423 | } 424 | 425 | [TestMethod] 426 | public void should_replace_list_creation_as_method_invocation_parameter_with_array_empty() 427 | { 428 | var before = @" 429 | using System.Collections.Generic; 430 | 431 | namespace SampleNamespace 432 | { 433 | class SampleClass 434 | { 435 | public void DoSomething() 436 | { 437 | Do([|new List()|]); 438 | } 439 | 440 | private void Do(IEnumerable a) 441 | { 442 | 443 | } 444 | } 445 | }"; 446 | var after = @" 447 | using System.Collections.Generic; 448 | 449 | namespace SampleNamespace 450 | { 451 | class SampleClass 452 | { 453 | public void DoSomething() 454 | { 455 | Do(Array.Empty()); 456 | } 457 | 458 | private void Do(IEnumerable a) 459 | { 460 | 461 | } 462 | } 463 | }"; 464 | 465 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewObjectRule.Id, 0); 466 | } 467 | 468 | [TestMethod] 469 | public void should_replace_array_creation_as_method_invocation_parameter_with_array_empty() 470 | { 471 | var before = @" 472 | using System.Collections.Generic; 473 | 474 | namespace SampleNamespace 475 | { 476 | class SampleClass 477 | { 478 | public void DoSomething() 479 | { 480 | Do([|new int[0]|]); 481 | } 482 | 483 | private void Do(IEnumerable a) 484 | { 485 | 486 | } 487 | } 488 | }"; 489 | var after = @" 490 | using System.Collections.Generic; 491 | 492 | namespace SampleNamespace 493 | { 494 | class SampleClass 495 | { 496 | public void DoSomething() 497 | { 498 | Do(Array.Empty()); 499 | } 500 | 501 | private void Do(IEnumerable a) 502 | { 503 | 504 | } 505 | } 506 | }"; 507 | 508 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewArrayRule.Id, 0); 509 | } 510 | 511 | [TestMethod] 512 | public void should_replace_array_creation_as_delegate_invocation_parameter_with_array_empty() 513 | { 514 | var before = @" 515 | using System.Collections.Generic; 516 | using System; 517 | 518 | namespace SampleNamespace 519 | { 520 | class SampleClass 521 | { 522 | public void DoSomething(Action> doSth) 523 | { 524 | doSth([|new int[0]|]); 525 | } 526 | } 527 | }"; 528 | var after = @" 529 | using System.Collections.Generic; 530 | using System; 531 | 532 | namespace SampleNamespace 533 | { 534 | class SampleClass 535 | { 536 | public void DoSomething(Action> doSth) 537 | { 538 | doSth(Array.Empty()); 539 | } 540 | } 541 | }"; 542 | 543 | TestCodeFix(before, after, ExplicitAllocationAnalyzer.NewArrayRule.Id, 0); 544 | } 545 | } 546 | } 547 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/CallSiteImplicitAllocationAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace ClrHeapAllocationAnalyzer.Test 6 | { 7 | [TestClass] 8 | public class CallSiteImplicitAllocationAnalyzerTests : AllocationAnalyzerTests 9 | { 10 | [TestMethod] 11 | public void CallSiteImplicitAllocation_Param() 12 | { 13 | var sampleProgram = 14 | @"using System; 15 | 16 | Params(); //no allocation, because compiler will implicitly substitute Array.Empty 17 | Params(1, 2); 18 | Params(new [] { 1, 2}); // explicit, so no warning 19 | ParamsWithObjects(new [] { 1, 2}); // explicit, but converted to objects, so still a warning?! 20 | 21 | // Only 4 args and above use the params overload of String.Format 22 | var test = String.Format(""Testing {0}, {1}, {2}, {3}"", 1, ""blah"", 2.0m, 'c'); 23 | 24 | public void Params(params int[] args) 25 | { 26 | } 27 | 28 | public void ParamsWithObjects(params object[] args) 29 | { 30 | }"; 31 | 32 | var analyser = new CallSiteImplicitAllocationAnalyzer(); 33 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.InvocationExpression)); 34 | 35 | Assert.AreEqual(3, info.Allocations.Count, "Should report 3 allocations"); 36 | // Diagnostic: (4,1): warning HeapAnalyzerImplicitParamsRule: This call site is calling into a function with a 'params' parameter. This results in an array allocation 37 | AssertEx.ContainsDiagnostic(info.Allocations, id: CallSiteImplicitAllocationAnalyzer.ParamsParameterRule.Id, line: 4, character: 1); 38 | // Diagnostic: (6,1): warning HeapAnalyzerImplicitParamsRule: This call site is calling into a function with a 'params' parameter. This results in an array allocation 39 | AssertEx.ContainsDiagnostic(info.Allocations, id: CallSiteImplicitAllocationAnalyzer.ParamsParameterRule.Id, line: 6, character: 1); 40 | // Diagnostic: (9,12): warning HeapAnalyzerImplicitParamsRule: This call site is calling into a function with a 'params' parameter. This results in an array allocation 41 | AssertEx.ContainsDiagnostic(info.Allocations, id: CallSiteImplicitAllocationAnalyzer.ParamsParameterRule.Id, line: 9, character: 12); 42 | } 43 | 44 | [TestMethod] 45 | public void CallSiteImplicitAllocation_NonOverridenMethodOnStruct() { 46 | var sampleProgram = 47 | @"using System; 48 | 49 | var normal = new Normal().GetHashCode(); 50 | var overridden = new OverrideToHashCode().GetHashCode(); 51 | 52 | struct Normal 53 | { 54 | 55 | } 56 | 57 | struct OverrideToHashCode 58 | { 59 | 60 | public override int GetHashCode() 61 | { 62 | return -1; 63 | } 64 | }"; 65 | 66 | var analyser = new CallSiteImplicitAllocationAnalyzer(); 67 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.InvocationExpression)); 68 | 69 | Assert.AreEqual(1, info.Allocations.Count); 70 | // Diagnostic: (3,14): warning HeapAnalyzerValueTypeNonOverridenCallRule: Non-overriden virtual method call on a value type adds a boxing or constrained instruction 71 | AssertEx.ContainsDiagnostic(info.Allocations, id: CallSiteImplicitAllocationAnalyzer.ValueTypeNonOverridenCallRule.Id, line: 3, character: 14); 72 | } 73 | 74 | [TestMethod] 75 | public void CallSiteImplicitAllocation_DoNotReportNonOverriddenMethodCallForStaticCalls() { 76 | var snippet = @"var t = System.Enum.GetUnderlyingType(typeof(System.StringComparison));"; 77 | 78 | var analyser = new CallSiteImplicitAllocationAnalyzer(); 79 | var info = ProcessCode(analyser, snippet, ImmutableArray.Create(SyntaxKind.InvocationExpression)); 80 | 81 | Assert.AreEqual(0, info.Allocations.Count); 82 | } 83 | 84 | [TestMethod] 85 | public void CallSiteImplicitAllocation_DoNotReportNonOverriddenMethodCallForNonVirtualCalls() { 86 | var snippet = @" 87 | using System.IO; 88 | 89 | FileAttributes attr = FileAttributes.System; 90 | attr.HasFlag (FileAttributes.Directory); 91 | "; 92 | 93 | var analyser = new CallSiteImplicitAllocationAnalyzer(); 94 | var info = ProcessCode(analyser, snippet, ImmutableArray.Create(SyntaxKind.InvocationExpression)); 95 | 96 | Assert.AreEqual(0, info.Allocations.Count); 97 | } 98 | 99 | [TestMethod] 100 | public void ParamsIsPrecededByOptionalParameters() 101 | { 102 | var sampleProgram = @" 103 | using System.IO; 104 | 105 | public class MyClass 106 | { 107 | static class Demo 108 | { 109 | static void Fun1() 110 | { 111 | Fun2(); 112 | Fun2(args: """", i: 5); 113 | } 114 | static void Fun2(int i = 0, params object[] args) 115 | { 116 | } 117 | } 118 | }"; 119 | 120 | var analyser = new CallSiteImplicitAllocationAnalyzer(); 121 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.InvocationExpression)); 122 | 123 | Assert.AreEqual(1, info.Allocations.Count, "Should report 1 allocation."); 124 | // Diagnostic: (11,13): warning HeapAnalyzerImplicitParamsRule: This call site is calling into a function with a 'params' parameter. This results in an array allocation 125 | AssertEx.ContainsDiagnostic(info.Allocations, id: CallSiteImplicitAllocationAnalyzer.ParamsParameterRule.Id, line: 11, character: 13); 126 | } 127 | 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/ClrHeapAllocationsAnalyzer.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net472 5 | false 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/ConcatenationAllocationAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using System.Linq; 3 | using System.Collections.Immutable; 4 | using Microsoft.CodeAnalysis.CSharp; 5 | 6 | namespace ClrHeapAllocationAnalyzer.Test 7 | { 8 | [TestClass] 9 | public class ConcatenationAllocationAnalyzerTests : AllocationAnalyzerTests { 10 | [TestMethod] 11 | public void ConcatenationAllocation_Basic() { 12 | var snippet0 = @"string s0 = ""hello"" + 0.ToString() + ""world"" + 1.ToString();"; 13 | var snippet1 = @"string s2 = ""ohell"" + 2.ToString() + ""world"" + 3.ToString() + 4.ToString();"; 14 | 15 | var analyser = new ConcatenationAllocationAnalyzer(); 16 | var info0 = ProcessCode(analyser, snippet0, ImmutableArray.Create(SyntaxKind.AddExpression, SyntaxKind.AddAssignmentExpression)); 17 | var info1 = ProcessCode(analyser, snippet1, ImmutableArray.Create(SyntaxKind.AddExpression, SyntaxKind.AddAssignmentExpression)); 18 | 19 | Assert.AreEqual(0, info0.Allocations.Count(d => d.Id == ConcatenationAllocationAnalyzer.StringConcatenationAllocationRule.Id)); 20 | Assert.AreEqual(1, info1.Allocations.Count(d => d.Id == ConcatenationAllocationAnalyzer.StringConcatenationAllocationRule.Id)); 21 | AssertEx.ContainsDiagnostic(info1.Allocations, id: ConcatenationAllocationAnalyzer.StringConcatenationAllocationRule.Id, line: 1, character: 13); 22 | } 23 | 24 | [TestMethod] 25 | public void ConcatenationAllocation_DoNotWarnForOptimizedValueTypes() { 26 | var snippets = new[] 27 | { 28 | @"string s0 = nameof(System.String) + '-';", 29 | @"string s0 = nameof(System.String) + true;", 30 | @"string s0 = nameof(System.String) + new System.IntPtr();", 31 | @"string s0 = nameof(System.String) + new System.UIntPtr();" 32 | }; 33 | 34 | var analyser = new ConcatenationAllocationAnalyzer(); 35 | foreach (var snippet in snippets) { 36 | var info = ProcessCode(analyser, snippet, ImmutableArray.Create(SyntaxKind.AddExpression, SyntaxKind.AddAssignmentExpression)); 37 | Assert.AreEqual(0, info.Allocations.Count(x => x.Id == ConcatenationAllocationAnalyzer.ValueTypeToReferenceTypeInAStringConcatenationRule.Id)); 38 | } 39 | } 40 | 41 | [TestMethod] 42 | public void ConcatenationAllocation_DoNotWarnForConst() { 43 | var snippets = new[] 44 | { 45 | @"const string s0 = nameof(System.String) + ""."" + nameof(System.String);", 46 | @"const string s0 = nameof(System.String) + ""."";", 47 | @"string s0 = nameof(System.String) + ""."" + nameof(System.String);", 48 | @"string s0 = nameof(System.String) + ""."";" 49 | }; 50 | 51 | var analyser = new ConcatenationAllocationAnalyzer(); 52 | foreach (var snippet in snippets) { 53 | var info = ProcessCode(analyser, snippet, ImmutableArray.Create(SyntaxKind.AddExpression, SyntaxKind.AddAssignmentExpression)); 54 | Assert.AreEqual(0, info.Allocations.Count); 55 | } 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/DiagnosticEqualityComparer.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using Microsoft.CodeAnalysis; 3 | 4 | namespace ClrHeapAllocationAnalyzer.Test 5 | { 6 | internal class DiagnosticEqualityComparer : IEqualityComparer 7 | { 8 | public static DiagnosticEqualityComparer Instance = new DiagnosticEqualityComparer(); 9 | 10 | public bool Equals(Diagnostic x, Diagnostic y) 11 | { 12 | return x.Equals(y); 13 | } 14 | 15 | public int GetHashCode(Diagnostic obj) 16 | { 17 | return Combine(obj?.Descriptor.GetHashCode(), 18 | Combine(obj?.GetMessage().GetHashCode(), 19 | Combine(obj?.Location.GetHashCode(), 20 | Combine(obj?.Severity.GetHashCode(), obj?.WarningLevel) 21 | ))); 22 | } 23 | 24 | internal static int Combine(int? newKeyPart, int? currentKey) 25 | { 26 | int hash = unchecked(currentKey.Value * (int)0xA5555529); 27 | 28 | if (newKeyPart.HasValue) 29 | { 30 | return unchecked(hash + newKeyPart.Value); 31 | } 32 | 33 | return hash; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/DisplayClassAllocationAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Collections.Immutable; 4 | 5 | namespace ClrHeapAllocationAnalyzer.Test 6 | { 7 | [TestClass] 8 | public class DisplayClassAllocationAnalyzerTests : AllocationAnalyzerTests 9 | { 10 | [TestMethod] 11 | public void DisplayClassAllocation_AnonymousMethodExpressionSyntax() 12 | { 13 | var sampleProgram = 14 | @"using System; 15 | 16 | class Test 17 | { 18 | static void Main() 19 | { 20 | Action action = CreateAction(5); 21 | } 22 | 23 | static Action CreateAction(T item) 24 | { 25 | T test = default(T); 26 | int counter = 0; 27 | return delegate 28 | { 29 | counter++; 30 | Console.WriteLine(""counter={0}"", counter); 31 | }; 32 | } 33 | }"; 34 | 35 | var analyser = new DisplayClassAllocationAnalyzer(); 36 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression, SyntaxKind.AnonymousMethodExpression)); 37 | 38 | Assert.AreEqual(3, info.Allocations.Count); 39 | // Diagnostic: (14,16): warning HeapAnalyzerLambdaInGenericMethodRule: Considering moving this out of the generic method 40 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.LambaOrAnonymousMethodInGenericMethodRule.Id, line: 14, character: 16); 41 | // Diagnostic: (13,13): warning HeapAnalyzerClosureCaptureRule: The compiler will emit a class that will hold this as a field to allow capturing of this closure 42 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureCaptureRule.Id, line: 13, character: 13); 43 | // Diagnostic: (14,16): warning HeapAnalyzerClosureSourceRule: Heap allocation of closure Captures: counter 44 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureDriverRule.Id, line: 14, character: 16); 45 | } 46 | 47 | [TestMethod] 48 | public void DisplayClassAllocation_SimpleLambdaExpressionSyntax() 49 | { 50 | var sampleProgram = 51 | @"using System.Collections.Generic; 52 | using System; 53 | using System.Linq; 54 | 55 | public class Testing 56 | { 57 | public Testing() 58 | { 59 | int[] intData = new[] { 123, 32, 4 }; 60 | int min = 31; 61 | var results = intData.Where(i => i > min).ToList(); 62 | } 63 | }"; 64 | 65 | var analyser = new DisplayClassAllocationAnalyzer(); 66 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.SimpleLambdaExpression)); 67 | 68 | Assert.AreEqual(2, info.Allocations.Count); 69 | // Diagnostic: (10,13): warning HeapAnalyzerClosureCaptureRule: The compiler will emit a class that will hold this as a field to allow capturing of this closure 70 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureCaptureRule.Id, line: 10, character: 13); 71 | // Diagnostic: (11,39): warning HeapAnalyzerClosureSourceRule: Heap allocation of closure Captures: min 72 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureDriverRule.Id, line: 11, character: 39); 73 | } 74 | 75 | [TestMethod] 76 | public void DisplayClassAllocation_ParenthesizedLambdaExpressionSyntax() 77 | { 78 | var sampleProgram = 79 | @"using System.Collections.Generic; 80 | using System; 81 | using System.Linq; 82 | 83 | var words = new[] { ""foo"", ""bar"", ""baz"", ""beer"" }; 84 | var actions = new List(); 85 | foreach (string word in words) // <-- captured closure 86 | { 87 | actions.Add(() => Console.WriteLine(word)); // <-- reason for closure capture 88 | }"; 89 | 90 | var analyser = new DisplayClassAllocationAnalyzer(); 91 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ParenthesizedLambdaExpression)); 92 | 93 | Assert.AreEqual(2, info.Allocations.Count); 94 | // Diagnostic: (7,17): warning HeapAnalyzerClosureCaptureRule: The compiler will emit a class that will hold this as a field to allow capturing of this closure 95 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureCaptureRule.Id, line: 7, character: 17); 96 | // Diagnostic: (9,20): warning HeapAnalyzerClosureSourceRule: Heap allocation of closure Captures: word 97 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureDriverRule.Id, line: 9, character: 20); 98 | } 99 | 100 | [TestMethod] 101 | public void DisplayClassAllocation_DoNotReportForNonCapturingAnonymousMethod() 102 | { 103 | var snippet = @" 104 | public static void Sorter(int[] arr) { 105 | System.Array.Sort(arr, delegate(int x, int y) { return x - y; }); 106 | }"; 107 | 108 | var analyser = new DisplayClassAllocationAnalyzer(); 109 | var info = ProcessCode(analyser, snippet, ImmutableArray.Create()); 110 | Assert.AreEqual(0, info.Allocations.Count); 111 | } 112 | 113 | [TestMethod] 114 | public void DisplayClassAllocation_DoNotReportForNonCapturingLambda() 115 | { 116 | var snippet = @" 117 | public void Sorter(int[] arr) { 118 | System.Array.Sort(arr, (x, y) => x - y); 119 | }"; 120 | 121 | var analyser = new DisplayClassAllocationAnalyzer(); 122 | var info = ProcessCode(analyser, snippet, ImmutableArray.Create()); 123 | Assert.AreEqual(0, info.Allocations.Count); 124 | } 125 | 126 | [TestMethod] 127 | public void DisplayClassAllocation_ReportForCapturingAnonymousMethod() 128 | { 129 | var snippet = @" 130 | public void Sorter(int[] arr) { 131 | int z = 2; 132 | System.Array.Sort(arr, delegate(int x, int y) { return x - z; }); 133 | }"; 134 | 135 | var analyser = new DisplayClassAllocationAnalyzer(); 136 | var info = ProcessCode(analyser, snippet, ImmutableArray.Create()); 137 | Assert.AreEqual(2, info.Allocations.Count); 138 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureCaptureRule.Id, line: 3, character: 25); 139 | AssertEx.ContainsDiagnostic(info.Allocations, id: DisplayClassAllocationAnalyzer.ClosureDriverRule.Id, line: 4, character: 44); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/EnumeratorAllocationAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Collections.Immutable; 4 | 5 | namespace ClrHeapAllocationAnalyzer.Test 6 | { 7 | [TestClass] 8 | public class EnumeratorAllocationAnalyzerTests : AllocationAnalyzerTests 9 | { 10 | [TestMethod] 11 | public void EnumeratorAllocation_Basic() 12 | { 13 | var sampleProgram = 14 | @"using System.Collections.Generic; 15 | using System; 16 | using System.Linq; 17 | 18 | int[] intData = new[] { 123, 32, 4 }; 19 | IList iListData = new[] { 123, 32, 4 }; 20 | List listData = new[] { 123, 32, 4 }.ToList(); 21 | 22 | foreach (var i in intData) 23 | { 24 | Console.WriteLine(i); 25 | } 26 | 27 | foreach (var i in listData) 28 | { 29 | Console.WriteLine(i); 30 | } 31 | 32 | foreach (var i in iListData) // Allocations (line 19) 33 | { 34 | Console.WriteLine(i); 35 | } 36 | 37 | foreach (var i in (IEnumerable)intData) // Allocations (line 24) 38 | { 39 | Console.WriteLine(i); 40 | }"; 41 | 42 | var analyser = new EnumeratorAllocationAnalyzer(); 43 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ForEachStatement)); 44 | 45 | Assert.AreEqual(2, info.Allocations.Count); 46 | // Diagnostic: (19,16): warning HeapAnalyzerEnumeratorAllocationRule: Non-ValueType enumerator may result in a heap allocation 47 | AssertEx.ContainsDiagnostic(info.Allocations, id: EnumeratorAllocationAnalyzer.ReferenceTypeEnumeratorRule.Id, line: 19, character: 16); 48 | // Diagnostic: (24,16): warning HeapAnalyzerEnumeratorAllocationRule: Non-ValueType enumerator may result in a heap allocation 49 | AssertEx.ContainsDiagnostic(info.Allocations, id: EnumeratorAllocationAnalyzer.ReferenceTypeEnumeratorRule.Id, line: 24, character: 16); 50 | } 51 | 52 | [TestMethod] 53 | public void EnumeratorAllocation_Advanced() 54 | { 55 | var sampleProgram = 56 | @"using System.Collections.Generic; 57 | using System; 58 | 59 | // These next 3 are from the YouTube video 60 | foreach (object a in new[] { 1, 2, 3}) // Allocations 'new [] { 1. 2, 3}' 61 | { 62 | Console.WriteLine(a.ToString()); 63 | } 64 | 65 | IEnumerable fx1 = default(IEnumerable); 66 | foreach (var f in fx1) // Allocations 'in' 67 | { 68 | } 69 | 70 | List fx2 = default(List); 71 | foreach (var f in fx2) // NO Allocations 72 | { 73 | }"; 74 | 75 | var analyser = new EnumeratorAllocationAnalyzer(); 76 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ForEachStatement, SyntaxKind.InvocationExpression)); 77 | 78 | Assert.AreEqual(1, info.Allocations.Count); 79 | // Diagnostic: (11,16): warning HeapAnalyzerEnumeratorAllocationRule: Non-ValueType enumerator may result in a heap allocation 80 | AssertEx.ContainsDiagnostic(info.Allocations, id: EnumeratorAllocationAnalyzer.ReferenceTypeEnumeratorRule.Id, line: 11, character: 16); 81 | } 82 | 83 | [TestMethod] 84 | public void EnumeratorAllocation_Via_InvocationExpressionSyntax() 85 | { 86 | var sampleProgram = 87 | @"using System.Collections.Generic; 88 | using System.Collections; 89 | using System; 90 | 91 | var enumeratorRaw = GetIEnumerableRaw(); 92 | while (enumeratorRaw.MoveNext()) 93 | { 94 | Console.WriteLine(enumeratorRaw.Current.ToString()); 95 | } 96 | 97 | var enumeratorRawViaIEnumerable = GetIEnumeratorViaIEnumerable(); 98 | while (enumeratorRawViaIEnumerable.MoveNext()) 99 | { 100 | Console.WriteLine(enumeratorRawViaIEnumerable.Current.ToString()); 101 | } 102 | 103 | private IEnumerator GetIEnumerableRaw() 104 | { 105 | return new[] { 123, 32, 4 }.GetEnumerator(); 106 | } 107 | 108 | private IEnumerator GetIEnumeratorViaIEnumerable() 109 | { 110 | int[] intData = new[] { 123, 32, 4 }; 111 | return (IEnumerator)intData.GetEnumerator(); 112 | }"; 113 | 114 | var analyser = new EnumeratorAllocationAnalyzer(); 115 | var expectedNodes = ImmutableArray.Create(SyntaxKind.InvocationExpression); 116 | var info = ProcessCode(analyser, sampleProgram, expectedNodes); 117 | 118 | Assert.AreEqual(1, info.Allocations.Count); 119 | // Diagnostic: (11,35): warning HeapAnalyzerEnumeratorAllocationRule: Non-ValueType enumerator may result in a heap allocation *** 120 | AssertEx.ContainsDiagnostic(info.Allocations, id: EnumeratorAllocationAnalyzer.ReferenceTypeEnumeratorRule.Id, line: 11, character: 35); 121 | } 122 | 123 | [TestMethod] 124 | public void EnumeratorAllocation_IterateOverString_NoWarning() 125 | { 126 | var sampleProgram = "foreach (char c in \"foo\") { }"; 127 | 128 | var analyser = new EnumeratorAllocationAnalyzer(); 129 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ForEachStatement)); 130 | 131 | Assert.AreEqual(0, info.Allocations.Count); 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/ExplicitAllocationAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using System.Collections.Immutable; 4 | 5 | namespace ClrHeapAllocationAnalyzer.Test 6 | { 7 | [TestClass] 8 | public class ExplicitAllocationAnalyzerTests : AllocationAnalyzerTests 9 | { 10 | [TestMethod] 11 | public void ExplicitAllocation_InitializerExpressionSyntax() 12 | { 13 | var sampleProgram = 14 | @"using System; 15 | 16 | var @struct = new TestStruct { Name = ""Bob"" }; 17 | var @class = new TestClass { Name = ""Bob"" }; 18 | 19 | public struct TestStruct 20 | { 21 | public string Name { get; set; } 22 | } 23 | 24 | public class TestClass 25 | { 26 | public string Name { get; set; } 27 | }"; 28 | 29 | var analyser = new ExplicitAllocationAnalyzer(); 30 | // SyntaxKind.ObjectInitializerExpression IS linked to InitializerExpressionSyntax (naming is a bit confusing) 31 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ObjectInitializerExpression)); 32 | 33 | Assert.AreEqual(2, info.Allocations.Count); 34 | // Diagnostic: (4,14): info HeapAnalyzerExplicitNewObjectRule: Explicit new reference type allocation 35 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.NewObjectRule.Id, line: 4, character: 14); 36 | 37 | // Diagnostic: (4,5): info HeapAnalyzerInitializerCreationRule: Initializer reference type allocation *** 38 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.InitializerCreationRule.Id, line: 4, character: 5); 39 | } 40 | 41 | [TestMethod] 42 | public void ExplicitAllocation_ImplicitArrayCreationExpressionSyntax() 43 | { 44 | var sampleProgram = 45 | @"using System.Collections.Generic; 46 | 47 | int[] intData = new[] { 123, 32, 4 };"; 48 | 49 | var analyser = new ExplicitAllocationAnalyzer(); 50 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ImplicitArrayCreationExpression)); 51 | 52 | Assert.AreEqual(1, info.Allocations.Count); 53 | // Diagnostic: (3,17): info HeapAnalyzerImplicitNewArrayCreationRule: Implicit new array creation allocation 54 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.ImplicitArrayCreationRule.Id, line: 3, character: 17); 55 | } 56 | 57 | [TestMethod] 58 | public void ExplicitAllocation_AnonymousObjectCreationExpressionSyntax() 59 | { 60 | var sampleProgram = 61 | @"using System; 62 | 63 | var temp = new { A = 123, Name = ""Test"", };"; 64 | 65 | var analyser = new ExplicitAllocationAnalyzer(); 66 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.AnonymousObjectCreationExpression)); 67 | 68 | Assert.AreEqual(1, info.Allocations.Count); 69 | // Diagnostic: (3,12): info HeapAnalyzerExplicitNewAnonymousObjectRule: Explicit new anonymous object allocation 70 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.AnonymousNewObjectRule.Id, line: 3, character: 12); 71 | } 72 | 73 | [TestMethod] 74 | public void ExplicitAllocation_ArrayCreationExpressionSyntax() 75 | { 76 | var sampleProgram = 77 | @"using System.Collections.Generic; 78 | 79 | int[] intData = new int[] { 123, 32, 4 };"; 80 | 81 | var analyser = new ExplicitAllocationAnalyzer(); 82 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ArrayCreationExpression)); 83 | 84 | Assert.AreEqual(1, info.Allocations.Count); 85 | // Diagnostic: (3,17): info HeapAnalyzerExplicitNewArrayRule: Implicit new array creation allocation 86 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.NewArrayRule.Id, line: 3, character: 17); 87 | } 88 | 89 | [TestMethod] 90 | public void ExplicitAllocation_ObjectCreationExpressionSyntax() 91 | { 92 | var sampleProgram = 93 | @"using System; 94 | 95 | var allocation = new String('a', 10); 96 | var noAllocation = new DateTime();"; 97 | 98 | var analyser = new ExplicitAllocationAnalyzer(); 99 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ObjectCreationExpression)); 100 | 101 | Assert.AreEqual(1, info.Allocations.Count); 102 | // Diagnostic: (3,18): info HeapAnalyzerExplicitNewObjectRule: Explicit new reference type allocation 103 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.NewObjectRule.Id, line: 3, character: 18); 104 | } 105 | 106 | [TestMethod] 107 | public void ExplicitAllocation_LetClauseSyntax() 108 | { 109 | var sampleProgram = 110 | @"using System.Collections.Generic; 111 | using System.Linq; 112 | 113 | int[] intData = new[] { 123, 32, 4 }; 114 | var result = (from a in intData 115 | let b = a * 3 116 | select b).ToList(); 117 | "; 118 | 119 | var analyser = new ExplicitAllocationAnalyzer(); 120 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.LetClause)); 121 | 122 | Assert.AreEqual(2, info.Allocations.Count); 123 | // Diagnostic: (4,17): info HeapAnalyzerImplicitNewArrayCreationRule: Implicit new array creation allocation 124 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.ImplicitArrayCreationRule.Id, line: 4, character: 17); 125 | 126 | // Diagnostic: (6,15): info HeapAnalyzerLetClauseRule: Let clause induced allocation 127 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.LetCauseRule.Id, line: 6, character: 15); 128 | } 129 | 130 | [TestMethod] 131 | public void ExplicitAllocation_AllSyntax() 132 | { 133 | var sampleProgram = 134 | @"using System; 135 | using System.Collections.Generic; 136 | using System.Linq; 137 | 138 | var @struct = new TestStruct { Name = ""Bob"" }; 139 | var @class = new TestClass { Name = ""Bob"" }; 140 | 141 | int[] intDataImplicit = new[] { 123, 32, 4 }; 142 | 143 | var temp = new { A = 123, Name = ""Test"", }; 144 | 145 | int[] intDataExplicit = new int[] { 123, 32, 4 }; 146 | 147 | var allocation = new String('a', 10); 148 | var noAllocation = new DateTime(); 149 | 150 | int[] intDataLinq = new int[] { 123, 32, 4 }; 151 | var result = (from a in intDataLinq 152 | let b = a * 3 153 | select b).ToList(); 154 | 155 | public struct TestStruct 156 | { 157 | public string Name { get; set; } 158 | } 159 | 160 | public class TestClass 161 | { 162 | public string Name { get; set; } 163 | }"; 164 | 165 | // This test is here so that we use SyntaxKindsOfInterest explicitly, to make sure it works 166 | var analyser = new ExplicitAllocationAnalyzer(); 167 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ObjectCreationExpression, SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.ArrayInitializerExpression, SyntaxKind.CollectionInitializerExpression,SyntaxKind.ComplexElementInitializerExpression, SyntaxKind.ObjectInitializerExpression, SyntaxKind.ArrayCreationExpression, SyntaxKind.ImplicitArrayCreationExpression, SyntaxKind.LetClause)); 168 | 169 | Assert.AreEqual(8, info.Allocations.Count); 170 | // Diagnostic: (6,14): info HeapAnalyzerExplicitNewObjectRule: Explicit new reference type allocation 171 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.NewObjectRule.Id, line: 6, character: 14); 172 | // Diagnostic: (6,5): info HeapAnalyzerInitializerCreationRule: Initializer reference type allocation 173 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.InitializerCreationRule.Id, line: 6, character: 5); 174 | // Diagnostic: (8,25): info HeapAnalyzerImplicitNewArrayCreationRule: Implicit new array creation allocation 175 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.ImplicitArrayCreationRule.Id, line: 8, character: 25); 176 | // Diagnostic: (10,12): info HeapAnalyzerExplicitNewAnonymousObjectRule: Explicit new anonymous object allocation 177 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.AnonymousNewObjectRule.Id, line: 10, character: 12); 178 | // Diagnostic: (12,25): info HeapAnalyzerExplicitNewArrayRule: Explicit new array type allocation 179 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.NewArrayRule.Id, line: 12, character: 25); 180 | // Diagnostic: (14,18): info HeapAnalyzerExplicitNewObjectRule: Explicit new reference type allocation 181 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.NewObjectRule.Id, line: 14, character: 18); 182 | // Diagnostic: (17,21): info HeapAnalyzerExplicitNewArrayRule: Explicit new array type allocation 183 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.NewArrayRule.Id, line: 17, character: 21); 184 | // Diagnostic: (19,15): info HeapAnalyzerLetClauseRule: Let clause induced allocation 185 | AssertEx.ContainsDiagnostic(info.Allocations, id: ExplicitAllocationAnalyzer.LetCauseRule.Id, line: 19, character: 15); 186 | } 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/IgnoreTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace ClrHeapAllocationAnalyzer.Test 6 | { 7 | [TestClass] 8 | public class IgnoreTests : AllocationAnalyzerTests 9 | { 10 | [TestMethod] 11 | public void AnalyzeProgram_TakesIgnoredAttributesIntoAccount() 12 | { 13 | const string sampleProgram = 14 | @"using System; 15 | 16 | [System.Runtime.CompilerServices.CompilerGeneratedAttribute] 17 | public void CreateString1() { 18 | string str = new string('a', 5); 19 | } 20 | 21 | [System.CodeDom.Compiler.GeneratedCodeAttribute(""MyCompiler"", ""1.0.0.3"")] 22 | public void CreateString2() { 23 | string str = new string('a', 5); 24 | } 25 | 26 | [System.ObsoleteAttribute] 27 | public void CreateString3() { 28 | string str = new string('a', 5); 29 | }"; 30 | 31 | var analyser = new ExplicitAllocationAnalyzer(); 32 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ObjectInitializerExpression)); 33 | Assert.AreEqual(1, info.Allocations.Count); 34 | } 35 | 36 | [TestMethod] 37 | public void AnalyzeProgram_TakesIgnoredFilesIntoAccount() 38 | { 39 | const string sampleProgram = 40 | @"using System; 41 | public void CreateString() { 42 | string str = new string('a', 5); 43 | }"; 44 | 45 | var analyser = new ExplicitAllocationAnalyzer(); 46 | void Check(int expectedCount, string path) 47 | { 48 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ObjectInitializerExpression), filePath: path); 49 | Assert.AreEqual(expectedCount, info.Allocations.Count); 50 | } 51 | 52 | Check(0, "test.g.cs"); 53 | Check(0, "test.G.cS"); 54 | Check(1, "test.cs"); 55 | Check(1, "test.cpp"); 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("ClrHeapAllocationsAnalyzer.Test")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("CA, Inc.")] 12 | [assembly: AssemblyProduct("ClrHeapAllocationsAnalyzer.Test")] 13 | [assembly: AssemblyCopyright("Copyright © CA, Inc. 2014")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("e61b2b97-821b-45de-9c3b-948d1abb30f3")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/StackOverflowAnswerTests.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Immutable; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | 5 | namespace ClrHeapAllocationAnalyzer.Test 6 | { 7 | /// 8 | /// Taken from http://stackoverflow.com/questions/7995606/boxing-occurrence-in-c-sharp 9 | /// 10 | [TestClass] 11 | public class StackOverflowAnswerTests : AllocationAnalyzerTests 12 | { 13 | [TestMethod] 14 | public void Converting_any_value_type_to_System_Object_type() 15 | { 16 | var @script = 17 | @"struct S { } 18 | object box = new S();"; 19 | var analyser = new ExplicitAllocationAnalyzer(); 20 | var info = ProcessCode(analyser, @script, ImmutableArray.Create(SyntaxKind.ObjectCreationExpression, SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.ArrayInitializerExpression, SyntaxKind.CollectionInitializerExpression, SyntaxKind.ComplexElementInitializerExpression, SyntaxKind.ObjectInitializerExpression, SyntaxKind.ArrayCreationExpression, SyntaxKind.ImplicitArrayCreationExpression, SyntaxKind.LetClause)); 21 | Assert.AreEqual(1, info.Allocations.Count); 22 | // Diagnostic: (2,34): info HeapAnalyzerExplicitNewObjectRule: Explicit new reference type allocation 23 | AssertEx.ContainsDiagnostic(info.Allocations, ExplicitAllocationAnalyzer.NewObjectRule.Id, line: 2, character: 34); 24 | } 25 | 26 | [TestMethod] 27 | public void Converting_any_value_type_to_System_ValueType_type() 28 | { 29 | var @script = 30 | @"struct S { } 31 | System.ValueType box = new S();"; 32 | var analyser = new ExplicitAllocationAnalyzer(); 33 | var info = ProcessCode(analyser, @script, ImmutableArray.Create(SyntaxKind.ObjectCreationExpression, SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.ArrayInitializerExpression, SyntaxKind.CollectionInitializerExpression, SyntaxKind.ComplexElementInitializerExpression, SyntaxKind.ObjectInitializerExpression, SyntaxKind.ArrayCreationExpression, SyntaxKind.ImplicitArrayCreationExpression, SyntaxKind.LetClause)); 34 | Assert.AreEqual(1, info.Allocations.Count); 35 | // Diagnostic: (2,44): info HeapAnalyzerExplicitNewObjectRule: Explicit new reference type allocation 36 | AssertEx.ContainsDiagnostic(info.Allocations, ExplicitAllocationAnalyzer.NewObjectRule.Id, line: 2, character: 44); 37 | } 38 | 39 | [TestMethod] 40 | public void Converting_any_enumeration_type_to_System_Enum_type() 41 | { 42 | var @script = 43 | @"enum E { A } 44 | System.Enum box = E.A;"; 45 | var analyser = new TypeConversionAllocationAnalyzer(); 46 | var info = ProcessCode(analyser, @script, ImmutableArray.Create( 47 | SyntaxKind.SimpleAssignmentExpression, 48 | SyntaxKind.ReturnStatement, 49 | SyntaxKind.YieldReturnStatement, 50 | SyntaxKind.CastExpression, 51 | SyntaxKind.AsExpression, 52 | SyntaxKind.CoalesceExpression, 53 | SyntaxKind.ConditionalExpression, 54 | SyntaxKind.ForEachStatement, 55 | SyntaxKind.EqualsValueClause, 56 | SyntaxKind.Argument)); 57 | Assert.AreEqual(1, info.Allocations.Count); 58 | // Diagnostic: (2,35): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 59 | AssertEx.ContainsDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 2, character: 35); 60 | } 61 | 62 | [TestMethod] 63 | public void Converting_any_value_type_into_interface_reference() 64 | { 65 | var @script = 66 | @"interface I { } 67 | struct S : I { } 68 | I box = new S();"; 69 | var analyser = new ExplicitAllocationAnalyzer(); 70 | var info = ProcessCode(analyser, @script, ImmutableArray.Create(SyntaxKind.ObjectCreationExpression, SyntaxKind.AnonymousObjectCreationExpression, SyntaxKind.ArrayInitializerExpression, SyntaxKind.CollectionInitializerExpression, SyntaxKind.ComplexElementInitializerExpression, SyntaxKind.ObjectInitializerExpression, SyntaxKind.ArrayCreationExpression, SyntaxKind.ImplicitArrayCreationExpression, SyntaxKind.LetClause)); 71 | Assert.AreEqual(1, info.Allocations.Count); 72 | // Diagnostic: (3,25): info HeapAnalyzerExplicitNewObjectRule: Explicit new reference type allocation 73 | AssertEx.ContainsDiagnostic(info.Allocations, ExplicitAllocationAnalyzer.NewObjectRule.Id, line: 3, character: 25); 74 | } 75 | 76 | [TestMethod] 77 | public void Non_constant_value_types_in_CSharp_string_concatenation() 78 | { 79 | var @script = 80 | @"System.DateTime c = System.DateTime.Now;; 81 | string s1 = ""char value will box"" + c;"; 82 | var analyser = new ConcatenationAllocationAnalyzer(); 83 | var info = ProcessCode(analyser, @script, ImmutableArray.Create(SyntaxKind.AddExpression, SyntaxKind.AddAssignmentExpression)); 84 | Assert.AreEqual(1, info.Allocations.Count); 85 | //Diagnostic: (2,53): warning HeapAnalyzerBoxingRule: Value type (char) is being boxed to a reference type for a string concatenation. 86 | AssertEx.ContainsDiagnostic(info.Allocations, ConcatenationAllocationAnalyzer.ValueTypeToReferenceTypeInAStringConcatenationRule.Id, line: 2, character: 53); 87 | } 88 | 89 | [TestMethod] 90 | public void Creating_delegate_from_value_type_instance_method() 91 | { 92 | var @script = 93 | @"using System; 94 | struct S { public void M() {} } 95 | Action box = new S().M;"; 96 | var analyser = new TypeConversionAllocationAnalyzer(); 97 | var info = ProcessCode(analyser, @script, ImmutableArray.Create( 98 | SyntaxKind.SimpleAssignmentExpression, 99 | SyntaxKind.ReturnStatement, 100 | SyntaxKind.YieldReturnStatement, 101 | SyntaxKind.CastExpression, 102 | SyntaxKind.AsExpression, 103 | SyntaxKind.CoalesceExpression, 104 | SyntaxKind.ConditionalExpression, 105 | SyntaxKind.ForEachStatement, 106 | SyntaxKind.EqualsValueClause, 107 | SyntaxKind.Argument)); 108 | Assert.AreEqual(2, info.Allocations.Count); 109 | // Diagnostic: (3,30): warning HeapAnalyzerMethodGroupAllocationRule: This will allocate a delegate instance 110 | AssertEx.ContainsDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 3, character: 30); 111 | // Diagnostic: (3,30): warning HeapAnalyzerDelegateOnStructRule: Struct instance method being used for delegate creation, this will result in a boxing instruction 112 | AssertEx.ContainsDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, line: 3, character: 30); 113 | } 114 | 115 | [TestMethod] 116 | public void Calling_non_overridden_virtual_methods_on_value_types() 117 | { 118 | var @script = 119 | @"enum E { A } 120 | E.A.GetHashCode();"; 121 | var analyser = new CallSiteImplicitAllocationAnalyzer(); 122 | var info = ProcessCode(analyser, @script, ImmutableArray.Create(SyntaxKind.InvocationExpression)); 123 | Assert.AreEqual(1, info.Allocations.Count); 124 | // Diagnostic: (2,17): warning HeapAnalyzerValueTypeNonOverridenCallRule: Non-overriden virtual method call on a value type adds a boxing or constrained instruction 125 | AssertEx.ContainsDiagnostic(info.Allocations, CallSiteImplicitAllocationAnalyzer.ValueTypeNonOverridenCallRule.Id, line: 2, character: 17); 126 | } 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/TypeConversionAllocationAnalyzerTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.CodeAnalysis.CSharp; 3 | using Microsoft.VisualStudio.TestTools.UnitTesting; 4 | using System.Collections.Immutable; 5 | using System.Linq; 6 | 7 | namespace ClrHeapAllocationAnalyzer.Test 8 | { 9 | [TestClass] 10 | public class TypeConversionAllocationAnalyzerTests : AllocationAnalyzerTests 11 | { 12 | [TestMethod] 13 | public void TypeConversionAllocation_ArgumentSyntax() 14 | { 15 | var sampleProgram = 16 | @"using System; 17 | 18 | var result = fooObjCall(10); // Allocation 19 | var temp = new MyObject(10); // Allocation 20 | 21 | private string fooObjCall(object obj) 22 | { 23 | return obj.ToString(); 24 | } 25 | 26 | public class MyObject 27 | { 28 | private Object Obj; 29 | 30 | public MyObject(object obj) 31 | { 32 | this.Obj = obj; 33 | } 34 | }"; 35 | 36 | var analyser = new TypeConversionAllocationAnalyzer(); 37 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.Argument)); 38 | 39 | Assert.AreEqual(2, info.Allocations.Count); 40 | // Diagnostic: (3,25): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable *** 41 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 3, character: 25); 42 | // Diagnostic: (4,25): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable *** 43 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 4, character: 25); 44 | } 45 | 46 | [TestMethod] 47 | public void TypeConversionAllocation_ArgumentSyntax_WithDelegates() 48 | { 49 | var sampleProgram = 50 | @"using System; 51 | 52 | public class MyClass 53 | { 54 | public void Testing() 55 | { 56 | var @class = new MyClass(); 57 | @class.ProcessFunc(fooObjCall); // implicit, so Allocation 58 | @class.ProcessFunc(new Func(fooObjCall)); // Explicit, so NO Allocation 59 | } 60 | 61 | public void ProcessFunc(Func func) 62 | { 63 | } 64 | 65 | private string fooObjCall(object obj) 66 | { 67 | return obj.ToString(); 68 | } 69 | } 70 | 71 | public struct MyStruct 72 | { 73 | public void Testing() 74 | { 75 | var @struct = new MyStruct(); 76 | @struct.ProcessFunc(fooObjCall); // implicit allocation + boxing 77 | @struct.ProcessFunc(new Func(fooObjCall)); // Explicit allocation + boxing 78 | } 79 | 80 | public void ProcessFunc(Func func) 81 | { 82 | } 83 | 84 | private string fooObjCall(object obj) 85 | { 86 | return obj.ToString(); 87 | } 88 | }"; 89 | 90 | var analyser = new TypeConversionAllocationAnalyzer(); 91 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.Argument)); 92 | 93 | Assert.AreEqual(4, info.Allocations.Count); 94 | // Diagnostic: (8,28): warning HeapAnalyzerMethodGroupAllocationRule: This will allocate a delegate instance 95 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 8, character: 28); 96 | // Diagnostic: (27,29): warning HeapAnalyzerMethodGroupAllocationRule: This will allocate a delegate instance 97 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 27, character: 29); 98 | // Diagnostic: (27,29): warning HeapAnalyzerDelegateOnStructRule: Struct instance method being used for delegate creation, this will result in a boxing instruction 99 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, line: 27, character: 29); 100 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, line: 28, character: 54); 101 | } 102 | 103 | [TestMethod] 104 | public void TypeConversionAllocation_ReturnStatementSyntax() 105 | { 106 | var sampleProgram = 107 | @"using System; 108 | 109 | var result1 = new MyObject().Obj; // Allocation 110 | var result2 = new MyObject().ObjNoAllocation; // Allocation 111 | 112 | public class MyObject 113 | { 114 | public Object Obj { get { return 0; } } 115 | 116 | public Object ObjNoAllocation { get { return 0.ToString(); } } 117 | }"; 118 | 119 | var analyser = new TypeConversionAllocationAnalyzer(); 120 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ReturnStatement)); 121 | 122 | Assert.AreEqual(1, info.Allocations.Count); 123 | 124 | // Diagnostic: (7,38): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 125 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 8, character: 38); 126 | } 127 | 128 | [TestMethod] 129 | public void TypeConversionAllocation_YieldStatementSyntax() 130 | { 131 | var sampleProgram = 132 | @"using System; 133 | using System.Collections.Generic; 134 | 135 | foreach (var item in GetItems()) 136 | { 137 | } 138 | 139 | foreach (var item in GetItemsNoAllocation()) 140 | { 141 | } 142 | 143 | public IEnumerable GetItems() 144 | { 145 | yield return 0; // Allocation 146 | yield break; 147 | } 148 | 149 | public IEnumerable GetItemsNoAllocation() 150 | { 151 | yield return 0; // NO Allocation (IEnumerable) 152 | yield break; 153 | }"; 154 | 155 | var analyser = new TypeConversionAllocationAnalyzer(); 156 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.YieldReturnStatement)); 157 | 158 | Assert.AreEqual(1, info.Allocations.Count); 159 | 160 | // Diagnostic: (14,18): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 161 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 14, character: 18); 162 | // TODO this is a false positive 163 | // Diagnostic: (8,22): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 164 | } 165 | 166 | [TestMethod] 167 | public void TypeConversionAllocation_BinaryExpressionSyntax() 168 | { 169 | var sampleProgram = 170 | @"using System; 171 | 172 | object x = ""blah""; 173 | object a1 = x ?? 0; // Allocation 174 | object a2 = x ?? 0.ToString(); // No Allocation 175 | 176 | var b1 = 10 as object; // Allocation 177 | var b2 = 10.ToString() as object; // No Allocation 178 | "; 179 | 180 | var analyser = new TypeConversionAllocationAnalyzer(); 181 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.CoalesceExpression, SyntaxKind.AsExpression)); 182 | 183 | Assert.AreEqual(2, info.Allocations.Count); 184 | 185 | // Diagnostic: (4,17): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 186 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 4, character: 18); 187 | // Diagnostic: (7,9): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 188 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 7, character: 10); 189 | } 190 | 191 | [TestMethod] 192 | public void TypeConversionAllocation_BinaryExpressionSyntax_WithDelegates() 193 | { 194 | var sampleProgram = 195 | @"using System; 196 | 197 | public class MyClass 198 | { 199 | public void Testing() 200 | { 201 | Func temp = null; 202 | var result1 = temp ?? fooObjCall; // implicit, so Allocation 203 | var result2 = temp ?? new Func(fooObjCall); // Explicit, so NO Allocation 204 | } 205 | 206 | private string fooObjCall(object obj) 207 | { 208 | return obj.ToString(); 209 | } 210 | } 211 | 212 | public struct MyStruct 213 | { 214 | public void Testing() 215 | { 216 | Func temp = null; 217 | var result1 = temp ?? fooObjCall; // implicit allocation + boxing 218 | var result2 = temp ?? new Func(fooObjCall); // Explicit allocation + boxing 219 | } 220 | 221 | private string fooObjCall(object obj) 222 | { 223 | return obj.ToString(); 224 | } 225 | }"; 226 | 227 | var analyser = new TypeConversionAllocationAnalyzer(); 228 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.CoalesceExpression, SyntaxKind.AsExpression)); 229 | 230 | Assert.AreEqual(4, info.Allocations.Count); 231 | 232 | // Diagnostic: (8,31): warning HeapAnalyzerMethodGroupAllocationRule: This will allocate a delegate instance 233 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 8, character: 31); 234 | // Diagnostic: (23,31): warning HeapAnalyzerMethodGroupAllocationRule: This will allocate a delegate instance 235 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 23, character: 31); 236 | // Diagnostic: (23,31): warning HeapAnalyzerDelegateOnStructRule: Struct instance method being used for delegate creation, this will result in a boxing instruction 237 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, line: 23, character: 31); 238 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, line: 24, character: 56); 239 | } 240 | 241 | [TestMethod] 242 | public void TypeConversionAllocation_EqualsValueClauseSyntax() 243 | { 244 | // for (object i = 0;;) 245 | var sampleProgram = 246 | @"using System; 247 | 248 | for (object i = 0;;) // Allocation 249 | { 250 | } 251 | 252 | for (int i = 0;;) // NO Allocation 253 | { 254 | }"; 255 | 256 | var analyser = new TypeConversionAllocationAnalyzer(); 257 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create( 258 | SyntaxKind.SimpleAssignmentExpression, 259 | SyntaxKind.ReturnStatement, 260 | SyntaxKind.YieldReturnStatement, 261 | SyntaxKind.CastExpression, 262 | SyntaxKind.AsExpression, 263 | SyntaxKind.CoalesceExpression, 264 | SyntaxKind.ConditionalExpression, 265 | SyntaxKind.ForEachStatement, 266 | SyntaxKind.EqualsValueClause, 267 | SyntaxKind.Argument)); 268 | 269 | Assert.AreEqual(1, info.Allocations.Count); 270 | 271 | // Diagnostic: (3,17): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 272 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 3, character: 17); 273 | } 274 | 275 | [TestMethod] 276 | public void TypeConversionAllocation_EqualsValueClauseSyntax_WithDelegates() 277 | { 278 | var sampleProgram = 279 | @"using System; 280 | 281 | public class MyClass 282 | { 283 | public void Testing() 284 | { 285 | Func func2 = fooObjCall; // implicit, so Allocation 286 | Func func1 = new Func(fooObjCall); // Explicit, so NO Allocation 287 | } 288 | 289 | private string fooObjCall(object obj) 290 | { 291 | return obj.ToString(); 292 | } 293 | } 294 | 295 | public struct MyStruct 296 | { 297 | public void Testing() 298 | { 299 | Func func2 = fooObjCall; // implicit allocation + boxing 300 | Func func1 = new Func(fooObjCall); // Explicit allocation + boxing 301 | } 302 | 303 | private string fooObjCall(object obj) 304 | { 305 | return obj.ToString(); 306 | } 307 | }"; 308 | 309 | var analyser = new TypeConversionAllocationAnalyzer(); 310 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.CoalesceExpression, SyntaxKind.EqualsValueClause)); 311 | 312 | Assert.AreEqual(4, info.Allocations.Count); 313 | 314 | // Diagnostic: (7,38): warning HeapAnalyzerMethodGroupAllocationRule: This will allocate a delegate instance 315 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 7, character: 38); 316 | // Diagnostic: (21,38): warning HeapAnalyzerMethodGroupAllocationRule: This will allocate a delegate instance 317 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 21, character: 38); 318 | // Diagnostic: (21,38): warning HeapAnalyzerDelegateOnStructRule: Struct instance method being used for delegate creation, this will result in a boxing instruction 319 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, line: 21, character: 38); 320 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, line: 22, character: 63); 321 | // TODO this is a false positive 322 | // Diagnostic: (22,63): warning HeapAnalyzerDelegateOnStructRule: Struct instance method being used for delegate creation, this will result in a boxing instruction 323 | } 324 | 325 | [TestMethod] 326 | public void TypeConversionAllocation_EqualsValueClause_ExplicitMethodGroupAllocation_Bug() 327 | { 328 | // See https://github.com/mjsabby/RoslynClrHeapAllocationAnalyzer/issues/2 329 | var sampleProgram = 330 | @"using System; 331 | 332 | public class MyClass 333 | { 334 | public void Testing() 335 | { 336 | Action methodGroup = this.Method; 337 | } 338 | 339 | private void Method() 340 | { 341 | } 342 | } 343 | 344 | public struct MyStruct 345 | { 346 | public void Testing() 347 | { 348 | Action methodGroup = this.Method; 349 | } 350 | 351 | private void Method() 352 | { 353 | } 354 | }"; 355 | 356 | var analyser = new TypeConversionAllocationAnalyzer(); 357 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.EqualsValueClause)); 358 | 359 | Assert.AreEqual(3, info.Allocations.Count); 360 | } 361 | 362 | [TestMethod] 363 | public void TypeConversionAllocation_ConditionalExpressionSyntax() 364 | { 365 | var sampleProgram = 366 | @"using System; 367 | 368 | object obj = ""test""; 369 | object test1 = true ? 0 : obj; // Allocation 370 | object test2 = true ? 0.ToString() : obj; // NO Allocation 371 | "; 372 | 373 | var analyser = new TypeConversionAllocationAnalyzer(); 374 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.ConditionalExpression)); 375 | 376 | Assert.AreEqual(1, info.Allocations.Count); 377 | 378 | // Diagnostic: (4,23): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 379 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 4, character: 23); 380 | } 381 | 382 | [TestMethod] 383 | public void TypeConversionAllocation_CastExpressionSyntax() 384 | { 385 | var sampleProgram = 386 | @"using System; 387 | 388 | var f1 = (object)5; // Allocation 389 | var f2 = (object)""5""; // NO Allocation 390 | "; 391 | 392 | var analyser = new TypeConversionAllocationAnalyzer(); 393 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.CastExpression)); 394 | 395 | Assert.AreEqual(1, info.Allocations.Count); 396 | 397 | // Diagnostic: (3,18): warning HeapAnalyzerBoxingRule: Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable 398 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 3, character: 18); 399 | } 400 | 401 | [TestMethod] 402 | public void TypeConversionAllocation_ArgumentWithImplicitStringCastOperator() { 403 | const string programWithoutImplicitCastOperator = @" 404 | public struct AStruct 405 | { 406 | public static void Dump(AStruct astruct) 407 | { 408 | System.Console.WriteLine(astruct); 409 | } 410 | } 411 | "; 412 | 413 | const string programWithImplicitCastOperator = @" 414 | public struct AStruct 415 | { 416 | public readonly string WrappedString; 417 | 418 | public AStruct(string s) 419 | { 420 | WrappedString = s ?? """"; 421 | } 422 | 423 | public static void Dump(AStruct astruct) 424 | { 425 | System.Console.WriteLine(astruct); 426 | } 427 | 428 | public static implicit operator string(AStruct astruct) 429 | { 430 | return astruct.WrappedString; 431 | } 432 | } 433 | "; 434 | 435 | var analyzer = new TypeConversionAllocationAnalyzer(); 436 | 437 | var info0 = ProcessCode(analyzer, programWithoutImplicitCastOperator, ImmutableArray.Create(SyntaxKind.Argument)); 438 | AssertEx.ContainsDiagnostic(info0.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 6, character: 50); 439 | 440 | var info1 = ProcessCode(analyzer, programWithImplicitCastOperator, ImmutableArray.Create(SyntaxKind.Argument)); 441 | Assert.AreEqual(0, info1.Allocations.Count); 442 | } 443 | 444 | 445 | [TestMethod] 446 | public void TypeConversionAllocation_YieldReturnImplicitStringCastOperator() { 447 | const string programWithoutImplicitCastOperator = @" 448 | public struct AStruct 449 | { 450 | public System.Collections.Generic.IEnumerator GetEnumerator() 451 | { 452 | yield return this; 453 | } 454 | } 455 | "; 456 | 457 | const string programWithImplicitCastOperator = @" 458 | public struct AStruct 459 | { 460 | public System.Collections.Generic.IEnumerator GetEnumerator() 461 | { 462 | yield return this; 463 | } 464 | 465 | public static implicit operator string(AStruct astruct) 466 | { 467 | return """"; 468 | } 469 | } 470 | "; 471 | 472 | var analyzer = new TypeConversionAllocationAnalyzer(); 473 | 474 | var info0 = ProcessCode(analyzer, programWithoutImplicitCastOperator, ImmutableArray.Create(SyntaxKind.Argument)); 475 | AssertEx.ContainsDiagnostic(info0.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 6, character: 38); 476 | 477 | var info1 = ProcessCode(analyzer, programWithImplicitCastOperator, ImmutableArray.Create(SyntaxKind.Argument)); 478 | Assert.AreEqual(0, info1.Allocations.Count); 479 | } 480 | 481 | [TestMethod] 482 | public void TypeConversionAllocation_InterpolatedStringWithInt_BoxingWarning() { 483 | var sampleProgram = @"string s = $""{1}"";"; 484 | 485 | var analyser = new TypeConversionAllocationAnalyzer(); 486 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.Interpolation)); 487 | 488 | Assert.AreEqual(1, info.Allocations.Count); 489 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 1, character: 15); 490 | } 491 | 492 | [TestMethod] 493 | public void TypeConversionAllocation_InterpolatedStringWithString_NoWarning() { 494 | var sampleProgram = @"string s = $""{1.ToString()}"";"; 495 | 496 | var analyser = new TypeConversionAllocationAnalyzer(); 497 | var info = ProcessCode(analyser, sampleProgram, ImmutableArray.Create(SyntaxKind.Interpolation)); 498 | 499 | Assert.AreEqual(0, info.Allocations.Count); 500 | } 501 | 502 | [TestMethod] 503 | public void TypeConversionAllocation_DelegateAssignmentToReadonly_DoNotWarn() 504 | { 505 | string[] snippets = 506 | { 507 | @"private readonly System.Func fileExists = System.IO.File.Exists;", 508 | @"private static readonly System.Func fileExists = System.IO.File.Exists;", 509 | @"private System.Func fileExists { get; } = System.IO.File.Exists;", 510 | @"private static System.Func fileExists { get; } = System.IO.File.Exists;" 511 | }; 512 | 513 | var analyzer = new TypeConversionAllocationAnalyzer(); 514 | foreach (var snippet in snippets) 515 | { 516 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create(SyntaxKind.Argument)); 517 | Assert.AreEqual(1, info.Allocations.Count(x => x.Id == TypeConversionAllocationAnalyzer.ReadonlyMethodGroupAllocationRule.Id), snippet); 518 | } 519 | } 520 | 521 | [TestMethod] 522 | public void TypeConversionAllocation_ExpressionBodiedPropertyBoxing_WithBoxing() { 523 | const string snippet = @" 524 | class Program 525 | { 526 | object Obj => 1; 527 | } 528 | "; 529 | 530 | var analyzer = new TypeConversionAllocationAnalyzer(); 531 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create( 532 | SyntaxKind.ArrowExpressionClause)); 533 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.ValueTypeToReferenceTypeConversionRule.Id, line: 4, character: 35); 534 | } 535 | 536 | [TestMethod] 537 | public void TypeConversionAllocation_ExpressionBodiedPropertyBoxing_WithoutBoxing() { 538 | const string snippet = @" 539 | class Program 540 | { 541 | object Obj => 1.ToString(); 542 | } 543 | "; 544 | 545 | var analyzer = new TypeConversionAllocationAnalyzer(); 546 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create( 547 | SyntaxKind.ArrowExpressionClause)); 548 | Assert.AreEqual(0, info.Allocations.Count); 549 | } 550 | 551 | [TestMethod] 552 | public void TypeConversionAllocation_ExpressionBodiedPropertyDelegate() { 553 | const string snippet = @" 554 | using System; 555 | class Program 556 | { 557 | void Function(int i) { } 558 | 559 | Action Obj => Function; 560 | } 561 | "; 562 | 563 | var analyzer = new TypeConversionAllocationAnalyzer(); 564 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create( 565 | SyntaxKind.ArrowExpressionClause)); 566 | AssertEx.ContainsDiagnostic(info.Allocations, id: TypeConversionAllocationAnalyzer.MethodGroupAllocationRule.Id, line: 7, character: 40); 567 | } 568 | 569 | [TestMethod] 570 | [Description("Tests that an explicit delegate creation does not trigger HAA0603. " + 571 | "It should be handled by HAA0502.")] 572 | public void TypeConversionAllocation_ExpressionBodiedPropertyExplicitDelegate_NoWarning() { 573 | const string snippet = @" 574 | using System; 575 | class Program 576 | { 577 | void Function(int i) { } 578 | 579 | Action Obj => new Action(Function); 580 | } 581 | "; 582 | 583 | var analyzer = new TypeConversionAllocationAnalyzer(); 584 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create( 585 | SyntaxKind.ArrowExpressionClause)); 586 | Assert.AreEqual(0, info.Allocations.Count); 587 | } 588 | 589 | [TestMethod] 590 | public void TypeConversionAllocation_NoDiagnosticWhenPassingDelegateAsArgument() { 591 | const string snippet = @" 592 | using System; 593 | struct Foo 594 | { 595 | void Do(Action process) 596 | { 597 | DoMore(process); // Analyzer triggers warning here, indicating 'process' will be boxed. 598 | } 599 | 600 | void DoMore(Action process) 601 | { 602 | process(); 603 | } 604 | } 605 | "; 606 | 607 | var analyzer = new TypeConversionAllocationAnalyzer(); 608 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create(SyntaxKind.Argument)); 609 | AssertEx.ContainsNoDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, 7, 16 ); 610 | } 611 | 612 | [TestMethod] 613 | public void TypeConversionAllocation_ReportBoxingAllocationForPassingStructInstanceMethodForDelegateConstructor() { 614 | const string snippet = @" 615 | using System; 616 | public struct MyStruct { 617 | public void Testing() { 618 | var @struct = new MyStruct(); 619 | @struct.ProcessFunc(new Func(FooObjCall)); 620 | } 621 | 622 | public void ProcessFunc(Func func) { 623 | } 624 | 625 | private string FooObjCall(object obj) { 626 | return obj.ToString(); 627 | } 628 | } 629 | "; 630 | 631 | var analyzer = new TypeConversionAllocationAnalyzer(); 632 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create(SyntaxKind.Argument)); 633 | AssertEx.ContainsDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, 6, 54 ); 634 | } 635 | 636 | [TestMethod] 637 | public void TypeConversionAllocation_DoNotReportBoxingAllocationForPassingStructStaticMethodForDelegateConstructor() { 638 | const string snippet = @" 639 | using System; 640 | public struct MyStruct { 641 | public void Testing() { 642 | var @struct = new MyStruct(); 643 | @struct.ProcessFunc(new Func(FooObjCall)); 644 | } 645 | 646 | public void ProcessFunc(Func func) { 647 | } 648 | 649 | private static string FooObjCall(object obj) { 650 | return obj.ToString(); 651 | } 652 | } 653 | "; 654 | 655 | var analyzer = new TypeConversionAllocationAnalyzer(); 656 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create(SyntaxKind.Argument)); 657 | AssertEx.ContainsNoDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, 6, 54 ); 658 | } 659 | 660 | [TestMethod] 661 | public void TypeConversionAllocation_DoNotReportInlineDelegateAsStructInstanceMethods() { 662 | const string snippet = @" 663 | using System; 664 | public struct MyStruct { 665 | public void Testing() { 666 | var ints = new[] { 5, 4, 3, 2, 1 }; 667 | Array.Sort(ints, delegate(int x, int y) { return x - y; }); 668 | Array.Sort(ints, (x, y) => x - y); 669 | DoSomething(() => throw new Exception()); 670 | DoSomething(delegate() { throw new Exception(); }); 671 | } 672 | 673 | private static void DoSomething(Action action) 674 | { 675 | } 676 | } 677 | "; 678 | 679 | var analyzer = new TypeConversionAllocationAnalyzer(); 680 | var info = ProcessCode(analyzer, snippet, ImmutableArray.Create(SyntaxKind.Argument)); 681 | AssertEx.ContainsNoDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, 6, 26 ); 682 | AssertEx.ContainsNoDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, 7, 26 ); 683 | AssertEx.ContainsNoDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, 8, 26 ); 684 | AssertEx.ContainsNoDiagnostic(info.Allocations, TypeConversionAllocationAnalyzer.DelegateOnStructInstanceRule.Id, 9, 26 ); 685 | } 686 | } 687 | } 688 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Test/app.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Vsix/ClrHeapAllocationAnalyzer.Vsix.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 15.0 6 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 7 | 14.0 8 | publish\ 9 | true 10 | Disk 11 | false 12 | Foreground 13 | 7 14 | Days 15 | false 16 | false 17 | true 18 | 0 19 | 1.0.0.%2a 20 | false 21 | false 22 | true 23 | 24 | 25 | 26 | 27 | Debug 28 | AnyCPU 29 | AnyCPU 30 | 2.0 31 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 32 | {B1412F49-1ABA-4698-B928-451FDA8DAF85} 33 | Library 34 | Properties 35 | ClrHeapAllocation 36 | ClrHeapAllocation 37 | v4.7.2 38 | false 39 | false 40 | false 41 | false 42 | false 43 | false 44 | Roslyn 45 | 46 | 47 | true 48 | full 49 | false 50 | bin\Debug\ 51 | DEBUG;TRACE 52 | prompt 53 | 4 54 | 55 | 56 | pdbonly 57 | true 58 | bin\Release\ 59 | TRACE 60 | prompt 61 | 4 62 | 63 | 64 | Program 65 | $(DevEnvDir)devenv.exe 66 | /rootsuffix Roslyn 67 | 68 | 69 | 70 | Designer 71 | 72 | 73 | 74 | 75 | Always 76 | true 77 | 78 | 79 | 80 | 81 | {EAF46154-6613-4CD6-BE70-9015FF94F9CF} 82 | ClrHeapAllocationAnalyzer 83 | 84 | 85 | 86 | 87 | False 88 | Microsoft .NET Framework 4.6.1 %28x86 and x64%29 89 | true 90 | 91 | 92 | False 93 | .NET Framework 3.5 SP1 94 | false 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Vsix/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer.Vsix/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Clr Heap Allocation Analyzer 6 | Clr Heap Allocation Analyzer is a Roslyn based Diagnostic Analyzer that is able to detect most allocations in code in the local method and bring them to your attention in Visual Studio. It can detect subtle allocations caused by value type boxing, closure captures, delegate instantiations, etc. 7 | https://github.com/Microsoft/RoslynClrHeapAllocationAnalyzer 8 | LICENSE.txt 9 | roslyn clr allocations boxing closure display performance 10 | 11 | 12 | 13 | x86 14 | 15 | 16 | amd64 17 | 18 | 19 | x86 20 | 21 | 22 | amd64 23 | 24 | 25 | x86 26 | 27 | 28 | amd64 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/AllocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis.CSharp; 2 | using Microsoft.CodeAnalysis.Diagnostics; 3 | using System.Linq; 4 | 5 | namespace ClrHeapAllocationAnalyzer 6 | { 7 | public abstract class AllocationAnalyzer : DiagnosticAnalyzer 8 | { 9 | protected abstract SyntaxKind[] Expressions { get; } 10 | 11 | protected abstract void AnalyzeNode(SyntaxNodeAnalysisContext context); 12 | 13 | public override void Initialize(AnalysisContext context) 14 | { 15 | context.EnableConcurrentExecution(); 16 | context.ConfigureGeneratedCodeAnalysis(GeneratedCodeAnalysisFlags.Analyze | GeneratedCodeAnalysisFlags.ReportDiagnostics); 17 | context.RegisterSyntaxNodeAction(Analyze, Expressions); 18 | } 19 | 20 | private void Analyze(SyntaxNodeAnalysisContext context) 21 | { 22 | if (AllocationRules.IsIgnoredFile(context.Node.SyntaxTree.FilePath)) 23 | { 24 | return; 25 | } 26 | 27 | if (context.ContainingSymbol.GetAttributes().Any(AllocationRules.IsIgnoredAttribute)) 28 | { 29 | return; 30 | } 31 | 32 | AnalyzeNode(context); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/AllocationRules.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | using System; 3 | using System.Collections.Generic; 4 | 5 | namespace ClrHeapAllocationAnalyzer 6 | { 7 | public class AllocationRules 8 | { 9 | private static readonly HashSet> IgnoredAttributes = new HashSet<(string, string)> 10 | { 11 | ("System.Runtime.CompilerServices", "CompilerGeneratedAttribute"), 12 | ("System.CodeDom.Compiler", "GeneratedCodeAttribute") 13 | }; 14 | 15 | public static bool IsIgnoredFile(string filePath) 16 | { 17 | return filePath.EndsWith(".g.cs", StringComparison.OrdinalIgnoreCase); 18 | } 19 | 20 | public static bool IsIgnoredAttribute(AttributeData attribute) 21 | { 22 | return IgnoredAttributes.Contains((attribute.AttributeClass.ContainingNamespace.ToString(), attribute.AttributeClass.Name)); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/AvoidAllocationWithArrayEmptyCodeFix.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Composition; 5 | using System.Threading.Tasks; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CodeFixes; 8 | using System.Linq; 9 | using System.Threading; 10 | using Microsoft.CodeAnalysis.CodeActions; 11 | using Microsoft.CodeAnalysis.CSharp; 12 | using Microsoft.CodeAnalysis.CSharp.Syntax; 13 | 14 | namespace ClrHeapAllocationAnalyzer 15 | { 16 | [ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(AvoidAllocationWithArrayEmptyCodeFix)), Shared] 17 | public class AvoidAllocationWithArrayEmptyCodeFix : CodeFixProvider 18 | { 19 | private const string RemoveUnnecessaryListCreation = "Avoid allocation by using Array.Empty<>()"; 20 | 21 | public sealed override ImmutableArray FixableDiagnosticIds 22 | => ImmutableArray.Create(ExplicitAllocationAnalyzer.NewObjectRule.Id, ExplicitAllocationAnalyzer.NewArrayRule.Id); 23 | 24 | public sealed override FixAllProvider GetFixAllProvider() => WellKnownFixAllProviders.BatchFixer; 25 | 26 | public sealed override async Task RegisterCodeFixesAsync(CodeFixContext context) 27 | { 28 | var root = await context.Document.GetSyntaxRootAsync(context.CancellationToken).ConfigureAwait(false); 29 | var diagnostic = context.Diagnostics.First(); 30 | var diagnosticSpan = diagnostic.Location.SourceSpan; 31 | var node = root.FindNode(diagnosticSpan); 32 | 33 | if (IsReturnStatement(node)) 34 | { 35 | await TryToRegisterCodeFixesForReturnStatement(context, node, diagnostic); 36 | return; 37 | } 38 | 39 | if (IsMethodInvocationParameter(node)) 40 | { 41 | await TryToRegisterCodeFixesForMethodInvocationParameter(context, node, diagnostic); 42 | return; 43 | } 44 | } 45 | 46 | private async Task TryToRegisterCodeFixesForMethodInvocationParameter(CodeFixContext context, SyntaxNode node, Diagnostic diagnostic) 47 | { 48 | var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken); 49 | if (IsExpectedParameterReadonlySequence(node, semanticModel) && node is ArgumentSyntax argument) 50 | { 51 | TryRegisterCodeFix(context, node, diagnostic, argument.Expression, semanticModel); 52 | } 53 | } 54 | 55 | private async Task TryToRegisterCodeFixesForReturnStatement(CodeFixContext context, SyntaxNode node, Diagnostic diagnostic) 56 | { 57 | var semanticModel = await context.Document.GetSemanticModelAsync(context.CancellationToken); 58 | 59 | if (IsInsideMemberReturningEnumerable(node, semanticModel)) 60 | { 61 | TryRegisterCodeFix(context, node, diagnostic, node, semanticModel); 62 | } 63 | } 64 | 65 | private void TryRegisterCodeFix(CodeFixContext context, SyntaxNode node, Diagnostic diagnostic, SyntaxNode creationExpression, SemanticModel semanticModel) 66 | { 67 | switch (creationExpression) 68 | { 69 | case ObjectCreationExpressionSyntax objectCreation: 70 | { 71 | if (CanBeReplaceWithEnumerableEmpty(objectCreation, semanticModel)) 72 | { 73 | if (objectCreation.Type is GenericNameSyntax genericName) 74 | { 75 | var codeAction = CodeAction.Create(RemoveUnnecessaryListCreation, 76 | token => Transform(context.Document, node, genericName.TypeArgumentList.Arguments[0], token), 77 | RemoveUnnecessaryListCreation); 78 | context.RegisterCodeFix(codeAction, diagnostic); 79 | } 80 | } 81 | } 82 | break; 83 | case ArrayCreationExpressionSyntax arrayCreation: 84 | { 85 | if (CanBeReplaceWithEnumerableEmpty(arrayCreation)) 86 | { 87 | var codeAction = CodeAction.Create(RemoveUnnecessaryListCreation, 88 | token => Transform(context.Document, node, arrayCreation.Type.ElementType, token), 89 | RemoveUnnecessaryListCreation); 90 | context.RegisterCodeFix(codeAction, diagnostic); 91 | } 92 | } 93 | break; 94 | } 95 | } 96 | 97 | 98 | private bool IsMethodInvocationParameter(SyntaxNode node) => node is ArgumentSyntax; 99 | 100 | private static bool IsReturnStatement(SyntaxNode node) 101 | { 102 | return node.Parent is ReturnStatementSyntax || node.Parent is YieldStatementSyntax || node.Parent is ArrowExpressionClauseSyntax; 103 | } 104 | 105 | private bool IsInsideMemberReturningEnumerable(SyntaxNode node, SemanticModel semanticModel) 106 | { 107 | return IsInsideMethodReturningEnumerable(node, semanticModel) || 108 | IsInsidePropertyDeclaration(node, semanticModel); 109 | 110 | } 111 | 112 | private bool IsInsidePropertyDeclaration(SyntaxNode node, SemanticModel semanticModel) 113 | { 114 | if(node.FindContainer() is PropertyDeclarationSyntax propertyDeclaration && IsPropertyTypeReadonlySequence(semanticModel, propertyDeclaration)) 115 | { 116 | return IsAutoPropertyWithGetter(node) || IsArrowExpression(node); 117 | } 118 | 119 | return false; 120 | } 121 | 122 | private bool IsAutoPropertyWithGetter(SyntaxNode node) 123 | { 124 | if(node.FindContainer() is AccessorDeclarationSyntax accessorDeclaration) 125 | { 126 | return accessorDeclaration.Keyword.Text == "get"; 127 | } 128 | 129 | return false; 130 | } 131 | 132 | private bool IsArrowExpression(SyntaxNode node) 133 | { 134 | return node.FindContainer() != null; 135 | } 136 | 137 | private bool CanBeReplaceWithEnumerableEmpty(ArrayCreationExpressionSyntax arrayCreation) 138 | { 139 | return IsInitializationBlockEmpty(arrayCreation.Initializer); 140 | } 141 | 142 | private bool CanBeReplaceWithEnumerableEmpty(ObjectCreationExpressionSyntax objectCreation, SemanticModel semanticModel) 143 | { 144 | return IsCollectionType(semanticModel, objectCreation) && 145 | IsInitializationBlockEmpty(objectCreation.Initializer) && 146 | IsCopyConstructor(semanticModel, objectCreation) == false; 147 | } 148 | 149 | private static bool IsInsideMethodReturningEnumerable(SyntaxNode node, SemanticModel semanticModel) 150 | { 151 | if (node.FindContainer() is MethodDeclarationSyntax methodDeclaration) 152 | { 153 | if (IsReturnTypeReadonlySequence(semanticModel, methodDeclaration)) 154 | { 155 | return true; 156 | } 157 | } 158 | 159 | return false; 160 | } 161 | 162 | private async Task Transform(Document contextDocument, SyntaxNode node, TypeSyntax typeArgument, CancellationToken cancellationToken) 163 | { 164 | var noAllocation = SyntaxFactory.ParseExpression($"Array.Empty<{typeArgument}>()"); 165 | var newNode = ReplaceExpression(node, noAllocation); 166 | if (newNode == null) 167 | { 168 | return contextDocument; 169 | } 170 | var syntaxRootAsync = await contextDocument.GetSyntaxRootAsync(cancellationToken); 171 | var newSyntaxRoot = syntaxRootAsync.ReplaceNode(node.Parent, newNode); 172 | return contextDocument.WithSyntaxRoot(newSyntaxRoot); 173 | } 174 | 175 | private SyntaxNode ReplaceExpression(SyntaxNode node, ExpressionSyntax newExpression) 176 | { 177 | switch (node.Parent) 178 | { 179 | case ReturnStatementSyntax parentReturn: 180 | return parentReturn.WithExpression(newExpression); 181 | case ArrowExpressionClauseSyntax arrowStatement: 182 | return arrowStatement.WithExpression(newExpression); 183 | case ArgumentListSyntax argumentList: 184 | var newArguments = argumentList.Arguments.Select(x => x == node ? SyntaxFactory.Argument(newExpression) : x); 185 | return argumentList.WithArguments(SyntaxFactory.SeparatedList(newArguments)); 186 | default: 187 | return null; 188 | } 189 | } 190 | 191 | private bool IsCopyConstructor(SemanticModel semanticModel, ObjectCreationExpressionSyntax objectCreation) 192 | { 193 | if (objectCreation.ArgumentList == null || objectCreation.ArgumentList.Arguments.Count == 0) 194 | { 195 | return false; 196 | } 197 | 198 | if (semanticModel.GetSymbolInfo(objectCreation).Symbol is IMethodSymbol methodSymbol) 199 | { 200 | if (methodSymbol.Parameters.Any(x=> x.Type is INamedTypeSymbol namedType && IsCollectionType(namedType))) 201 | { 202 | return true; 203 | } 204 | } 205 | return false; 206 | } 207 | 208 | private static bool IsInitializationBlockEmpty(InitializerExpressionSyntax initializer) 209 | { 210 | return initializer == null || initializer.Expressions.Count == 0; 211 | } 212 | 213 | private bool IsCollectionType(SemanticModel semanticModel, ObjectCreationExpressionSyntax objectCreationExpressionSyntax) 214 | { 215 | return semanticModel.GetTypeInfo(objectCreationExpressionSyntax).Type is INamedTypeSymbol createdType && 216 | (createdType.TypeKind == TypeKind.Array || IsCollectionType(createdType) ); 217 | } 218 | 219 | private bool IsCollectionType(INamedTypeSymbol typeSymbol) 220 | { 221 | return typeSymbol.ConstructedFrom.Interfaces.Any(x => 222 | x.IsGenericType && x.ToString().StartsWith("System.Collections.Generic.ICollection")); 223 | } 224 | 225 | private static bool IsPropertyTypeReadonlySequence(SemanticModel semanticModel, PropertyDeclarationSyntax propertyDeclaration) 226 | { 227 | return IsTypeReadonlySequence(semanticModel, propertyDeclaration.Type); 228 | } 229 | 230 | private static bool IsReturnTypeReadonlySequence(SemanticModel semanticModel, MethodDeclarationSyntax methodDeclarationSyntax) 231 | { 232 | var typeSyntax = methodDeclarationSyntax.ReturnType; 233 | return IsTypeReadonlySequence(semanticModel, typeSyntax); 234 | } 235 | 236 | private bool IsExpectedParameterReadonlySequence(SyntaxNode node, SemanticModel semanticModel) 237 | { 238 | if (node is ArgumentSyntax argument && node.Parent is ArgumentListSyntax argumentList) 239 | { 240 | var argumentIndex = argumentList.Arguments.IndexOf(argument); 241 | if (semanticModel.GetSymbolInfo(argumentList.Parent).Symbol is IMethodSymbol methodSymbol) 242 | { 243 | if (methodSymbol.Parameters.Length > argumentIndex) 244 | { 245 | var parameterType = methodSymbol.Parameters[argumentIndex].Type; 246 | if (IsTypeReadonlySequence(semanticModel, parameterType)) 247 | { 248 | return true; 249 | } 250 | } 251 | } 252 | } 253 | 254 | return false; 255 | } 256 | 257 | private static bool IsTypeReadonlySequence(SemanticModel semanticModel, TypeSyntax typeSyntax) 258 | { 259 | var returnType = ModelExtensions.GetTypeInfo(semanticModel, typeSyntax).Type; 260 | return IsTypeReadonlySequence(semanticModel, returnType); 261 | } 262 | 263 | private static bool IsTypeReadonlySequence(SemanticModel semanticModel, ITypeSymbol type) 264 | { 265 | if (type.Kind == SymbolKind.ArrayType) 266 | { 267 | return true; 268 | } 269 | 270 | if (type is INamedTypeSymbol namedType && namedType.IsGenericType) 271 | { 272 | foreach (var readonlySequence in GetReadonlySequenceTypes(semanticModel)) 273 | { 274 | if (readonlySequence.Equals(namedType.ConstructedFrom)) 275 | { 276 | return true; 277 | } 278 | } 279 | } 280 | 281 | return false; 282 | } 283 | 284 | private static IEnumerable GetReadonlySequenceTypes(SemanticModel semanticModel) 285 | { 286 | yield return semanticModel.Compilation.GetTypeByMetadataName("System.Collections.Generic.IEnumerable`1"); 287 | yield return semanticModel.Compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyList`1"); 288 | yield return semanticModel.Compilation.GetTypeByMetadataName("System.Collections.Generic.IReadOnlyCollection`1"); 289 | } 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/CallSiteImplicitAllocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace ClrHeapAllocationAnalyzer 2 | { 3 | using System; 4 | using System.Collections.Immutable; 5 | using System.Runtime.CompilerServices; 6 | using System.Threading; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.CSharp.Syntax; 10 | using Microsoft.CodeAnalysis.Diagnostics; 11 | using Microsoft.CodeAnalysis.Operations; 12 | 13 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 14 | public sealed class CallSiteImplicitAllocationAnalyzer : AllocationAnalyzer 15 | { 16 | public static DiagnosticDescriptor ParamsParameterRule = new DiagnosticDescriptor("HAA0101", "Array allocation for params parameter", "This call site is calling into a function with a 'params' parameter. This results in an array allocation", "Performance", DiagnosticSeverity.Warning, true); 17 | 18 | public static DiagnosticDescriptor ValueTypeNonOverridenCallRule = new DiagnosticDescriptor("HAA0102", "Non-overridden virtual method call on value type", "Non-overridden virtual method call on a value type adds a boxing or constrained instruction", "Performance", DiagnosticSeverity.Warning, true); 19 | 20 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(ParamsParameterRule, ValueTypeNonOverridenCallRule); 21 | 22 | protected override SyntaxKind[] Expressions => new[] { SyntaxKind.InvocationExpression }; 23 | 24 | private static readonly object[] EmptyMessageArgs = { }; 25 | 26 | protected override void AnalyzeNode(SyntaxNodeAnalysisContext context) 27 | { 28 | var node = context.Node; 29 | var semanticModel = context.SemanticModel; 30 | Action reportDiagnostic = context.ReportDiagnostic; 31 | var cancellationToken = context.CancellationToken; 32 | string filePath = node.SyntaxTree.FilePath; 33 | 34 | var invocationOperation = semanticModel.GetOperation(node, cancellationToken) as IInvocationOperation; 35 | if(invocationOperation is null) 36 | { 37 | return; 38 | } 39 | 40 | var targetMethod = invocationOperation.TargetMethod; 41 | 42 | if (targetMethod.IsOverride) 43 | { 44 | CheckNonOverridenMethodOnStruct(targetMethod, reportDiagnostic, node, filePath); 45 | } 46 | 47 | bool compilationHasSystemArrayEmpty = !semanticModel.Compilation.GetSpecialType(SpecialType.System_Array).GetMembers("Empty").IsEmpty; 48 | 49 | // Loop on every argument because params argument may not be the last one. 50 | // static void Fun1() => Fun2(args: "", i: 5); 51 | // static void Fun2(int i = 0, params object[] args) {} 52 | foreach (var argument in invocationOperation.Arguments) 53 | { 54 | if (argument.ArgumentKind != ArgumentKind.ParamArray) 55 | { 56 | continue; 57 | } 58 | 59 | bool isEmpty = (argument.Value as IArrayCreationOperation)?.Initializer.ElementValues.IsEmpty == true; 60 | 61 | // Up to net45 the System.Array.Empty singleton didn't existed so an empty params array was still causing some memory allocation. 62 | if (argument.IsImplicit && (!isEmpty || !compilationHasSystemArrayEmpty)) 63 | { 64 | reportDiagnostic(Diagnostic.Create(ParamsParameterRule, node.GetLocation(), EmptyMessageArgs)); 65 | } 66 | 67 | break; 68 | } 69 | } 70 | 71 | private static void CheckNonOverridenMethodOnStruct(IMethodSymbol methodInfo, Action reportDiagnostic, SyntaxNode node, string filePath) 72 | { 73 | if (methodInfo.ContainingType != null) 74 | { 75 | // hack? Hmmm. 76 | var containingType = methodInfo.ContainingType.ToString(); 77 | if (string.Equals(containingType, "System.ValueType", StringComparison.OrdinalIgnoreCase) || string.Equals(containingType, "System.Enum", StringComparison.OrdinalIgnoreCase)) 78 | { 79 | reportDiagnostic(Diagnostic.Create(ValueTypeNonOverridenCallRule, node.GetLocation(), EmptyMessageArgs)); 80 | HeapAllocationAnalyzerEventSource.Logger.NonOverridenVirtualMethodCallOnValueType(filePath); 81 | } 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/ClrHeapAllocationAnalyzer.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | false 6 | True 7 | 8 | 9 | 10 | ClrHeapAllocationAnalyzer 11 | 3.0.0.0 12 | mjsabby 13 | https://github.com/Microsoft/RoslynClrHeapAllocationAnalyzer/blob/master/LICENSE 14 | https://github.com/Microsoft/RoslynClrHeapAllocationAnalyzer 15 | https://github.com/Microsoft/RoslynClrHeapAllocationAnalyzer 16 | false 17 | 18 | Roslyn based C# heap allocation diagnostic analyzer that can detect explicit and many implicit allocations like boxing, display classes a.k.a closures, implicit delegate creations, etc 19 | 20 | The code-assist version that integrates with the Visual Studio 2019 IDE is here, https://marketplace.visualstudio.com/items?itemName=MukulSabharwal.ClrHeapAllocationAnalyzer 21 | 22 | NOTE: This is the build analyzer. 23 | NOTE: You require ' ' ' ' Visual Studio 2019 ' ' ' ' for this to work. 24 | 25 | Summary of changes made in this release of the package. 26 | Copyright 27 | clr allocations boxing closure displayclass delegate enumerator newobj roslyn analyzer diagnostic 28 | true 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/ConcatenationAllocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace ClrHeapAllocationAnalyzer { 2 | using System; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | using Microsoft.CodeAnalysis.Diagnostics; 9 | 10 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 11 | public sealed class ConcatenationAllocationAnalyzer : AllocationAnalyzer 12 | { 13 | public static DiagnosticDescriptor StringConcatenationAllocationRule = new DiagnosticDescriptor("HAA0201", "Implicit string concatenation allocation", "Considering using StringBuilder", "Performance", DiagnosticSeverity.Warning, true, string.Empty, "https://docs.microsoft.com/en-us/dotnet/standard/base-types/stringbuilder"); 14 | 15 | public static DiagnosticDescriptor ValueTypeToReferenceTypeInAStringConcatenationRule = new DiagnosticDescriptor("HAA0202", "Value type to reference type conversion allocation for string concatenation", "Value type ({0}) is being boxed to a reference type for a string concatenation.", "Performance", DiagnosticSeverity.Warning, true, string.Empty, "https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/types/boxing-and-unboxing"); 16 | 17 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(StringConcatenationAllocationRule, ValueTypeToReferenceTypeInAStringConcatenationRule); 18 | 19 | protected override SyntaxKind[] Expressions => new[] { SyntaxKind.AddExpression, SyntaxKind.AddAssignmentExpression }; 20 | 21 | private static readonly object[] EmptyMessageArgs = { }; 22 | 23 | protected override void AnalyzeNode(SyntaxNodeAnalysisContext context) 24 | { 25 | var node = context.Node; 26 | var semanticModel = context.SemanticModel; 27 | Action reportDiagnostic = context.ReportDiagnostic; 28 | var cancellationToken = context.CancellationToken; 29 | string filePath = node.SyntaxTree.FilePath; 30 | var binaryExpressions = node.DescendantNodesAndSelf().OfType().Reverse(); // need inner most expressions 31 | 32 | int stringConcatenationCount = 0; 33 | foreach (var binaryExpression in binaryExpressions) { 34 | if (binaryExpression.Left == null || binaryExpression.Right == null) { 35 | continue; 36 | } 37 | 38 | bool isConstant = semanticModel.GetConstantValue(binaryExpression, cancellationToken).HasValue; 39 | if (isConstant) { 40 | continue; 41 | } 42 | 43 | var left = semanticModel.GetTypeInfo(binaryExpression.Left, cancellationToken); 44 | var leftConversion = semanticModel.GetConversion(binaryExpression.Left, cancellationToken); 45 | CheckTypeConversion(left, leftConversion, reportDiagnostic, binaryExpression.Left.GetLocation(), filePath); 46 | 47 | var right = semanticModel.GetTypeInfo(binaryExpression.Right, cancellationToken); 48 | var rightConversion = semanticModel.GetConversion(binaryExpression.Right, cancellationToken); 49 | CheckTypeConversion(right, rightConversion, reportDiagnostic, binaryExpression.Right.GetLocation(), filePath); 50 | 51 | // regular string allocation 52 | if (left.Type?.SpecialType == SpecialType.System_String || right.Type?.SpecialType == SpecialType.System_String) { 53 | stringConcatenationCount++; 54 | } 55 | } 56 | 57 | if (stringConcatenationCount > 3) 58 | { 59 | reportDiagnostic(Diagnostic.Create(StringConcatenationAllocationRule, node.GetLocation(), EmptyMessageArgs)); 60 | HeapAllocationAnalyzerEventSource.Logger.StringConcatenationAllocation(filePath); 61 | } 62 | } 63 | 64 | private static void CheckTypeConversion(TypeInfo typeInfo, Conversion conversionInfo, Action reportDiagnostic, Location location, string filePath) { 65 | bool IsOptimizedValueType(ITypeSymbol type) { 66 | return type.SpecialType == SpecialType.System_Boolean || 67 | type.SpecialType == SpecialType.System_Char || 68 | type.SpecialType == SpecialType.System_IntPtr || 69 | type.SpecialType == SpecialType.System_UIntPtr; 70 | } 71 | 72 | if (conversionInfo.IsBoxing && !IsOptimizedValueType(typeInfo.Type)) { 73 | reportDiagnostic(Diagnostic.Create(ValueTypeToReferenceTypeInAStringConcatenationRule, location, new[] { typeInfo.Type.ToDisplayString() })); 74 | HeapAllocationAnalyzerEventSource.Logger.BoxingAllocationInStringConcatenation(filePath); 75 | } 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/DisplayClassAllocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace ClrHeapAllocationAnalyzer 2 | { 3 | using System; 4 | using System.Collections.Immutable; 5 | using System.Linq; 6 | using System.Threading; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.CodeAnalysis.CSharp.Syntax; 10 | using Microsoft.CodeAnalysis.Diagnostics; 11 | 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public sealed class DisplayClassAllocationAnalyzer : AllocationAnalyzer 14 | { 15 | public static DiagnosticDescriptor ClosureDriverRule = new DiagnosticDescriptor("HAA0301", "Closure Allocation Source", "Heap allocation of closure Captures: {0}", "Performance", DiagnosticSeverity.Warning, true); 16 | 17 | public static DiagnosticDescriptor ClosureCaptureRule = new DiagnosticDescriptor("HAA0302", "Display class allocation to capture closure", "The compiler will emit a class that will hold this as a field to allow capturing of this closure", "Performance", DiagnosticSeverity.Warning, true); 18 | 19 | public static DiagnosticDescriptor LambaOrAnonymousMethodInGenericMethodRule = new DiagnosticDescriptor("HAA0303", "Lambda or anonymous method in a generic method allocates a delegate instance", "Considering moving this out of the generic method", "Performance", DiagnosticSeverity.Warning, true); 20 | 21 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(ClosureCaptureRule, ClosureDriverRule, LambaOrAnonymousMethodInGenericMethodRule); 22 | 23 | protected override SyntaxKind[] Expressions => new[] { SyntaxKind.ParenthesizedLambdaExpression, SyntaxKind.SimpleLambdaExpression, SyntaxKind.AnonymousMethodExpression }; 24 | 25 | private static readonly object[] EmptyMessageArgs = { }; 26 | 27 | protected override void AnalyzeNode(SyntaxNodeAnalysisContext context) 28 | { 29 | var node = context.Node; 30 | var semanticModel = context.SemanticModel; 31 | var cancellationToken = context.CancellationToken; 32 | Action reportDiagnostic = context.ReportDiagnostic; 33 | 34 | var anonExpr = node as AnonymousMethodExpressionSyntax; 35 | if (anonExpr?.Block?.ChildNodes() != null && anonExpr.Block.ChildNodes().Any()) 36 | { 37 | GenericMethodCheck(semanticModel, node, anonExpr.DelegateKeyword.GetLocation(), reportDiagnostic, cancellationToken); 38 | ClosureCaptureDataFlowAnalysis(semanticModel.AnalyzeDataFlow(anonExpr.Block.ChildNodes().First(), anonExpr.Block.ChildNodes().Last()), reportDiagnostic, anonExpr.DelegateKeyword.GetLocation()); 39 | return; 40 | } 41 | 42 | if (node is SimpleLambdaExpressionSyntax lambdaExpr) 43 | { 44 | GenericMethodCheck(semanticModel, node, lambdaExpr.ArrowToken.GetLocation(), reportDiagnostic, cancellationToken); 45 | ClosureCaptureDataFlowAnalysis(semanticModel.AnalyzeDataFlow(lambdaExpr), reportDiagnostic, lambdaExpr.ArrowToken.GetLocation()); 46 | return; 47 | } 48 | 49 | if (node is ParenthesizedLambdaExpressionSyntax parenLambdaExpr) 50 | { 51 | GenericMethodCheck(semanticModel, node, parenLambdaExpr.ArrowToken.GetLocation(), reportDiagnostic, cancellationToken); 52 | ClosureCaptureDataFlowAnalysis(semanticModel.AnalyzeDataFlow(parenLambdaExpr), reportDiagnostic, parenLambdaExpr.ArrowToken.GetLocation()); 53 | return; 54 | } 55 | } 56 | 57 | private static void ClosureCaptureDataFlowAnalysis(DataFlowAnalysis flow, Action reportDiagnostic, Location location) 58 | { 59 | if (flow?.Captured.Length <= 0) 60 | { 61 | return; 62 | } 63 | 64 | foreach (var capture in flow.Captured) 65 | { 66 | if (capture.Name != null && capture.Locations != null) 67 | { 68 | foreach (var l in capture.Locations) 69 | { 70 | reportDiagnostic(Diagnostic.Create(ClosureCaptureRule, l, EmptyMessageArgs)); 71 | } 72 | } 73 | } 74 | 75 | reportDiagnostic(Diagnostic.Create(ClosureDriverRule, location, new[] { string.Join(",", flow.Captured.Select(x => x.Name)) })); 76 | } 77 | 78 | private static void GenericMethodCheck(SemanticModel semanticModel, SyntaxNode node, Location location, Action reportDiagnostic, CancellationToken cancellationToken) 79 | { 80 | if (semanticModel.GetSymbolInfo(node, cancellationToken).Symbol != null) 81 | { 82 | var containingSymbol = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol.ContainingSymbol as IMethodSymbol; 83 | if (containingSymbol != null && containingSymbol.Arity > 0) 84 | { 85 | reportDiagnostic(Diagnostic.Create(LambaOrAnonymousMethodInGenericMethodRule, location, EmptyMessageArgs)); 86 | } 87 | } 88 | } 89 | } 90 | } -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/EnumeratorAllocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace ClrHeapAllocationAnalyzer 2 | { 3 | using System; 4 | using System.Collections.Immutable; 5 | using System.Linq; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CSharp; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | using Microsoft.CodeAnalysis.Diagnostics; 10 | 11 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 12 | public sealed class EnumeratorAllocationAnalyzer : AllocationAnalyzer 13 | { 14 | public static DiagnosticDescriptor ReferenceTypeEnumeratorRule = new DiagnosticDescriptor("HAA0401", "Possible allocation of reference type enumerator", "Non-ValueType enumerator may result in a heap allocation", "Performance", DiagnosticSeverity.Warning, true); 15 | 16 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(ReferenceTypeEnumeratorRule); 17 | 18 | protected override SyntaxKind[] Expressions => new[] { SyntaxKind.ForEachStatement, SyntaxKind.InvocationExpression }; 19 | 20 | private static readonly object[] EmptyMessageArgs = { }; 21 | 22 | protected override void AnalyzeNode(SyntaxNodeAnalysisContext context) 23 | { 24 | var node = context.Node; 25 | var semanticModel = context.SemanticModel; 26 | Action reportDiagnostic = context.ReportDiagnostic; 27 | var cancellationToken = context.CancellationToken; 28 | string filePath = node.SyntaxTree.FilePath; 29 | var foreachExpression = node as ForEachStatementSyntax; 30 | if (foreachExpression != null) 31 | { 32 | var typeInfo = semanticModel.GetTypeInfo(foreachExpression.Expression, cancellationToken); 33 | if (typeInfo.Type == null) 34 | return; 35 | 36 | if (typeInfo.Type.Name == "String" && typeInfo.Type.ContainingNamespace.Name == "System") 37 | { 38 | // Special case for System.String which is optmizined by 39 | // the compiler and does not result in an allocation. 40 | return; 41 | } 42 | 43 | // Regular way of getting the enumerator 44 | ImmutableArray enumerator = typeInfo.Type.GetMembers("GetEnumerator"); 45 | if ((enumerator == null || enumerator.Length == 0) && typeInfo.ConvertedType != null) 46 | { 47 | // 1st we try and fallback to using the ConvertedType 48 | enumerator = typeInfo.ConvertedType.GetMembers("GetEnumerator"); 49 | } 50 | if ((enumerator == null || enumerator.Length == 0) && typeInfo.Type.Interfaces != null) 51 | { 52 | // 2nd fallback, now we try and find the IEnumerable Interface explicitly 53 | var iEnumerable = typeInfo.Type.Interfaces.Where(i => i.Name == "IEnumerable").ToImmutableArray(); 54 | if (iEnumerable != null && iEnumerable.Length > 0) 55 | { 56 | enumerator = iEnumerable[0].GetMembers("GetEnumerator"); 57 | } 58 | } 59 | 60 | if (enumerator != null && enumerator.Length > 0) 61 | { 62 | var methodSymbol = enumerator[0] as IMethodSymbol; // probably should do something better here, hack. 63 | if (methodSymbol != null) 64 | { 65 | if (methodSymbol.ReturnType.IsReferenceType && methodSymbol.ReturnType.SpecialType != SpecialType.System_Collections_IEnumerator) 66 | { 67 | reportDiagnostic(Diagnostic.Create(ReferenceTypeEnumeratorRule, foreachExpression.InKeyword.GetLocation(), EmptyMessageArgs)); 68 | HeapAllocationAnalyzerEventSource.Logger.EnumeratorAllocation(filePath); 69 | } 70 | } 71 | } 72 | 73 | return; 74 | } 75 | 76 | var invocationExpression = node as InvocationExpressionSyntax; 77 | if (invocationExpression != null) 78 | { 79 | var methodInfo = semanticModel.GetSymbolInfo(invocationExpression, cancellationToken).Symbol as IMethodSymbol; 80 | if (methodInfo?.ReturnType != null && methodInfo.ReturnType.IsReferenceType) 81 | { 82 | if (methodInfo.ReturnType.AllInterfaces != null) 83 | { 84 | foreach (var @interface in methodInfo.ReturnType.AllInterfaces) 85 | { 86 | if (@interface.SpecialType == SpecialType.System_Collections_Generic_IEnumerator_T || @interface.SpecialType == SpecialType.System_Collections_IEnumerator) 87 | { 88 | reportDiagnostic(Diagnostic.Create(ReferenceTypeEnumeratorRule, invocationExpression.GetLocation(), EmptyMessageArgs)); 89 | HeapAllocationAnalyzerEventSource.Logger.EnumeratorAllocation(filePath); 90 | } 91 | } 92 | } 93 | } 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/ExplicitAllocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace ClrHeapAllocationAnalyzer 2 | { 3 | using System; 4 | using System.Collections.Immutable; 5 | using Microsoft.CodeAnalysis; 6 | using Microsoft.CodeAnalysis.CSharp; 7 | using Microsoft.CodeAnalysis.CSharp.Syntax; 8 | using Microsoft.CodeAnalysis.Diagnostics; 9 | 10 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 11 | public sealed class ExplicitAllocationAnalyzer : AllocationAnalyzer 12 | { 13 | public static DiagnosticDescriptor NewArrayRule = new DiagnosticDescriptor("HAA0501", "Explicit new array type allocation", "Explicit new array type allocation", "Performance", DiagnosticSeverity.Info, true); 14 | 15 | public static DiagnosticDescriptor NewObjectRule = new DiagnosticDescriptor("HAA0502", "Explicit new reference type allocation", "Explicit new reference type allocation", "Performance", DiagnosticSeverity.Info, true); 16 | 17 | public static DiagnosticDescriptor AnonymousNewObjectRule = new DiagnosticDescriptor("HAA0503", "Explicit new anonymous object allocation", "Explicit new anonymous object allocation", "Performance", DiagnosticSeverity.Info, true, string.Empty, "https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/anonymous-types"); 18 | 19 | public static DiagnosticDescriptor ImplicitArrayCreationRule = new DiagnosticDescriptor("HAA0504", "Implicit new array creation allocation", "Implicit new array creation allocation", "Performance", DiagnosticSeverity.Info, true); 20 | 21 | public static DiagnosticDescriptor InitializerCreationRule = new DiagnosticDescriptor("HAA0505", "Initializer reference type allocation", "Initializer reference type allocation", "Performance", DiagnosticSeverity.Info, true); 22 | 23 | public static DiagnosticDescriptor LetCauseRule = new DiagnosticDescriptor("HAA0506", "Let clause induced allocation", "Let clause induced allocation", "Performance", DiagnosticSeverity.Info, true); 24 | 25 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(LetCauseRule, InitializerCreationRule, ImplicitArrayCreationRule, AnonymousNewObjectRule, NewObjectRule, NewArrayRule); 26 | 27 | protected override SyntaxKind[] Expressions => new[] 28 | { 29 | SyntaxKind.ObjectCreationExpression, // Used 30 | SyntaxKind.AnonymousObjectCreationExpression, // Used 31 | SyntaxKind.ArrayInitializerExpression, // Used (this is inside an ImplicitArrayCreationExpression) 32 | SyntaxKind.CollectionInitializerExpression, // Is this used anywhere? 33 | SyntaxKind.ComplexElementInitializerExpression, // Is this used anywhere? For what this is see http://source.roslyn.codeplex.com/#Microsoft.CodeAnalysis.CSharp/Compilation/CSharpSemanticModel.cs,80 34 | SyntaxKind.ObjectInitializerExpression, // Used linked to InitializerExpressionSyntax 35 | SyntaxKind.ArrayCreationExpression, // Used 36 | SyntaxKind.ImplicitArrayCreationExpression, // Used (this then contains an ArrayInitializerExpression) 37 | SyntaxKind.LetClause // Used 38 | }; 39 | 40 | private static readonly object[] EmptyMessageArgs = { }; 41 | 42 | protected override void AnalyzeNode(SyntaxNodeAnalysisContext context) 43 | { 44 | var node = context.Node; 45 | var semanticModel = context.SemanticModel; 46 | Action reportDiagnostic = context.ReportDiagnostic; 47 | var cancellationToken = context.CancellationToken; 48 | string filePath = node.SyntaxTree.FilePath; 49 | 50 | // An InitializerExpressionSyntax has an ObjectCreationExpressionSyntax as it's parent, i.e 51 | // var testing = new TestClass { Name = "Bob" }; 52 | // | |--------------| <- InitializerExpressionSyntax or SyntaxKind.ObjectInitializerExpression 53 | // |----------------------------| <- ObjectCreationExpressionSyntax or SyntaxKind.ObjectCreationExpression 54 | var initializerExpression = node as InitializerExpressionSyntax; 55 | if (initializerExpression?.Parent is ObjectCreationExpressionSyntax) 56 | { 57 | var objectCreation = node.Parent as ObjectCreationExpressionSyntax; 58 | var typeInfo = semanticModel.GetTypeInfo(objectCreation, cancellationToken); 59 | if (typeInfo.ConvertedType?.TypeKind != TypeKind.Error && 60 | typeInfo.ConvertedType?.IsReferenceType == true && 61 | objectCreation.Parent?.IsKind(SyntaxKind.EqualsValueClause) == true && 62 | objectCreation.Parent?.Parent?.IsKind(SyntaxKind.VariableDeclarator) == true) 63 | { 64 | reportDiagnostic(Diagnostic.Create(InitializerCreationRule, ((VariableDeclaratorSyntax)objectCreation.Parent.Parent).Identifier.GetLocation(), EmptyMessageArgs)); 65 | HeapAllocationAnalyzerEventSource.Logger.NewInitializerExpression(filePath); 66 | return; 67 | } 68 | } 69 | 70 | var implicitArrayExpression = node as ImplicitArrayCreationExpressionSyntax; 71 | if (implicitArrayExpression != null) 72 | { 73 | reportDiagnostic(Diagnostic.Create(ImplicitArrayCreationRule, implicitArrayExpression.NewKeyword.GetLocation(), EmptyMessageArgs)); 74 | HeapAllocationAnalyzerEventSource.Logger.NewImplicitArrayCreationExpression(filePath); 75 | return; 76 | } 77 | 78 | var newAnon = node as AnonymousObjectCreationExpressionSyntax; 79 | if (newAnon != null) 80 | { 81 | reportDiagnostic(Diagnostic.Create(AnonymousNewObjectRule, newAnon.NewKeyword.GetLocation(), EmptyMessageArgs)); 82 | HeapAllocationAnalyzerEventSource.Logger.NewAnonymousObjectCreationExpression(filePath); 83 | return; 84 | } 85 | 86 | var newArr = node as ArrayCreationExpressionSyntax; 87 | if (newArr != null) 88 | { 89 | reportDiagnostic(Diagnostic.Create(NewArrayRule, newArr.NewKeyword.GetLocation(), EmptyMessageArgs)); 90 | HeapAllocationAnalyzerEventSource.Logger.NewArrayExpression(filePath); 91 | return; 92 | } 93 | 94 | var newObj = node as ObjectCreationExpressionSyntax; 95 | if (newObj != null) 96 | { 97 | var typeInfo = semanticModel.GetTypeInfo(newObj, cancellationToken); 98 | if (typeInfo.ConvertedType != null && typeInfo.ConvertedType.TypeKind != TypeKind.Error && typeInfo.ConvertedType.IsReferenceType) 99 | { 100 | reportDiagnostic(Diagnostic.Create(NewObjectRule, newObj.NewKeyword.GetLocation(), EmptyMessageArgs)); 101 | HeapAllocationAnalyzerEventSource.Logger.NewObjectCreationExpression(filePath); 102 | } 103 | return; 104 | } 105 | 106 | var letKind = node as LetClauseSyntax; 107 | if (letKind != null) 108 | { 109 | reportDiagnostic(Diagnostic.Create(LetCauseRule, letKind.LetKeyword.GetLocation(), EmptyMessageArgs)); 110 | HeapAllocationAnalyzerEventSource.Logger.LetClauseExpression(filePath); 111 | return; 112 | } 113 | } 114 | } 115 | } -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/HeapAllocationAnalyzerEventSource.cs: -------------------------------------------------------------------------------- 1 | namespace ClrHeapAllocationAnalyzer 2 | { 3 | using System.Diagnostics.Tracing; 4 | 5 | internal sealed class HeapAllocationAnalyzerEventSource : EventSource 6 | { 7 | public static HeapAllocationAnalyzerEventSource Logger = new HeapAllocationAnalyzerEventSource(); 8 | 9 | public void StringConcatenationAllocation(string filePath) 10 | { 11 | if (this.IsEnabled()) 12 | { 13 | this.WriteEvent(1, filePath); 14 | } 15 | } 16 | 17 | public void BoxingAllocationInStringConcatenation(string filePath) 18 | { 19 | if (this.IsEnabled()) 20 | { 21 | this.WriteEvent(2, filePath); 22 | } 23 | } 24 | 25 | public void NewInitializerExpression(string filePath) 26 | { 27 | if (this.IsEnabled()) 28 | { 29 | this.WriteEvent(3, filePath); 30 | } 31 | } 32 | 33 | public void NewImplicitArrayCreationExpression(string filePath) 34 | { 35 | if (this.IsEnabled()) 36 | { 37 | this.WriteEvent(4, filePath); 38 | } 39 | } 40 | 41 | public void NewAnonymousObjectCreationExpression(string filePath) 42 | { 43 | if (this.IsEnabled()) 44 | { 45 | this.WriteEvent(5, filePath); 46 | } 47 | } 48 | 49 | public void NewObjectCreationExpression(string filePath) 50 | { 51 | if (this.IsEnabled()) 52 | { 53 | this.WriteEvent(6, filePath); 54 | } 55 | } 56 | 57 | public void LetClauseExpression(string filePath) 58 | { 59 | if (this.IsEnabled()) 60 | { 61 | this.WriteEvent(7, filePath); 62 | } 63 | } 64 | 65 | public void ParamsAllocation(string filePath) 66 | { 67 | if (this.IsEnabled()) 68 | { 69 | this.WriteEvent(8, filePath); 70 | } 71 | } 72 | 73 | public void NonOverridenVirtualMethodCallOnValueType(string filePath) 74 | { 75 | if (this.IsEnabled()) 76 | { 77 | this.WriteEvent(9, filePath); 78 | } 79 | } 80 | 81 | public void BoxingAllocation(string filePath) 82 | { 83 | if (this.IsEnabled()) 84 | { 85 | this.WriteEvent(10, filePath); 86 | } 87 | } 88 | 89 | public void MethodGroupAllocation(string filePath) 90 | { 91 | if (this.IsEnabled()) 92 | { 93 | this.WriteEvent(11, filePath); 94 | } 95 | } 96 | 97 | public void EnumeratorAllocation(string filePath) 98 | { 99 | if (this.IsEnabled()) 100 | { 101 | this.WriteEvent(12, filePath); 102 | } 103 | } 104 | 105 | public void ClosureCapture(string filePath) 106 | { 107 | if (this.IsEnabled()) 108 | { 109 | this.WriteEvent(13, filePath); 110 | } 111 | } 112 | 113 | public void NewArrayExpression(string filePath) 114 | { 115 | if (this.IsEnabled()) 116 | { 117 | this.WriteEvent(14, filePath); 118 | } 119 | } 120 | 121 | public void ReadonlyMethodGroupAllocation(string filePath) { 122 | if (this.IsEnabled()) { 123 | this.WriteEvent(15, filePath); 124 | } 125 | } 126 | } 127 | } -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/SyntaxHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.CodeAnalysis; 2 | 3 | namespace ClrHeapAllocationAnalyzer 4 | { 5 | internal static class SyntaxHelper 6 | { 7 | public static T FindContainer(this SyntaxNode tokenParent) where T : SyntaxNode 8 | { 9 | if (tokenParent is T invocation) 10 | { 11 | return invocation; 12 | } 13 | 14 | return tokenParent.Parent == null ? null : FindContainer(tokenParent.Parent); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ClrHeapAllocationsAnalyzer/TypeConversionAllocationAnalyzer.cs: -------------------------------------------------------------------------------- 1 | namespace ClrHeapAllocationAnalyzer 2 | { 3 | using System; 4 | using System.Collections.Immutable; 5 | using System.Threading; 6 | using Microsoft.CodeAnalysis; 7 | using Microsoft.CodeAnalysis.CSharp; 8 | using Microsoft.CodeAnalysis.CSharp.Syntax; 9 | using Microsoft.CodeAnalysis.Diagnostics; 10 | 11 | 12 | [DiagnosticAnalyzer(LanguageNames.CSharp)] 13 | public sealed class TypeConversionAllocationAnalyzer : AllocationAnalyzer 14 | { 15 | public static DiagnosticDescriptor ValueTypeToReferenceTypeConversionRule = new DiagnosticDescriptor("HAA0601", "Value type to reference type conversion causing boxing allocation", "Value type to reference type conversion causes boxing at call site (here), and unboxing at the callee-site. Consider using generics if applicable", "Performance", DiagnosticSeverity.Warning, true); 16 | 17 | public static DiagnosticDescriptor DelegateOnStructInstanceRule = new DiagnosticDescriptor("HAA0602", "Delegate on struct instance caused a boxing allocation", "Struct instance method being used for delegate creation, this will result in a boxing instruction", "Performance", DiagnosticSeverity.Warning, true); 18 | 19 | public static DiagnosticDescriptor MethodGroupAllocationRule = new DiagnosticDescriptor("HAA0603", "Delegate allocation from a method group", "This will allocate a delegate instance", "Performance", DiagnosticSeverity.Warning, true); 20 | 21 | public static DiagnosticDescriptor ReadonlyMethodGroupAllocationRule = new DiagnosticDescriptor("HAA0604", "Delegate allocation from a method group", "This will allocate a delegate instance", "Performance", DiagnosticSeverity.Info, true); 22 | 23 | public override ImmutableArray SupportedDiagnostics => ImmutableArray.Create(ValueTypeToReferenceTypeConversionRule, DelegateOnStructInstanceRule, MethodGroupAllocationRule, ReadonlyMethodGroupAllocationRule); 24 | 25 | protected override SyntaxKind[] Expressions => new[] 26 | { 27 | SyntaxKind.SimpleAssignmentExpression, 28 | SyntaxKind.ReturnStatement, 29 | SyntaxKind.YieldReturnStatement, 30 | SyntaxKind.CastExpression, 31 | SyntaxKind.AsExpression, 32 | SyntaxKind.CoalesceExpression, 33 | SyntaxKind.ConditionalExpression, 34 | SyntaxKind.ForEachStatement, 35 | SyntaxKind.EqualsValueClause, 36 | SyntaxKind.Argument, 37 | SyntaxKind.ArrowExpressionClause, 38 | SyntaxKind.Interpolation 39 | }; 40 | 41 | private static readonly object[] EmptyMessageArgs = { }; 42 | 43 | protected override void AnalyzeNode(SyntaxNodeAnalysisContext context) 44 | { 45 | var node = context.Node; 46 | var semanticModel = context.SemanticModel; 47 | var cancellationToken = context.CancellationToken; 48 | Action reportDiagnostic = context.ReportDiagnostic; 49 | string filePath = node.SyntaxTree.FilePath; 50 | bool assignedToReadonlyFieldOrProperty = 51 | (context.ContainingSymbol as IFieldSymbol)?.IsReadOnly == true || 52 | (context.ContainingSymbol as IPropertySymbol)?.IsReadOnly == true; 53 | 54 | // this.fooObjCall(10); 55 | // new myobject(10); 56 | if (node is ArgumentSyntax) 57 | { 58 | ArgumentSyntaxCheck(node, semanticModel, assignedToReadonlyFieldOrProperty, reportDiagnostic, filePath, cancellationToken); 59 | } 60 | 61 | // object foo { get { return 0; } } 62 | if (node is ReturnStatementSyntax) 63 | { 64 | ReturnStatementExpressionCheck(node, semanticModel, reportDiagnostic, filePath, cancellationToken); 65 | return; 66 | } 67 | 68 | // yield return 0 69 | if (node is YieldStatementSyntax) 70 | { 71 | YieldReturnStatementExpressionCheck(node, semanticModel, reportDiagnostic, filePath, cancellationToken); 72 | return; 73 | } 74 | 75 | // object a = x ?? 0; 76 | // var a = 10 as object; 77 | if (node is BinaryExpressionSyntax) 78 | { 79 | BinaryExpressionCheck(node, semanticModel, assignedToReadonlyFieldOrProperty, reportDiagnostic, filePath, cancellationToken); 80 | return; 81 | } 82 | 83 | // for (object i = 0;;) 84 | if (node is EqualsValueClauseSyntax) 85 | { 86 | EqualsValueClauseCheck(node, semanticModel, assignedToReadonlyFieldOrProperty, reportDiagnostic, filePath, cancellationToken); 87 | return; 88 | } 89 | 90 | // object = true ? 0 : obj 91 | if (node is ConditionalExpressionSyntax) 92 | { 93 | ConditionalExpressionCheck(node, semanticModel, reportDiagnostic, filePath, cancellationToken); 94 | return; 95 | } 96 | 97 | // string a = $"{1}"; 98 | if (node is InterpolationSyntax) { 99 | InterpolationCheck(node, semanticModel, reportDiagnostic, filePath, cancellationToken); 100 | return; 101 | } 102 | 103 | // var f = (object) 104 | if (node is CastExpressionSyntax) 105 | { 106 | CastExpressionCheck(node, semanticModel, reportDiagnostic, filePath, cancellationToken); 107 | return; 108 | } 109 | 110 | // object Foo => 1 111 | if (node is ArrowExpressionClauseSyntax) 112 | { 113 | ArrowExpressionCheck(node, semanticModel, assignedToReadonlyFieldOrProperty, reportDiagnostic, filePath, cancellationToken); 114 | return; 115 | } 116 | } 117 | 118 | private static void ReturnStatementExpressionCheck(SyntaxNode node, SemanticModel semanticModel, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 119 | { 120 | var returnStatementExpression = node as ReturnStatementSyntax; 121 | if (returnStatementExpression.Expression != null) 122 | { 123 | var returnConversionInfo = semanticModel.GetConversion(returnStatementExpression.Expression, cancellationToken); 124 | CheckTypeConversion(returnConversionInfo, reportDiagnostic, returnStatementExpression.Expression.GetLocation(), filePath); 125 | } 126 | } 127 | 128 | private static void YieldReturnStatementExpressionCheck(SyntaxNode node, SemanticModel semanticModel, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 129 | { 130 | var yieldExpression = node as YieldStatementSyntax; 131 | if (yieldExpression.Expression != null) 132 | { 133 | var returnConversionInfo = semanticModel.GetConversion(yieldExpression.Expression, cancellationToken); 134 | CheckTypeConversion(returnConversionInfo, reportDiagnostic, yieldExpression.Expression.GetLocation(), filePath); 135 | } 136 | } 137 | 138 | private static void ArgumentSyntaxCheck(SyntaxNode node, SemanticModel semanticModel, bool isAssignmentToReadonly, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 139 | { 140 | var argument = node as ArgumentSyntax; 141 | if (argument.Expression != null) 142 | { 143 | var argumentTypeInfo = semanticModel.GetTypeInfo(argument.Expression, cancellationToken); 144 | var argumentConversionInfo = semanticModel.GetConversion(argument.Expression, cancellationToken); 145 | CheckTypeConversion(argumentConversionInfo, reportDiagnostic, argument.Expression.GetLocation(), filePath); 146 | CheckDelegateCreation(argument.Expression, argumentTypeInfo, semanticModel, isAssignmentToReadonly, reportDiagnostic, argument.Expression.GetLocation(), filePath, cancellationToken); 147 | } 148 | } 149 | 150 | private static void BinaryExpressionCheck(SyntaxNode node, SemanticModel semanticModel, bool isAssignmentToReadonly, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 151 | { 152 | var binaryExpression = node as BinaryExpressionSyntax; 153 | 154 | // as expression 155 | if (binaryExpression.IsKind(SyntaxKind.AsExpression) && binaryExpression.Left != null && binaryExpression.Right != null) 156 | { 157 | var leftT = semanticModel.GetTypeInfo(binaryExpression.Left, cancellationToken); 158 | var rightT = semanticModel.GetTypeInfo(binaryExpression.Right, cancellationToken); 159 | 160 | if (leftT.Type?.IsValueType == true && rightT.Type?.IsReferenceType == true) 161 | { 162 | reportDiagnostic(Diagnostic.Create(ValueTypeToReferenceTypeConversionRule, binaryExpression.Left.GetLocation(), EmptyMessageArgs)); 163 | HeapAllocationAnalyzerEventSource.Logger.BoxingAllocation(filePath); 164 | } 165 | 166 | return; 167 | } 168 | 169 | if (binaryExpression.Right != null) 170 | { 171 | var assignmentExprTypeInfo = semanticModel.GetTypeInfo(binaryExpression.Right, cancellationToken); 172 | var assignmentExprConversionInfo = semanticModel.GetConversion(binaryExpression.Right, cancellationToken); 173 | CheckTypeConversion(assignmentExprConversionInfo, reportDiagnostic, binaryExpression.Right.GetLocation(), filePath); 174 | CheckDelegateCreation(binaryExpression.Right, assignmentExprTypeInfo, semanticModel, isAssignmentToReadonly, reportDiagnostic, binaryExpression.Right.GetLocation(), filePath, cancellationToken); 175 | return; 176 | } 177 | } 178 | 179 | private static void InterpolationCheck(SyntaxNode node, SemanticModel semanticModel, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 180 | { 181 | var interpolation = node as InterpolationSyntax; 182 | var typeInfo = semanticModel.GetTypeInfo(interpolation.Expression, cancellationToken); 183 | if (typeInfo.Type?.IsValueType == true) { 184 | reportDiagnostic(Diagnostic.Create(ValueTypeToReferenceTypeConversionRule, interpolation.Expression.GetLocation(), EmptyMessageArgs)); 185 | HeapAllocationAnalyzerEventSource.Logger.BoxingAllocation(filePath); 186 | } 187 | } 188 | 189 | private static void CastExpressionCheck(SyntaxNode node, SemanticModel semanticModel, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 190 | { 191 | var castExpression = node as CastExpressionSyntax; 192 | if (castExpression.Expression != null) 193 | { 194 | var castTypeInfo = semanticModel.GetTypeInfo(castExpression, cancellationToken); 195 | var expressionTypeInfo = semanticModel.GetTypeInfo(castExpression.Expression, cancellationToken); 196 | 197 | if (castTypeInfo.Type?.IsReferenceType == true && expressionTypeInfo.Type?.IsValueType == true) 198 | { 199 | reportDiagnostic(Diagnostic.Create(ValueTypeToReferenceTypeConversionRule, castExpression.Expression.GetLocation(), EmptyMessageArgs)); 200 | } 201 | } 202 | } 203 | 204 | private static void ConditionalExpressionCheck(SyntaxNode node, SemanticModel semanticModel, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 205 | { 206 | var conditionalExpression = node as ConditionalExpressionSyntax; 207 | 208 | var trueExp = conditionalExpression.WhenTrue; 209 | var falseExp = conditionalExpression.WhenFalse; 210 | 211 | if (trueExp != null) 212 | { 213 | CheckTypeConversion(semanticModel.GetConversion(trueExp, cancellationToken), reportDiagnostic, trueExp.GetLocation(), filePath); 214 | } 215 | 216 | if (falseExp != null) 217 | { 218 | CheckTypeConversion(semanticModel.GetConversion(falseExp, cancellationToken), reportDiagnostic, falseExp.GetLocation(), filePath); 219 | } 220 | } 221 | 222 | private static void EqualsValueClauseCheck(SyntaxNode node, SemanticModel semanticModel, bool isAssignmentToReadonly, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 223 | { 224 | var initializer = node as EqualsValueClauseSyntax; 225 | if (initializer.Value != null) 226 | { 227 | var typeInfo = semanticModel.GetTypeInfo(initializer.Value, cancellationToken); 228 | var conversionInfo = semanticModel.GetConversion(initializer.Value, cancellationToken); 229 | CheckTypeConversion(conversionInfo, reportDiagnostic, initializer.Value.GetLocation(), filePath); 230 | CheckDelegateCreation(initializer.Value, typeInfo, semanticModel, isAssignmentToReadonly, reportDiagnostic, initializer.Value.GetLocation(), filePath, cancellationToken); 231 | } 232 | } 233 | 234 | 235 | private static void ArrowExpressionCheck(SyntaxNode node, SemanticModel semanticModel, bool isAssignmentToReadonly, Action reportDiagnostic, string filePath, CancellationToken cancellationToken) 236 | { 237 | var syntax = node as ArrowExpressionClauseSyntax; 238 | 239 | var typeInfo = semanticModel.GetTypeInfo(syntax.Expression, cancellationToken); 240 | var conversionInfo = semanticModel.GetConversion(syntax.Expression, cancellationToken); 241 | CheckTypeConversion(conversionInfo, reportDiagnostic, syntax.Expression.GetLocation(), filePath); 242 | CheckDelegateCreation(syntax, typeInfo, semanticModel, false, reportDiagnostic, 243 | syntax.Expression.GetLocation(), filePath, cancellationToken); 244 | } 245 | 246 | private static void CheckTypeConversion(Conversion conversionInfo, Action reportDiagnostic, Location location, string filePath) 247 | { 248 | if (conversionInfo.IsBoxing) 249 | { 250 | reportDiagnostic(Diagnostic.Create(ValueTypeToReferenceTypeConversionRule, location, EmptyMessageArgs)); 251 | HeapAllocationAnalyzerEventSource.Logger.BoxingAllocation(filePath); 252 | } 253 | } 254 | 255 | private static void CheckDelegateCreation(SyntaxNode node, TypeInfo typeInfo, SemanticModel semanticModel, bool isAssignmentToReadonly, Action reportDiagnostic, Location location, string filePath, CancellationToken cancellationToken) 256 | { 257 | // special case: method groups 258 | if (typeInfo.ConvertedType?.TypeKind == TypeKind.Delegate) 259 | { 260 | // new Action(MethodGroup); should skip this one 261 | var insideObjectCreation = node?.Parent?.Parent?.Parent?.Kind() == SyntaxKind.ObjectCreationExpression; 262 | if (node is ParenthesizedLambdaExpressionSyntax || node is SimpleLambdaExpressionSyntax || 263 | node is AnonymousMethodExpressionSyntax || node is ObjectCreationExpressionSyntax || 264 | insideObjectCreation) 265 | { 266 | // skip this, because it's intended. 267 | } 268 | else 269 | { 270 | if (node.IsKind(SyntaxKind.IdentifierName)) 271 | { 272 | if (semanticModel.GetSymbolInfo(node, cancellationToken).Symbol is IMethodSymbol) { 273 | reportDiagnostic(Diagnostic.Create(MethodGroupAllocationRule, location, EmptyMessageArgs)); 274 | HeapAllocationAnalyzerEventSource.Logger.MethodGroupAllocation(filePath); 275 | } 276 | } 277 | else if (node.IsKind(SyntaxKind.SimpleMemberAccessExpression)) 278 | { 279 | var memberAccess = node as MemberAccessExpressionSyntax; 280 | if (semanticModel.GetSymbolInfo(memberAccess.Name, cancellationToken).Symbol is IMethodSymbol) 281 | { 282 | if (isAssignmentToReadonly) 283 | { 284 | reportDiagnostic(Diagnostic.Create(ReadonlyMethodGroupAllocationRule, location, EmptyMessageArgs)); 285 | HeapAllocationAnalyzerEventSource.Logger.ReadonlyMethodGroupAllocation(filePath); 286 | } 287 | else 288 | { 289 | reportDiagnostic(Diagnostic.Create(MethodGroupAllocationRule, location, EmptyMessageArgs)); 290 | HeapAllocationAnalyzerEventSource.Logger.MethodGroupAllocation(filePath); 291 | } 292 | } 293 | } 294 | else if (node is ArrowExpressionClauseSyntax) 295 | { 296 | var arrowClause = node as ArrowExpressionClauseSyntax; 297 | if (semanticModel.GetSymbolInfo(arrowClause.Expression, cancellationToken).Symbol is IMethodSymbol) { 298 | reportDiagnostic(Diagnostic.Create(MethodGroupAllocationRule, location, EmptyMessageArgs)); 299 | HeapAllocationAnalyzerEventSource.Logger.MethodGroupAllocation(filePath); 300 | } 301 | } 302 | } 303 | 304 | if (IsStructInstanceMethod(node, semanticModel, cancellationToken)) 305 | { 306 | reportDiagnostic(Diagnostic.Create(DelegateOnStructInstanceRule, location, EmptyMessageArgs)); 307 | } 308 | } 309 | } 310 | 311 | private static bool IsStructInstanceMethod(SyntaxNode node, SemanticModel semanticModel, CancellationToken cancellationToken) 312 | { 313 | var nodeKind = node.Kind(); 314 | if (nodeKind == SyntaxKind.AnonymousMethodExpression || nodeKind == SyntaxKind.ParenthesizedLambdaExpression) 315 | { 316 | return false; 317 | } 318 | 319 | var symbolInfo = semanticModel.GetSymbolInfo(node, cancellationToken).Symbol; 320 | return symbolInfo != null && symbolInfo.Kind == SymbolKind.Method && symbolInfo.IsStatic == false && symbolInfo.ContainingType?.IsValueType == true; 321 | } 322 | } 323 | } 324 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **NOTE** This project is no longer maintained. The high impact analyzers from this repository are being merged with dotnet/roslyn-analyzers. As such, this repo was archived 2 | 3 | Roslyn Clr Heap Allocation Analyzer 4 | =================================== 5 | 6 | [![Join the chat at https://gitter.im/mjsabby/RoslynClrHeapAllocationAnalyzer](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mjsabby/RoslynClrHeapAllocationAnalyzer?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) 7 | 8 | Quick Video: https://www.youtube.com/watch?v=Tw-wgT-cXYU&hd=1 9 | 10 | Download: **https://github.com/Microsoft/RoslynClrHeapAllocationAnalyzer/releases** 11 | 12 | Roslyn based C# heap allocation diagnostic analyzer that can detect explicit and many implicit allocations like boxing, display classes a.k.a closures, implicit delegate creations, etc. 13 | 14 | You can find also it on the Visual Studio Gallery, https://aka.ms/HeapAllocationAnalyzer 15 | 16 | If you want it use it in your build: https://www.nuget.org/packages/ClrHeapAllocationAnalyzer/ 17 | 18 | ![example](https://cloud.githubusercontent.com/assets/1930559/4606581/2a027d08-5225-11e4-8d4e-686c204a1267.png) 19 | 20 | ## Microsoft Open Source Code of Conduct 21 | 22 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 23 | --------------------------------------------------------------------------------