Skip to content

Commit 0f5cf3d

Browse files
committed
Heuristic
1 parent ec6078a commit 0f5cf3d

File tree

3 files changed

+189
-71
lines changed

3 files changed

+189
-71
lines changed

CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs

Lines changed: 186 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,38 @@
44

55
namespace CSharpCodeAnalyst.Exploration;
66

7-
internal class Context
7+
internal class Context(CodeGraph _codeGraph)
88
{
9-
public HashSet<string> Remember { get; } = [];
9+
public HashSet<CodeElement> ForbiddenCallSourcesInHierarchy { get; set; } = [];
1010

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+
}
1232

1333
public Context Clone()
1434
{
15-
var clone = new Context();
35+
var clone = new Context(_codeGraph)
36+
{
37+
ForbiddenCallSourcesInHierarchy = ForbiddenCallSourcesInHierarchy.ToHashSet()
38+
};
1639
return clone;
1740
}
1841
}
@@ -187,10 +210,131 @@ public Invocation FindIncomingCallsRecursive(string id)
187210
}
188211

189212
/// <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.
191328
/// </summary>
192329
public SearchResult FollowIncomingCallsHeuristically(string id)
193330
{
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+
194338
ArgumentNullException.ThrowIfNull(id);
195339

196340
if (_codeGraph is null)
@@ -208,7 +352,7 @@ public SearchResult FollowIncomingCallsHeuristically(string id)
208352
var method = _codeGraph.Nodes[id];
209353

210354
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
212356

213357
var foundRelationships = new HashSet<Relationship>();
214358
var foundElements = new HashSet<CodeElement>();
@@ -228,7 +372,7 @@ public SearchResult FollowIncomingCallsHeuristically(string id)
228372
continue;
229373
}
230374

231-
var currentContext = context.Clone();
375+
var currentContext = context;
232376

233377
if (element.ElementType == CodeElementType.Event)
234378
{
@@ -249,32 +393,55 @@ public SearchResult FollowIncomingCallsHeuristically(string id)
249393

250394
if (element.ElementType == CodeElementType.Method)
251395
{
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+
252428
// 1. Abstractions (priority 0)
253-
// The abstractions limit the allowed calls.
254429
// For methods the abstractions like interfaces may be called.
255430
var abstractions = allImplementsAndOverrides.Where(d => d.SourceId == element.Id).ToArray();
256431
foundRelationships.UnionWith(abstractions);
257432
var abstractionTargets = abstractions.Select(d => _codeGraph.Nodes[d.TargetId]).ToHashSet();
258433
foundElements.UnionWith(abstractionTargets);
259-
AddToProcessingQueue(abstractionTargets, currentContext, 0);
434+
AddToProcessingQueue(abstractionTargets, currentContext, 2);
260435

261436

262437
// Add Events that are handled by this method (priority 1).
263438
var handles = allHandles.Where(h => h.SourceId == element.Id).ToArray();
264439
foundRelationships.UnionWith(handles);
265440
var events = handles.Select(h => _codeGraph.Nodes[h.TargetId]).ToHashSet();
266441
foundElements.UnionWith(events);
267-
AddToProcessingQueue(events, currentContext, 1);
442+
AddToProcessingQueue(events, currentContext, 2);
268443

269444

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);
278445
}
279446
}
280447

@@ -288,14 +455,6 @@ void AddToProcessingQueue(IEnumerable<CodeElement> elementsToExplore, Context co
288455
}
289456
}
290457

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-
}
299458
}
300459

301460
/// <summary>
@@ -410,6 +569,7 @@ public SearchResult FindAbstractions(string id)
410569

411570
var relationships = element.Relationships
412571
.Where(d => (d.Type == RelationshipType.Overrides ||
572+
d.Type == RelationshipType.Inherits ||
413573
d.Type == RelationshipType.Implements) &&
414574
d.SourceId == element.Id).ToList();
415575
var methods = relationships.Select(m => _codeGraph.Nodes[m.TargetId]).ToList();
@@ -468,17 +628,6 @@ public SearchResult FindIncomingRelationships(string id)
468628
}
469629

470630

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-
}
482631

483632
private List<Relationship> GetCachedRelationships()
484633
{
@@ -527,35 +676,6 @@ void Collect(CodeElement c)
527676
}
528677
}
529678
}
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-
}
559679
}
560680

561681
public record struct SearchResult(IEnumerable<CodeElement> Elements, IEnumerable<Relationship> Relationships);

Contracts/Graph/Relationship.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class Relationship(string sourceId, string targetId, RelationshipType typ
1212

1313
public List<SourceLocation> SourceLocations { get; set; } = [];
1414

15-
public bool GetAttribute(RelationshipAttribute attribute)
15+
public bool HasAttribute(RelationshipAttribute attribute)
1616
{
1717
return Attributes.HasFlag(attribute);
1818
}

Contracts/Graph/RelationshipAttribute.cs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
1-
using System.Windows.Markup;
2-
3-
namespace Contracts.Graph;
1+
namespace Contracts.Graph;
42

53
[Flags]
64
public enum RelationshipAttribute : uint
@@ -18,7 +16,7 @@ public enum RelationshipAttribute : uint
1816

1917
public static class RelationshipAttributeExtensions
2018
{
21-
private static List<RelationshipAttribute> Flags = Enum.GetValues(typeof(RelationshipAttribute))
19+
private static readonly List<RelationshipAttribute> Flags = Enum.GetValues(typeof(RelationshipAttribute))
2220
.Cast<RelationshipAttribute>()
2321
.Where(r => r != RelationshipAttribute.None)
2422
.ToList();

0 commit comments

Comments
 (0)