Skip to content

Commit b710f33

Browse files
authored
Follow incoming calls (#8)
Follow incoming calls If the specialization of method is called we check also if the abstraction is called. In this case base calls are no longer considered.
1 parent 28f93ea commit b710f33

File tree

11 files changed

+524
-267
lines changed

11 files changed

+524
-267
lines changed

CSharpCodeAnalyst.sln.DotSettings

-5
This file was deleted.

CSharpCodeAnalyst/Exploration/CodeGraphExplorer.cs

+70-22
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using CodeParser.Analysis.Cycles;
1+
using System.Diagnostics;
2+
using CodeParser.Analysis.Cycles;
23
using Contracts.Graph;
34

45
namespace CSharpCodeAnalyst.Exploration;
@@ -29,8 +30,8 @@ public List<CodeElement> GetElements(List<string> ids)
2930
foreach (var id in ids)
3031
{
3132
if (_codeGraph.Nodes.TryGetValue(id, out var element))
32-
{
3333
// The element is cloned internally and the relationships discarded.
34+
{
3435
elements.Add(element);
3536
}
3637
}
@@ -172,7 +173,11 @@ public Invocation FindIncomingCallsRecursive(string id)
172173
return new Invocation(foundMethods, foundCalls);
173174
}
174175

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>
176181
public SearchResult FollowIncomingCallsRecursive(string id)
177182
{
178183
ArgumentNullException.ThrowIfNull(id);
@@ -191,12 +196,16 @@ public SearchResult FollowIncomingCallsRecursive(string id)
191196

192197
var method = _codeGraph.Nodes[id];
193198

199+
var restriction = new FollowIncomingCallsRestriction();
194200
var processingQueue = new Queue<CodeElement>();
195201
processingQueue.Enqueue(method);
196202

197203
var foundRelationships = new HashSet<Relationship>();
198204
var foundElements = new HashSet<CodeElement>();
199205

206+
// For convenience. The element is already in the graph. But this way the result is consistent.
207+
foundElements.Add(_codeGraph.Nodes[id]);
208+
200209

201210
var processed = new HashSet<string>();
202211
while (processingQueue.Any())
@@ -207,6 +216,7 @@ public SearchResult FollowIncomingCallsRecursive(string id)
207216
continue;
208217
}
209218

219+
210220
if (element.ElementType == CodeElementType.Event)
211221
{
212222
// An event is raised by the specialization
@@ -215,46 +225,43 @@ public SearchResult FollowIncomingCallsRecursive(string id)
215225
var specializedSources = specializations.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet();
216226
foundElements.UnionWith(specializedSources);
217227
AddToProcessingQueue(specializedSources);
218-
}
219228

220-
// Add all methods that invoke the event
221-
if (element.ElementType == CodeElementType.Event)
222-
{
229+
// Add all methods that invoke the event
223230
var invokes = allInvokes.Where(call => call.TargetId == element.Id).ToArray();
224231
foundRelationships.UnionWith(invokes);
225232
var invokeSources = invokes.Select(d => _codeGraph.Nodes[d.SourceId]).ToHashSet();
226233
foundElements.UnionWith(invokeSources);
227234
AddToProcessingQueue(invokeSources);
228235
}
229236

230-
// Add Events that are handled by this method.
231237
if (element.ElementType == CodeElementType.Method)
232238
{
239+
// Add Events that are handled by this method.
233240
var handles = allHandles.Where(h => h.SourceId == element.Id).ToArray();
234241
foundRelationships.UnionWith(handles);
235242
var events = handles.Select(h => _codeGraph.Nodes[h.TargetId]).ToHashSet();
236243
foundElements.UnionWith(events);
237244
AddToProcessingQueue(events);
238-
}
239245

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

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.
253249
var abstractions = allImplementsAndOverrides.Where(d => d.SourceId == element.Id).ToArray();
254250
foundRelationships.UnionWith(abstractions);
255251
var abstractionTargets = abstractions.Select(d => _codeGraph.Nodes[d.TargetId]).ToHashSet();
256252
foundElements.UnionWith(abstractionTargets);
253+
restriction.WasAddedBecauseItsAnAbstraction.UnionWith(abstractionTargets.Select(t => t.Id));
257254
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);
258265
}
259266
}
260267

@@ -267,6 +274,24 @@ void AddToProcessingQueue(IEnumerable<CodeElement> elementsToExplore)
267274
processingQueue.Enqueue(_codeGraph.Nodes[elementToExplore.Id]);
268275
}
269276
}
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+
}
270295
}
271296

272297
/// <summary>
@@ -322,8 +347,8 @@ public SearchResult FindFullInheritanceTree(string id)
322347
{
323348
var typeToAnalyze = processingQueue.Dequeue();
324349
if (!processed.Add(typeToAnalyze.Id))
325-
{
326350
// Since we evaluate both direction in one iteration, an already processed node is added again.
351+
{
327352
continue;
328353
}
329354

@@ -438,6 +463,19 @@ public SearchResult FindIncomingRelationships(string id)
438463
return new SearchResult(elements, relationships);
439464
}
440465

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+
441479
private List<Relationship> GetCachedRelationships()
442480
{
443481
if (_codeGraph is null)
@@ -485,6 +523,16 @@ void Collect(CodeElement c)
485523
}
486524
}
487525
}
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+
}
488536
}
489537

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

CSharpCodeAnalyst/Resources/Strings.Designer.cs

+1-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)