11using Microsoft . CodeAnalysis . MSBuild ;
22using Microsoft . CodeAnalysis ;
3- using Microsoft . CodeAnalysis . CSharp ;
4- using Microsoft . CodeAnalysis . FindSymbols ;
53using System . Diagnostics ;
64using System . Runtime . InteropServices ;
75using 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