Skip to content

Commit e753413

Browse files
committed
Memory and performance
Added our own reference detection and resolution. Started removing projects form the in memory solution once we process them. Added links from the solution to the projects. Added links between partial classes.
1 parent 6803bd6 commit e753413

6 files changed

Lines changed: 164 additions & 75 deletions

File tree

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)