1
- using CodeParser . Analysis . Cycles ;
1
+ using System . Diagnostics ;
2
+ using CodeParser . Analysis . Cycles ;
2
3
using Contracts . Graph ;
3
4
4
5
namespace CSharpCodeAnalyst . Exploration ;
@@ -29,8 +30,8 @@ public List<CodeElement> GetElements(List<string> ids)
29
30
foreach ( var id in ids )
30
31
{
31
32
if ( _codeGraph . Nodes . TryGetValue ( id , out var element ) )
32
- {
33
33
// The element is cloned internally and the relationships discarded.
34
+ {
34
35
elements . Add ( element ) ;
35
36
}
36
37
}
@@ -172,7 +173,11 @@ public Invocation FindIncomingCallsRecursive(string id)
172
173
return new Invocation ( foundMethods , foundCalls ) ;
173
174
}
174
175
175
-
176
+ /// <summary>
177
+ /// This gives a heuristic only. The graph model does not contain the information for analyzing dynamic behavior.
178
+ /// For example, it makes a difference if we call Foo() in an object instance or otherInstance.Foo().
179
+ /// The first case should not follow base.Foo() calls the second however should.
180
+ /// </summary>
176
181
public SearchResult FollowIncomingCallsRecursive ( string id )
177
182
{
178
183
ArgumentNullException . ThrowIfNull ( id ) ;
@@ -191,12 +196,16 @@ public SearchResult FollowIncomingCallsRecursive(string id)
191
196
192
197
var method = _codeGraph . Nodes [ id ] ;
193
198
199
+ var restriction = new FollowIncomingCallsRestriction ( ) ;
194
200
var processingQueue = new Queue < CodeElement > ( ) ;
195
201
processingQueue . Enqueue ( method ) ;
196
202
197
203
var foundRelationships = new HashSet < Relationship > ( ) ;
198
204
var foundElements = new HashSet < CodeElement > ( ) ;
199
205
206
+ // For convenience. The element is already in the graph. But this way the result is consistent.
207
+ foundElements . Add ( _codeGraph . Nodes [ id ] ) ;
208
+
200
209
201
210
var processed = new HashSet < string > ( ) ;
202
211
while ( processingQueue . Any ( ) )
@@ -207,6 +216,7 @@ public SearchResult FollowIncomingCallsRecursive(string id)
207
216
continue ;
208
217
}
209
218
219
+
210
220
if ( element . ElementType == CodeElementType . Event )
211
221
{
212
222
// An event is raised by the specialization
@@ -215,46 +225,43 @@ public SearchResult FollowIncomingCallsRecursive(string id)
215
225
var specializedSources = specializations . Select ( d => _codeGraph . Nodes [ d . SourceId ] ) . ToHashSet ( ) ;
216
226
foundElements . UnionWith ( specializedSources ) ;
217
227
AddToProcessingQueue ( specializedSources ) ;
218
- }
219
228
220
- // Add all methods that invoke the event
221
- if ( element . ElementType == CodeElementType . Event )
222
- {
229
+ // Add all methods that invoke the event
223
230
var invokes = allInvokes . Where ( call => call . TargetId == element . Id ) . ToArray ( ) ;
224
231
foundRelationships . UnionWith ( invokes ) ;
225
232
var invokeSources = invokes . Select ( d => _codeGraph . Nodes [ d . SourceId ] ) . ToHashSet ( ) ;
226
233
foundElements . UnionWith ( invokeSources ) ;
227
234
AddToProcessingQueue ( invokeSources ) ;
228
235
}
229
236
230
- // Add Events that are handled by this method.
231
237
if ( element . ElementType == CodeElementType . Method )
232
238
{
239
+ // Add Events that are handled by this method.
233
240
var handles = allHandles . Where ( h => h . SourceId == element . Id ) . ToArray ( ) ;
234
241
foundRelationships . UnionWith ( handles ) ;
235
242
var events = handles . Select ( h => _codeGraph . Nodes [ h . TargetId ] ) . ToHashSet ( ) ;
236
243
foundElements . UnionWith ( events ) ;
237
244
AddToProcessingQueue ( events ) ;
238
- }
239
245
240
- // Calls
241
- if ( element . ElementType == CodeElementType . Method )
242
- {
243
- var calls = allCalls . Where ( call => call . TargetId == element . Id ) . ToArray ( ) ;
244
- foundRelationships . UnionWith ( calls ) ;
245
- var callSources = calls . Select ( d => _codeGraph . Nodes [ d . SourceId ] ) . ToHashSet ( ) ;
246
- foundElements . UnionWith ( callSources ) ;
247
- AddToProcessingQueue ( callSources ) ;
248
- }
249
246
250
- // Abstractions. For methods the abstractions like interfaces are called.
251
- if ( element . ElementType == CodeElementType . Method )
252
- {
247
+ // Handle abstractions before the calls. The abstractions limit the allowed calls.
248
+ // Abstractions. For methods the abstractions like interfaces are called.
253
249
var abstractions = allImplementsAndOverrides . Where ( d => d . SourceId == element . Id ) . ToArray ( ) ;
254
250
foundRelationships . UnionWith ( abstractions ) ;
255
251
var abstractionTargets = abstractions . Select ( d => _codeGraph . Nodes [ d . TargetId ] ) . ToHashSet ( ) ;
256
252
foundElements . UnionWith ( abstractionTargets ) ;
253
+ restriction . WasAddedBecauseItsAnAbstraction . UnionWith ( abstractionTargets . Select ( t => t . Id ) ) ;
257
254
AddToProcessingQueue ( abstractionTargets ) ;
255
+
256
+
257
+ // Calls
258
+ var calls = allCalls . Where ( call => call . TargetId == element . Id && IsAllowedCall ( call ) ) . ToArray ( ) ;
259
+ foundRelationships . UnionWith ( calls ) ;
260
+ var callSources = calls
261
+ . Select ( d => _codeGraph . Nodes [ d . SourceId ] )
262
+ . ToHashSet ( ) ;
263
+ foundElements . UnionWith ( callSources ) ;
264
+ AddToProcessingQueue ( callSources ) ;
258
265
}
259
266
}
260
267
@@ -267,6 +274,24 @@ void AddToProcessingQueue(IEnumerable<CodeElement> elementsToExplore)
267
274
processingQueue . Enqueue ( _codeGraph . Nodes [ elementToExplore . Id ] ) ;
268
275
}
269
276
}
277
+
278
+ bool IsAllowedCall ( Relationship call )
279
+ {
280
+ if ( restriction . WasAddedBecauseItsAnAbstraction . Contains ( call . TargetId ) )
281
+ {
282
+ var allow = ! IsCallToOwnBase ( call ) ;
283
+ if ( ! allow )
284
+ {
285
+ var sourceName = _codeGraph . Nodes [ call . SourceId ] . FullName ;
286
+ var targetName = _codeGraph . Nodes [ call . TargetId ] . FullName ;
287
+ Trace . WriteLine ( $ "Removed: { sourceName } -> { targetName } ") ;
288
+ }
289
+
290
+ return allow ;
291
+ }
292
+
293
+ return true ;
294
+ }
270
295
}
271
296
272
297
/// <summary>
@@ -322,8 +347,8 @@ public SearchResult FindFullInheritanceTree(string id)
322
347
{
323
348
var typeToAnalyze = processingQueue . Dequeue ( ) ;
324
349
if ( ! processed . Add ( typeToAnalyze . Id ) )
325
- {
326
350
// Since we evaluate both direction in one iteration, an already processed node is added again.
351
+ {
327
352
continue ;
328
353
}
329
354
@@ -438,6 +463,19 @@ public SearchResult FindIncomingRelationships(string id)
438
463
return new SearchResult ( elements , relationships ) ;
439
464
}
440
465
466
+
467
+ /// <summary>
468
+ /// source --> target (abstract)
469
+ /// </summary>
470
+ private bool IsCallToOwnBase ( Relationship call )
471
+ {
472
+ // Is target more abstract than source?
473
+ // target (abstract) <-- source
474
+ var isCallToBaseClass = GetRelationships ( d => d . Type is RelationshipType . Overrides )
475
+ . Any ( r => r . SourceId == call . SourceId && r . TargetId == call . TargetId ) ;
476
+ return isCallToBaseClass ;
477
+ }
478
+
441
479
private List < Relationship > GetCachedRelationships ( )
442
480
{
443
481
if ( _codeGraph is null )
@@ -485,6 +523,16 @@ void Collect(CodeElement c)
485
523
}
486
524
}
487
525
}
526
+
527
+ private class FollowIncomingCallsRestriction
528
+ {
529
+ /// <summary>
530
+ /// If we follow incoming calls we include the abstraction of the method.
531
+ /// This is for example because the method may be indirectly called by the interface.
532
+ /// If we proceed we may also find calls the base. But this is the wrong direction for the path we follow.
533
+ /// </summary>
534
+ public HashSet < string > WasAddedBecauseItsAnAbstraction { get ; } = [ ] ;
535
+ }
488
536
}
489
537
490
538
public record struct SearchResult ( IEnumerable < CodeElement > Elements , IEnumerable < Relationship > Relationships ) ;
0 commit comments