Skip to content

Commit 4f9317d

Browse files
authored
Merge pull request #19 from Codesee-io/memory-and-performance-improvements
Memory and performance improvements
2 parents 6803bd6 + e753413 commit 4f9317d

File tree

6 files changed

+164
-75
lines changed

6 files changed

+164
-75
lines changed

DotNETDepends/Dependencies.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,9 @@ public DependencyEntry CreateProjectEntry(string filePath)
2525
return dependency;
2626
}
2727

28-
public DependencyEntry CreateCodeFileEntry(string filePath, SemanticModel semantic, SyntaxTree tree)
28+
public DependencyEntry CreateCodeFileEntry(string filePath)
2929
{
30-
var dependency = new DependencyEntry(filePath, EntryType.File)
31-
{
32-
Semantic = semantic,
33-
Tree = tree
34-
};
30+
var dependency = new DependencyEntry(filePath, EntryType.File);
3531
entries[filePath] = dependency;
3632
return dependency;
3733
}

DotNETDepends/DependencyEntry.cs

Lines changed: 21 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,4 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
6-
using Microsoft.CodeAnalysis;
1+
using Microsoft.CodeAnalysis;
72

83
namespace DotNETDepends
94
{
@@ -21,28 +16,41 @@ class DependencyEntry
2116
public readonly String FilePath;
2217
public readonly EntryType Type;
2318
public HashSet<ISymbol> Symbols = new(SymbolEqualityComparer.Default);
24-
//This is the Roslyn semantic model. Only valid if Type == File.
25-
public SemanticModel? Semantic { get; set; }
26-
//SyntaxTree of a C# or VB file. Comes from Roslyn.
27-
public SyntaxTree? Tree { get; set; }
19+
private readonly HashSet<string> References = new();
20+
2821
//File dependencies of the file
2922
public HashSet<string> Dependencies { get; } = new HashSet<string>();
3023

3124
public DependencyEntry(string filePath, EntryType type)
3225
{
3326
FilePath = filePath;
3427
Type = type;
35-
28+
29+
}
30+
31+
public void AddReference(ISymbol symbol)
32+
{
33+
//ToDisplayString formats the symbol as <namespace>.<typeName>
34+
//stringifying this helps with the lookup, as we can't use a
35+
//HashSet<ISymbol>::Contains to look them up
36+
References.Add(symbol.ToDisplayString());
37+
}
38+
39+
public bool ReferencesSymbol(ISymbol symbol)
40+
{
41+
//ToDisplayString formats the symbol as <namespace>.<typeName>
42+
var synName = symbol.ToDisplayString();
43+
return References.Contains(synName);
3644
}
3745

3846
public void AddDependency(string dependency)
3947
{
40-
if(dependency != FilePath)
48+
if (dependency != FilePath)
4149
{
4250
Dependencies.Add(dependency);
4351
}
4452
}
4553

46-
54+
4755
}
4856
}

DotNETDepends/Properties/launchSettings.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"profiles": {
33
"DotNETDepends": {
44
"commandName": "Project",
5-
"commandLineArgs": "\"C:\\Users\\Marcus\\projects\\With Space\\NetCoreMVC\\NetCoreMVC.sln\""
5+
"commandLineArgs": "\"C:\\Users\\Marcus\\projects\\AspNetWebStack\\Runtime.sln\""
66
}
77
}
88
}

DotNETDepends/RoslynProject.cs

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
11
using Disassembler;
22
using Microsoft.CodeAnalysis;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading.Tasks;
83

94
namespace DotNETDepends
105
{
@@ -14,22 +9,46 @@ namespace DotNETDepends
149
internal class SymbolDefinitionFinder : SyntaxWalker
1510
{
1611
private readonly DependencyEntry entry;
17-
public SymbolDefinitionFinder(DependencyEntry entry) : base(SyntaxWalkerDepth.Node)
12+
private readonly SemanticModel semanticModel;
13+
public SymbolDefinitionFinder(DependencyEntry entry, SemanticModel semanticModel) : base(SyntaxWalkerDepth.Node)
1814
{
1915
this.entry = entry;
16+
this.semanticModel = semanticModel;
2017
}
2118

2219
public override void Visit(SyntaxNode? node)
2320
{
2421
if (node != null)
2522
{
26-
var symbol = entry.Semantic?.GetDeclaredSymbol(node);
27-
//Look for NamedTypes. These are classes.
23+
var symbol = semanticModel.GetDeclaredSymbol(node);
24+
25+
//Look for NamedTypes. These are classes defined in the file
2826
if (symbol != null && symbol.Kind == SymbolKind.NamedType)
2927
{
3028
entry.Symbols.Add(symbol);
29+
30+
}
31+
else
32+
{
33+
//Look for references
34+
var info = semanticModel.GetSymbolInfo(node);
35+
36+
if (info.Symbol != null)
37+
{
38+
var sym = info.Symbol;
39+
if (sym.Kind != SymbolKind.NamedType)
40+
{
41+
var type = sym.ContainingType;
42+
if (type != null)
43+
{
44+
entry.AddReference(type);
45+
}
46+
}
47+
else { entry.AddReference(sym); }
48+
49+
}
50+
3151
}
32-
//We can optimize this in the future to not descend in to uninteresting nodes
3352
base.Visit(node);
3453
}
3554
}
@@ -56,18 +75,16 @@ public async Task Analyze()
5675
var compilation = await project.GetCompilationAsync().ConfigureAwait(false);
5776
if (compilation != null && solutionRoot != null)
5877
{
59-
6078
foreach (var tree in compilation.SyntaxTrees)
6179
{
62-
6380
//Make sure we don't add generated files that can get pulled in.
6481
if (tree.FilePath.StartsWith(solutionRoot))
6582
{
6683
//Register the file/type
67-
var fileDep = dependencies.CreateCodeFileEntry(Path.GetRelativePath(solutionRoot, tree.FilePath), compilation.GetSemanticModel(tree), tree);
68-
var walker = new SymbolDefinitionFinder(fileDep);
84+
var fileDep = dependencies.CreateCodeFileEntry(Path.GetRelativePath(solutionRoot, tree.FilePath));
85+
var walker = new SymbolDefinitionFinder(fileDep, compilation.GetSemanticModel(tree));
6986
//walk the syntaxtree to find referencable symbols
70-
walker.Visit(fileDep.Tree?.GetRoot());
87+
walker.Visit(tree.GetRoot());
7188
}
7289
}
7390
}

DotNETDepends/SolutionReader.cs

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
using Microsoft.CodeAnalysis.MSBuild;
22
using Microsoft.CodeAnalysis;
3-
using Microsoft.CodeAnalysis.CSharp;
4-
using Microsoft.CodeAnalysis.FindSymbols;
53
using System.Diagnostics;
64
using System.Runtime.InteropServices;
75
using Disassembler;
@@ -38,24 +36,74 @@ public async Task ReadSolutionAsync(String path, AnalysisOutput output)
3836
Console.WriteLine("Reading solution: " + path);
3937
var workspace = MSBuildWorkspace.Create();
4038
var solution = await workspace.OpenSolutionAsync(path).ConfigureAwait(false);
41-
39+
4240
RestoreSolution(solution, output);
4341

4442
var depGraph = solution.GetProjectDependencyGraph();
43+
44+
CreateProjectDependencies(solution, depGraph);
45+
4546
var dependencyOrderedProjects = depGraph.GetTopologicallySortedProjects();
46-
//Read all of the projects declared types and references (if ASP.NET)
47+
48+
foreach (var projectId in dependencyOrderedProjects)
49+
{
50+
var project = solution.GetProject(projectId);
51+
if (project != null)
52+
{
53+
await project.GetCompilationAsync().ConfigureAwait(false);
54+
}
55+
}
4756
foreach (var projectId in dependencyOrderedProjects)
4857
{
49-
await ProcessProjectAsync(projectId, solution, depGraph, output).ConfigureAwait(false);
58+
await ProcessProjectAsync(projectId, solution, output).ConfigureAwait(false);
59+
//once we process the project, remove it so that all of the compilation, ASTs, etc
60+
//can be garbage collected. This results in a new solution
61+
solution = solution.RemoveProject(projectId);
5062
}
51-
await ResolveDepenedenciesAsync(solution).ConfigureAwait(false);
63+
64+
65+
ResolveDependencies(solution);
5266
dependencies.GetLinks(output);
5367
}
5468

5569
/**
56-
* Processes all of the declared and referenced symbols we collected
70+
* Creates the dependency graph of the solution and all it's projects. Has to be done
71+
* before we start removing projects from the solution
5772
*/
58-
private async Task ResolveDepenedenciesAsync(Solution solution)
73+
private void CreateProjectDependencies(Solution solution, ProjectDependencyGraph depGraph)
74+
{
75+
var solutionDir = Path.GetDirectoryName(solution.FilePath);
76+
77+
if (solution.FilePath != null && solutionDir != null)
78+
{
79+
DependencyEntry solutionEntry = dependencies.CreateProjectEntry(Path.GetFileName(solution.FilePath));
80+
81+
foreach (var project in solution.Projects)
82+
{
83+
if (project.FilePath != null)
84+
{
85+
var projectRelativePath = Path.GetRelativePath(solutionDir, project.FilePath);
86+
solutionEntry.AddDependency(projectRelativePath);
87+
DependencyEntry projectEntry = dependencies.CreateProjectEntry(projectRelativePath);
88+
var depProjects = depGraph.GetProjectsThatDirectlyDependOnThisProject(project.Id);
89+
foreach (var depProject in depProjects)
90+
{
91+
var resolved = solution.GetProject(depProject);
92+
if (resolved != null && resolved.FilePath != null)
93+
{
94+
projectEntry.AddDependency(Path.GetRelativePath(solutionDir, resolved.FilePath));
95+
}
96+
}
97+
}
98+
99+
}
100+
}
101+
}
102+
103+
/**
104+
* Processes all of the declared and referenced symbols we collected
105+
*/
106+
private void ResolveDependencies(Solution solution)
59107
{
60108
var solutionRoot = Path.GetDirectoryName(solution.FilePath);
61109
if (solutionRoot != null)
@@ -64,7 +112,9 @@ private async Task ResolveDepenedenciesAsync(Solution solution)
64112
* These entries come from Roslyn. They are the declared types in
65113
* each file we walked.
66114
*/
67-
foreach (var entry in dependencies.GetFileEntries())
115+
var fileEntries = dependencies.GetFileEntries();
116+
Console.WriteLine("Resolving references for " + fileEntries.Count + " files.");
117+
foreach (var entry in fileEntries)
68118
{
69119

70120
foreach (var symbol in entry.Symbols)
@@ -73,23 +123,17 @@ private async Task ResolveDepenedenciesAsync(Solution solution)
73123
* For every symbol declaration we found, find all references in the solution to that symbol.
74124
* This will only cover C# abd VB source files.
75125
*/
76-
var references = await SymbolFinder.FindReferencesAsync(symbol, solution).ConfigureAwait(false);
77-
78-
foreach (var reference in references)
126+
foreach (var otherEntry in dependencies.GetFileEntries())
79127
{
80-
foreach (var location in reference.Locations)
128+
if (otherEntry != entry)
81129
{
82-
//CandidateLocations are guesses by Roslyn.
83-
//Also filter anything we don't have source for
84-
if (location.Location.IsInSource)
130+
if (otherEntry.ReferencesSymbol(symbol))
85131
{
86-
//Record the reference
87-
var path = location.Location.SourceTree.FilePath;
88-
var sourceEntry = dependencies.GetEntry(Path.GetRelativePath(solutionRoot, path));
89-
sourceEntry?.AddDependency(entry.FilePath);
132+
otherEntry.AddDependency(entry.FilePath);
90133
}
91134
}
92135
}
136+
93137
/**
94138
* Now look for references in the ASP.NET file we disassembled.
95139
*/
@@ -100,6 +144,7 @@ private async Task ResolveDepenedenciesAsync(Solution solution)
100144
}
101145

102146
}
147+
entry.Symbols.Clear();
103148
}
104149
/**
105150
* Find dependencies within all of the decompiled source types
@@ -117,11 +162,12 @@ private async Task ResolveDepenedenciesAsync(Solution solution)
117162
}
118163
}
119164

165+
120166
/**
121167
* Runs dotnet restore on the solution. This fetches any nuget dependencies
122168
* so that when we compile with Roslyn or decompile the assembly they are available.
123169
*/
124-
private bool RestoreSolution(Solution solution, AnalysisOutput analysisOutput)
170+
private static bool RestoreSolution(Solution solution, AnalysisOutput analysisOutput)
125171
{
126172
var dotnetPath = SDKTools.GetDotnetPath();
127173
if (solution.FilePath != null)
@@ -245,7 +291,7 @@ private static bool ProjectContainsNETWebFiles(Project project)
245291
* It then uses Roslyn to parse the references of any C# or VB files, regardless of
246292
* whether or not it is an ASP.NET project.
247293
*/
248-
private async Task ProcessProjectAsync(ProjectId projectId, Solution solution, ProjectDependencyGraph depGraph, AnalysisOutput analysisOutput)
294+
private async Task ProcessProjectAsync(ProjectId projectId, Solution solution, AnalysisOutput analysisOutput)
249295
{
250296

251297
var project = solution.GetProject(projectId);
@@ -254,19 +300,7 @@ private async Task ProcessProjectAsync(ProjectId projectId, Solution solution, P
254300
if (project != null && project.FilePath != null && solutionRoot != null)
255301
{
256302
Console.WriteLine("Processing project: " + project.FilePath);
257-
var depEntry = dependencies.CreateProjectEntry(Path.GetRelativePath(solutionRoot, project.FilePath));
258303

259-
var projectDependencies = depGraph.GetProjectsThatThisProjectDirectlyDependsOn(projectId);
260-
//Add all dependent projects to the entry
261-
foreach (var depId in projectDependencies)
262-
{
263-
var depProject = solution.GetProject(depId);
264-
if (depProject != null && depProject.FilePath != null)
265-
{
266-
depEntry.AddDependency(depProject.FilePath);
267-
}
268-
}
269-
270304
//Check for ASP.NET files
271305
if (ProjectContainsNETWebFiles(project))
272306
{
@@ -296,6 +330,7 @@ private async Task ProcessProjectAsync(ProjectId projectId, Solution solution, P
296330

297331
}
298332
}
333+
299334
}
300335
}
301336
}

0 commit comments

Comments
 (0)