4
4
5
5
namespace CSharpCodeAnalyst . Exploration ;
6
6
7
- internal class Context
7
+ internal class Context ( CodeGraph _codeGraph )
8
8
{
9
- public HashSet < string > Remember { get ; } = [ ] ;
9
+ public HashSet < CodeElement > ForbiddenCallSourcesInHierarchy { get ; set ; } = [ ] ;
10
10
11
- public HashSet < string > BlockedAbstraction { get ; } = [ ] ;
11
+ public bool IsCallAllowed ( Relationship call )
12
+ {
13
+ if ( ForbiddenCallSourcesInHierarchy . Count == 0 )
14
+ {
15
+ return true ; // No restrictions, all calls are allowed
16
+ }
17
+
18
+ if ( call . Attributes == RelationshipAttribute . None )
19
+ {
20
+ // Normal calls not a call to an object instance.
21
+ var sourceMethod = _codeGraph . Nodes [ call . SourceId ] ;
22
+ var sourceClass = CodeGraphExplorer . GetMethodContainer ( sourceMethod ) ;
23
+
24
+ if ( ForbiddenCallSourcesInHierarchy . Contains ( sourceClass ) )
25
+ {
26
+ return false ;
27
+ }
28
+ }
29
+
30
+ return true ;
31
+ }
12
32
13
33
public Context Clone ( )
14
34
{
15
- var clone = new Context ( ) ;
35
+ var clone = new Context ( _codeGraph )
36
+ {
37
+ ForbiddenCallSourcesInHierarchy = ForbiddenCallSourcesInHierarchy . ToHashSet ( )
38
+ } ;
16
39
return clone ;
17
40
}
18
41
}
@@ -187,10 +210,131 @@ public Invocation FindIncomingCallsRecursive(string id)
187
210
}
188
211
189
212
/// <summary>
190
- /// This gives a heuristic only. The graph model does not contain the information for analyzing dynamic behavior.
213
+ /// Returns all allowed call sources within the hierarchy.
214
+ /// </summary>
215
+ private HashSet < CodeElement > RestrictHierarchyCallSources ( CodeElement baseCallSource )
216
+ {
217
+ if ( _codeGraph is null )
218
+ {
219
+ return [ ] ;
220
+ }
221
+
222
+ var element = GetMethodContainer ( baseCallSource ) ;
223
+ if ( element is null )
224
+ {
225
+ return [ ] ;
226
+ }
227
+
228
+
229
+
230
+ var allowedHierarchy = GetBaseClassesRecursive ( element ) . Union ( GetDerivedClassesRecursive ( element ) ) . ToHashSet ( ) ;
231
+
232
+ var result = FindFullInheritanceTree ( element . Id ) ;
233
+ var forbiddenHierarchy = result . Elements . Except ( allowedHierarchy ) . ToHashSet ( ) ;
234
+ return forbiddenHierarchy ;
235
+
236
+ }
237
+
238
+
239
+
240
+ /// <summary>
241
+ /// The given element is not included in the result.
242
+ /// </summary>
243
+ private HashSet < CodeElement > GetBaseClassesRecursive ( CodeElement ? element )
244
+ {
245
+ if ( _codeGraph is null )
246
+ {
247
+ return [ ] ;
248
+ }
249
+
250
+ var baseClasses = new HashSet < CodeElement > ( ) ;
251
+
252
+ var queue = new Queue < CodeElement > ( ) ;
253
+ queue . Enqueue ( element ) ;
254
+
255
+ while ( queue . Any ( ) )
256
+ {
257
+ var currentElement = queue . Dequeue ( ) ;
258
+
259
+ var inheritsFrom = currentElement . Relationships
260
+ . Where ( d => d . Type == RelationshipType . Inherits && d . SourceId == element . Id )
261
+ . Select ( m => _codeGraph . Nodes [ m . TargetId ] ) . ToList ( ) ;
262
+
263
+ Debug . Assert ( inheritsFrom . Count <= 1 , "Only simple inheritance in C#" ) ;
264
+
265
+ foreach ( var baseClass in inheritsFrom )
266
+ {
267
+ if ( baseClasses . Add ( baseClass ) )
268
+ {
269
+ queue . Enqueue ( baseClass ) ;
270
+ }
271
+ }
272
+ }
273
+
274
+ return baseClasses ;
275
+ }
276
+
277
+ public static CodeElement ? GetMethodContainer ( CodeElement element )
278
+ {
279
+ while ( element . ElementType != CodeElementType . Class && element . ElementType != CodeElementType . Struct )
280
+ {
281
+ if ( element . Parent is null )
282
+ {
283
+ return null ; // No container found
284
+ }
285
+
286
+ element = element . Parent ;
287
+ }
288
+
289
+ return element ;
290
+ }
291
+
292
+
293
+ private HashSet < CodeElement > GetDerivedClassesRecursive ( CodeElement element )
294
+ {
295
+ if ( _codeGraph is null )
296
+ {
297
+ return [ ] ;
298
+ }
299
+
300
+ var derivedClasses = new HashSet < CodeElement > ( ) ;
301
+
302
+ var queue = new Queue < CodeElement > ( ) ;
303
+ queue . Enqueue ( element ) ;
304
+
305
+ while ( queue . Any ( ) )
306
+ {
307
+ var currentElement = queue . Dequeue ( ) ;
308
+
309
+ var derived = GetRelationships ( d => d . Type == RelationshipType . Inherits && d . TargetId == element . Id )
310
+ . Select ( m => _codeGraph . Nodes [ m . SourceId ] ) . ToList ( ) ;
311
+
312
+ foreach ( var baseClass in derived )
313
+ {
314
+ if ( derivedClasses . Add ( baseClass ) )
315
+ {
316
+ queue . Enqueue ( baseClass ) ;
317
+ }
318
+ }
319
+ }
320
+
321
+ return derivedClasses ;
322
+ }
323
+
324
+
325
+ /// <summary>
326
+ /// This gives a heuristic only.
327
+ /// The graph model does not contain the information for analyzing dynamic behavior.
191
328
/// </summary>
192
329
public SearchResult FollowIncomingCallsHeuristically ( string id )
193
330
{
331
+ // base call.
332
+ // I know the hierarchy
333
+ // lock all allowed calls
334
+ // - Get straight hierarchy
335
+ // rest calls when instance call is reached.
336
+
337
+
194
338
ArgumentNullException . ThrowIfNull ( id ) ;
195
339
196
340
if ( _codeGraph is null )
@@ -208,7 +352,7 @@ public SearchResult FollowIncomingCallsHeuristically(string id)
208
352
var method = _codeGraph . Nodes [ id ] ;
209
353
210
354
var processingQueue = new PriorityQueue < ( CodeElement , Context ) , int > ( ) ;
211
- processingQueue . Enqueue ( ( method , new Context ( ) ) , 0 ) ; // Start with the initial method, priority 0
355
+ processingQueue . Enqueue ( ( method , new Context ( _codeGraph ) ) , 0 ) ; // Start with the initial method, priority 0
212
356
213
357
var foundRelationships = new HashSet < Relationship > ( ) ;
214
358
var foundElements = new HashSet < CodeElement > ( ) ;
@@ -228,7 +372,7 @@ public SearchResult FollowIncomingCallsHeuristically(string id)
228
372
continue ;
229
373
}
230
374
231
- var currentContext = context . Clone ( ) ;
375
+ var currentContext = context ;
232
376
233
377
if ( element . ElementType == CodeElementType . Event )
234
378
{
@@ -249,32 +393,55 @@ public SearchResult FollowIncomingCallsHeuristically(string id)
249
393
250
394
if ( element . ElementType == CodeElementType . Method )
251
395
{
396
+
397
+
398
+
399
+ // 3. Calls (priority 2)
400
+ var calls = allCalls . Where ( call => call . TargetId == element . Id && currentContext . IsCallAllowed ( call ) ) . ToArray ( ) ;
401
+ foundRelationships . UnionWith ( calls ) ;
402
+
403
+ // We may restrict further paths
404
+ foreach ( var call in calls )
405
+ {
406
+ var callSource = _codeGraph . Nodes [ call . SourceId ] ;
407
+ foundElements . Add ( callSource ) ;
408
+
409
+ if ( call . HasAttribute ( RelationshipAttribute . IsInstanceCall ) ||
410
+ call . HasAttribute ( RelationshipAttribute . IsStaticCall ) ||
411
+ call . HasAttribute ( RelationshipAttribute . IsExtensionMethodCall ) )
412
+
413
+ {
414
+ // Starting on a new instance or static method we reset the context.
415
+ currentContext = new Context ( _codeGraph ) ;
416
+ }
417
+
418
+ if ( call . Attributes == RelationshipAttribute . IsBaseCall )
419
+ {
420
+ // Call to own base class restricts the possible further calls.
421
+ currentContext = currentContext . Clone ( ) ;
422
+ currentContext . ForbiddenCallSourcesInHierarchy . UnionWith ( RestrictHierarchyCallSources ( callSource ) ) ;
423
+ }
424
+
425
+ AddToProcessingQueue ( [ callSource ] , currentContext , 2 ) ;
426
+ }
427
+
252
428
// 1. Abstractions (priority 0)
253
- // The abstractions limit the allowed calls.
254
429
// For methods the abstractions like interfaces may be called.
255
430
var abstractions = allImplementsAndOverrides . Where ( d => d . SourceId == element . Id ) . ToArray ( ) ;
256
431
foundRelationships . UnionWith ( abstractions ) ;
257
432
var abstractionTargets = abstractions . Select ( d => _codeGraph . Nodes [ d . TargetId ] ) . ToHashSet ( ) ;
258
433
foundElements . UnionWith ( abstractionTargets ) ;
259
- AddToProcessingQueue ( abstractionTargets , currentContext , 0 ) ;
434
+ AddToProcessingQueue ( abstractionTargets , currentContext , 2 ) ;
260
435
261
436
262
437
// Add Events that are handled by this method (priority 1).
263
438
var handles = allHandles . Where ( h => h . SourceId == element . Id ) . ToArray ( ) ;
264
439
foundRelationships . UnionWith ( handles ) ;
265
440
var events = handles . Select ( h => _codeGraph . Nodes [ h . TargetId ] ) . ToHashSet ( ) ;
266
441
foundElements . UnionWith ( events ) ;
267
- AddToProcessingQueue ( events , currentContext , 1 ) ;
442
+ AddToProcessingQueue ( events , currentContext , 2 ) ;
268
443
269
444
270
- // 3. Calls (priority 2)
271
- var calls = allCalls . Where ( call => call . TargetId == element . Id && IsAllowedCall ( call , currentContext ) ) . ToArray ( ) ;
272
- foundRelationships . UnionWith ( calls ) ;
273
- var callSources = calls
274
- . Select ( d => _codeGraph . Nodes [ d . SourceId ] )
275
- . ToHashSet ( ) ;
276
- foundElements . UnionWith ( callSources ) ;
277
- AddToProcessingQueue ( callSources , currentContext , 2 ) ;
278
445
}
279
446
}
280
447
@@ -288,14 +455,6 @@ void AddToProcessingQueue(IEnumerable<CodeElement> elementsToExplore, Context co
288
455
}
289
456
}
290
457
291
- bool IsAllowedCall ( Relationship call , Context context )
292
- {
293
- var sourceName = _codeGraph . Nodes [ call . SourceId ] . FullName ;
294
- var targetName = _codeGraph . Nodes [ call . TargetId ] . FullName ;
295
- Trace . WriteLine ( $ "Removed: { sourceName } -> { targetName } ") ;
296
-
297
- return true ;
298
- }
299
458
}
300
459
301
460
/// <summary>
@@ -410,6 +569,7 @@ public SearchResult FindAbstractions(string id)
410
569
411
570
var relationships = element . Relationships
412
571
. Where ( d => ( d . Type == RelationshipType . Overrides ||
572
+ d . Type == RelationshipType . Inherits ||
413
573
d . Type == RelationshipType . Implements ) &&
414
574
d . SourceId == element . Id ) . ToList ( ) ;
415
575
var methods = relationships . Select ( m => _codeGraph . Nodes [ m . TargetId ] ) . ToList ( ) ;
@@ -468,17 +628,6 @@ public SearchResult FindIncomingRelationships(string id)
468
628
}
469
629
470
630
471
- /// <summary>
472
- /// source --> target (abstract)
473
- /// </summary>
474
- private bool IsCallToOwnBase ( Relationship call )
475
- {
476
- // Is target more abstract than source?
477
- // target (abstract) <-- source
478
- var isCallToBaseClass = GetRelationships ( d => d . Type is RelationshipType . Overrides )
479
- . Any ( r => r . SourceId == call . SourceId && r . TargetId == call . TargetId ) ;
480
- return isCallToBaseClass ;
481
- }
482
631
483
632
private List < Relationship > GetCachedRelationships ( )
484
633
{
@@ -527,35 +676,6 @@ void Collect(CodeElement c)
527
676
}
528
677
}
529
678
}
530
-
531
- private class FollowIncomingCallsRestriction
532
- {
533
- /// <summary>
534
- /// If we follow incoming calls we include the abstraction of the method.
535
- /// This is because the method may be indirectly called by the interface.
536
- /// If we proceed we may also find calls the base. But this is the wrong direction for the path we follow.
537
- /// So if we followed an abstraction we block the base call to this abstraction
538
- /// <code>
539
- /// class Base
540
- /// {
541
- /// protected virtual void Foo() {}
542
- /// }
543
- ///
544
- /// class Derived : Base
545
- /// {
546
- /// // We start following here!
547
- /// protected override void Foo()
548
- /// {
549
- /// base.Foo()
550
- /// }
551
- /// }
552
- /// </code>
553
- /// Hashset of target ids of base methods.
554
- /// </summary>
555
- public HashSet < string > BlockeBaseCalls { get ; } = [ ] ;
556
-
557
- public HashSet < string > BlockedAbstraction { get ; } = [ ] ;
558
- }
559
679
}
560
680
561
681
public record struct SearchResult ( IEnumerable < CodeElement > Elements , IEnumerable < Relationship > Relationships ) ;
0 commit comments