Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WI #2039 Compute cyclic call using transitive closure. #2052

Open
wants to merge 9 commits into
base: develop
Choose a base branch
from

Conversation

mayanje
Copy link
Contributor

@mayanje mayanje commented Oct 21, 2021

What have been done

We noticed that all cyclic perform call chains were not detected by the curent implementation: a -> b -> c -> a was not detected.
Thus as we know all direct calls, that is to say we know: a -> b, b -> c and c -> a
The idea here is to compute the transitive closure of all known direct calls.
So we compute the transitive closure of the direct call relation which is know as a transitive relation: a->b and b->c implies a->c
During the transitive closure computation, we compute the call chains of cycles in parallel.

@mayanje mayanje added this to the v1.6 milestone Oct 21, 2021
@mayanje mayanje self-assigned this Oct 21, 2021
@trafico-bot trafico-bot bot added the 🔍 Ready for Review Pull Request is not reviewed yet label Oct 21, 2021
@@ -60,7 +60,7 @@ public override IEnumerator<Sentence> GetEnumerator()

public override void AccumulateSentencesThrough(List<Sentence> sentences, Procedure end, out Procedure last)
{
if (_sentences != null)
if (_sentences != null && sentences != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I disagree with this change, sentences is an accumulator list given by the caller of AccumulateSentencesThrough. The purpose of the method is to fill this list so it does not make sense to allow a null reference for this arg.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9bf7eb1

Comment on lines 989 to 999
/// <summary>
/// Add a Direct Call to the Call Relation.
/// For a PERFORM THRU all intermediate procedure are added to the relation.
/// </summary>
/// <param name="caller">The caller of the PERFOM procedure</param>
/// <param name="callee">The PERFOM procedure called</param>
/// <param name="callRelation">CALL Relation</param>
/// <param name="callRelationDomain">The domain of the call relation</param>
private void AddDirectCallRelation(PerformProcedure performCaller, Procedure caller, PerformProcedure callee,
Dictionary<Procedure, Tuple<PerformProcedure, List<Node>, HashSet<Procedure>>> callRelation,
HashSet<Procedure> callRelationDomain)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming: what is a 'CALL relation' ? This is not a good naming choice because it looks like it refers to the Cobol CALL instruction but here we are dealing with PERFORMs only.

What is the first element in the tuple stored for each procedure in the dictionary ? If it is the caller of the said procedure then there is a problem because there can be multiple PERFORMs calling the same procedure in the whole program. Also, the second item of the tuple should be a list of PerformProcedure (not Node).

I think this method can be greatly simplified. Instead of doing the whole procedure resolution process again (finding the procedures targeted in a perform and the associated sentences) we could simply attach the resolved procedures and the list of sentences directly into BasicBlockForNodeGroup instance, at resolve time in ResolvePendingPERFORMProcedure. And then go over the list of groups to build the 'CALL relation' dictionary. The current procedure can also be attached to a Sentence instance very easily at build time so no need to compute it again.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9bf7eb1

}
callRelationDomain.Add(caller);

void addToRelation(Procedure _calleeProcedure, PerformProcedure _callee)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Naming: AddToRelation, first parameter should be called simply procedure to avoid conflict with calleeProcedure and second parameter can be removed as the local function ccan capture the callee parameter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do,e in c5f891a

if (!callRelation.TryGetValue(caller, out var callees))
{
callees = new Tuple<PerformProcedure, List<Node>, HashSet<Procedure>>(performCaller, new List<Node>(), new HashSet<Procedure>());
callRelation[caller] = callees;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an Add operation so better use callRelation.Add(caller, callees);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in c5f891a

Comment on lines 1047 to 1050
if (!callees.Item3.Contains(_calleeProcedure))
{
callees.Item3.Add(_calleeProcedure);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a HashSet, simply call Add.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

don in c5f891a

@@ -1056,6 +1248,7 @@ void StoreSentences(IEnumerable<Sentence> sentences)
if (block is BasicBlockForNodeGroup group0)
{
isPerform = true; //To avoid a second dynamic cast
AddDirectCallRelation(p, currentProcedure, (PerformProcedure)group0.Instructions.Last.Value, callRelation, callRelationDomain);

//Is there a recursion in the graph ?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part (Line 1253 to 1262) can now be removed ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes done in d80af72

foreach (var sentence in sentences)
{
var currentProcedure = n >= procedures.Count ? procedures[procedures.Count - 1] : procedures[n++];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed, store the current procedure inside each Sentence instance at build time.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 9bf7eb1

}
}

void dfs(Procedure a)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you explain the algorithm ? What does the N dictionary contain ? What does "an entry point of a calculated component" mean ?

Copy link
Contributor Author

@mayanje mayanje Nov 3, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok this algorithm is not my invention, it is based on J. Eve, R. Kurki-Suonio transitive closure calculation algorithm, you can google on it, there are many adaptation. I implemented also an adaptation for gathering perform chain path.
I added few comments on it in 9bf7eb1

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need it ? What about this much simpler algorithm ?

        public static Dictionary<N, HashSet<N>> TransitiveClosure<N>(Dictionary<N, HashSet<N>> graph)
        {
            var transitiveClosure = new Dictionary<N, HashSet<N>>();
            var root = graph.FirstOrDefault().Key;
            if (root != null)
            {
                ComputeDirectAndIndirectSuccessors(root);
            }
            return transitiveClosure;

            HashSet<N> ComputeDirectAndIndirectSuccessors(N node)
            {
                if (transitiveClosure.TryGetValue(node, out var result))
                {
                    return result;
                }

                var directSuccessors = graph[node];
                result = new HashSet<N>(directSuccessors);
                transitiveClosure.Add(node, result);

                foreach (var directSuccessor in directSuccessors)
                {
                    foreach (var indirectSuccessor in ComputeDirectAndIndirectSuccessors(directSuccessor))
                    {
                        result.Add(indirectSuccessor);
                    }
                }

                return result;
            }
        }

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok here is the url of the Eve paper: http://i.stanford.edu/pub/cstr/reports/cs/tr/75/508/CS-TR-75-508.pdf
It seems to me that the algorithm that you suggest is in the best case in O(n^2) and in the worst case in O(n^4) because it can implies 4 foreach loops.
In the paper it is say that computing transitive closure is in the best case be in O(n^2) and in the worst case in O(n^3), and it is said in the paper that the algorithm in the paper usually works in O(n) to O(n^2) where n is the number of vertices.

And I don't see in your algorithm where to compute perform call path chain.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes it was an attempt at writing a simpler algorithm to help understanding the transitive closure. Anyway my code does not work properly for cyclic graphs so we can forget about it.

However the main question still remains, do we need to compute the full closure ? Our goal is to gather cyclic chains but not necessarily building the transitive closure of the perform call graph.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My answer is yes we need to compute the full transitive closure, it is this computation that discovers Strongly connected components (that is to say cyclic chains).

@trafico-bot trafico-bot bot added ⚠️ Changes requested Pull Request needs changes before it can be reviewed again and removed 🔍 Ready for Review Pull Request is not reviewed yet labels Oct 22, 2021
@trafico-bot trafico-bot bot added 🔍 Ready for Review Pull Request is not reviewed yet and removed ⚠️ Changes requested Pull Request needs changes before it can be reviewed again labels Oct 29, 2021
@mayanje mayanje requested a review from fm-117 November 3, 2021 10:41
Comment on lines 15 to 22
/// <summary>
/// All target sentences
/// </summary>
internal List<Sentence> Sentences { get; private set; }
/// <summary>
/// All target procedures.
/// </summary>
internal List<Procedure> Procedures { get; private set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove private setters, they are redundant

/// Cache of PerformTarget instances already calculated for a Perform procedure
/// </summary>
private Dictionary<PerformProcedure, PerformTarget> _performTargetCache;
private PerformTarget ComputePerformTarget(PerformProcedure p, SectionNode sectionNode)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming, this is more like GetPerformTarget since we do not compute the target each time the method is called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4b969b9

{
if (_performTargetCache == null)
_performTargetCache = new Dictionary<PerformProcedure, PerformTarget>();
if (_performTargetCache.TryGetValue(p, out var target)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java indent style

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4b969b9

public partial class ControlFlowGraphBuilder<D>
{
/// <summary>
/// PerfomrTarget class to capture target sentences and procedures from a PERFORM instruction.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo: PerformTarget class

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4b969b9


Node procedureNode = ResolveProcedure(p, sectionNode, procedureReference);
if (procedureNode == null)
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This remark goes for all the return null; instructions in this method. We should actually store the null value inside the dictionary to prevent attempting resolving the target multiple times.
Even better: we should create a special instance Unresolved with empty sentences and procedures to use whenever a PerformProcedure cannot have its target properly resolved. This way the method would always return a non-null value.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would say this an abnormal situation it should never happened, let's take it in account with an assert.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact calling an inexisting procedure is a semantic error, so no need to assert here, and simply return null here without storing such error value.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a semantic error but the CFG builder may run on semantically wrong programs. CFG building happens during the Node phase so at this point we may have unresolved procedures. Storing a null PerformTarget in the cache for such cases is required to avoid trying the resolution multiple times. The null-object pattern is useful for the callers of the method which won't have to bother with testing for null value but it is optional.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not a good practice to store null values for semantic error reason.

Comment on lines 68 to 80
int n = 0;
int currentProcedureNumber = procedure.Number;
while (currentProcedureNumber <= throughProcedure.Number)
{
var currentProcedure = this.CurrentProgramCfgBuilder.AllProcedures[currentProcedureNumber];
currentProcedure.AccumulateSentencesThrough(sentences, throughProcedure, out var lastProcedure);
currentProcedureNumber = lastProcedure.Number + 1;
procedures.Add(currentProcedure);
while (n < sentences.Count)
{
sentences[n++].Procedure = currentProcedure;
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to track the current procedure using the n index. The Procedure property of each Sentence object can (and should) be set at construction time. See my previous remark in this PR to see how to do it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4b969b9

sentences[n++].Procedure = currentProcedure;
}
}
} else
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java indent style ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 4b969b9

/// <summary>
/// The associated procedure
/// </summary>
internal Procedure Procedure { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be read-only, set in constructor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is set in PerformTarget

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

right done in 4b969b9

@@ -1,10 +1,10 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
// Ce code a été généré par un outil.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strange the file is generated in French now ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe it's because I have a french version of VS2019

{
if (!callPerformRelation.TryGetValue(caller, out var callees))
{
callees = new Tuple<PerformProcedure, List<Node>, HashSet<Procedure>>(performCaller, new List<Node>(), new HashSet<Procedure>());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

List<Node> can be turned into List<PerformProcedure> if i recall correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would like, but it's because of ControlFlowGraph<N, D>.AddRecursivePerform(PerformProcedure perform, List recursiveJump)

@trafico-bot trafico-bot bot added ⚠️ Changes requested Pull Request needs changes before it can be reviewed again and removed 🔍 Ready for Review Pull Request is not reviewed yet labels Nov 3, 2021
@trafico-bot trafico-bot bot added 🔍 Ready for Review Pull Request is not reviewed yet and removed ⚠️ Changes requested Pull Request needs changes before it can be reviewed again labels Nov 12, 2021
@mayanje mayanje requested a review from fm-117 November 16, 2021 10:38
}

// Depth First Search Traversal.
void dfs(Procedure a)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This last version of the code without Tuple is much clearer.
Can you use more meaningful name for variables?
Like procedure or at least proc instead of a for the name of the procedure.
Same remarks for variables : d, fa, b, nb, ...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 35d0734

Comment on lines 135 to 136
int nb = Visited(b);
if (nb == 0)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With my previous remarks on meaningful variable name, you could inline variable:

                            if (Visited(b)== 0)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 35d0734

@trafico-bot trafico-bot bot added ⚠️ Changes requested Pull Request needs changes before it can be reviewed again and removed 🔍 Ready for Review Pull Request is not reviewed yet labels Nov 18, 2021

Node procedureNode = ResolveProcedure(p, sectionNode, procedureReference);
if (procedureNode == null)
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a semantic error but the CFG builder may run on semantically wrong programs. CFG building happens during the Node phase so at this point we may have unresolved procedures. Storing a null PerformTarget in the cache for such cases is required to avoid trying the resolution multiple times. The null-object pattern is useful for the callers of the method which won't have to bother with testing for null value but it is optional.

Comment on lines +1202 to +1203
// Report Recursive PERFORM call
ReportRecursivePerformCall(performCallRelation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As we have now a new mechanism to detect recursive PERFORMs, we can remove the RecursivityGroupSet property from BasicBlockForNodeGroup class. I've checked the code, it is now written only, never read.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 35d0734

@@ -226,7 +226,7 @@ internal void AddWrongOrderPerformThru(PerformProcedure performThru, N procedure
/// </summary>
/// <param name="perform">Perform node</param>
/// <param name="recursiveJump">Recursive jump node</param>
internal void AddRecursivePerform(PerformProcedure perform, N recursiveJump)
internal void AddRecursivePerform(PerformProcedure perform, List<N> recursiveJump)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is no need to check for the existence of the perform node entry in the dictionary now, we can call Add each time. The second parameter should be renamed also (but I can't find a proper name yet, it is not the cycle, it's all the perform reached through the one passed as first arg).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 35d0734

this.CurrentProgramCfgBuilder.CurrentSection;

if (currentProcedure != null)
if (sentence.Procedure != null)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now that I read it again, sentence.Procedure should never be null since we have a default root section.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, there is a bug in presence of DECLARATIVE:
#2081

//And finally relocate the Graph.
RelocateBasicBlockForNodeGroupGraph(p, group, clonedBlocksIndexMap);

void StoreSentences(IEnumerable<Sentence> sentences)
void StoreSentences(PerformTarget target)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parameter is not used, remove it please.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in 35d0734

@trafico-bot trafico-bot bot added the 🔍 Ready for Review Pull Request is not reviewed yet label Nov 18, 2021
@trafico-bot trafico-bot bot removed the ⚠️ Changes requested Pull Request needs changes before it can be reviewed again label Nov 18, 2021
{
Node throughProcedureNode = ResolveProcedure(p, sectionNode, throughProcedureReference);
if (throughProcedureNode == null)
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The null value should be stored here also.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 49d4035

{
// the second procedure name is declared before the first one.
this.Cfg.AddWrongOrderPerformThru(p, procedureNode, throughProcedureNode);
return null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And here too

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done in 49d4035

@mayanje mayanje requested a review from fm-117 November 22, 2021 16:40
/// Representation of Perform Call Relation.
/// It is a dictionary which gives for each Procedure its Perform Call chain.
/// </summary>
private class PerformCallRelation : Dictionary<Procedure, PerformCallChain>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The inheritance from Dictionary disturbs me.
You are not extending the concept of Dictionary here but rather you want to use a Dictionary to store some information.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not an extension of the concept of Dictionary because Generic Type Variables <TKey, TValue> of the Dictionary base class are instantiated with <Procedure, PerformCallChain>types.


namespace TypeCobol.Analysis.Cfg
{
public partial class ControlFlowGraphBuilder<D>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it have to be in the class ControlFlowGraphBuilder ?
It seems to me that the transitive closure mechanism could be put aside the ControlFlowGraphBuilder.
I mean it can be called by ControlFlowGraphBuilder, but the mechanism could also be called later. Once the graph is fully calculated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yes because only ControlFlowGraphBuilder knows how to discover direct perfrom calls. Once the graph is fully calculated direct perform calls context will be difficult to reproduce without retraversing the graph and analyzing each block to look for performs and perform targets..

Comment on lines 85 to 88
foreach (var sentence in procedure)
{
sentences.Add(sentence);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
foreach (var sentence in procedure)
{
sentences.Add(sentence);
}
sentences.AddRange(procedure);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes done in 99deb05

System.Diagnostics.Debug.Assert(offendingInstruction != null);
this.Cfg.AddRecursivePerform(p, offendingInstruction);
}
AddDirectCallPerformRelation(p, currentProcedure, (PerformProcedure)group0.Instructions.Last.Value, performCallRelation);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly a BasicBlockForNodeGroup is only used for Perform.
Such block always contains only 1 instruction which is the PerformProcedure.
If this is correct, it'd be easier to understand this by creating a getter in BasicBlockForNodeGroup to retrieve the PerformProcedure.
This way, you don't have to understand how BasicBlockForNodeGroup is implemented to retrieve the PerformProcedure.

So this code in this class would become:

Suggested change
AddDirectCallPerformRelation(p, currentProcedure, (PerformProcedure)group0.Instructions.Last.Value, performCallRelation);
AddDirectCallPerformRelation(p, currentProcedure, block.PerformProcedure, performCallRelation);

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes but this is a specialized knowledge whereas BasicBlockForNodeGroup is for a general purpose.

/// We browse the graph represented using the Call Relation, then we found during the visit cycles.
/// In the same time we compute the call chain.
/// </summary>
internal void TransitiveClosure()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It works but it seems we don't realy use the full capabilities of the CFG here. There is no use of SuccessorEdges.

Plus this algorithm only focus on cyclic perform call.
Why don't we try to detect all cyclic code? Like this:

        procedure division.
        a.
            go to b.
        b.
            perform a.

There is also PR #2050 currently in progress with almost the same algorithm.
Why not use the DFS in this other PR to detect every cycle ?
If our internal quality tools only want to report perform cycle as error, then we can filter the result.
And we can report other cycles (cycles not only with perform) as warning as a bonus for developers.

Copy link
Contributor Author

@mayanje mayanje Nov 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this will be an improvement because IBM compiler reports these warnings in such situation:

12  IGYCB7309-W   There may be a loop from the "PERFORM" statement at "PERFORM (line 12.1)" to itself. 
12  IGYCB7310-W   The "PERFORM" statement at "PERFORM (line 12.1)" cannot reach its exit.

But it seems that qualis does not report such situation also.

But our parser report a CFG error like this:
--- Diagnostics ---
Line 12[13,21] <37, Warning, General> - Warning: Statement ' go to b' located at line 10, column 13 prevents this PERFORM statement to reach its exit.

Ok for such situation I agree, Abstract Interpretation is welcome to be very similar with IBM Compiler.

@@ -0,0 +1,11 @@
--- Diagnostics ---
Line 5[12,20] <37, Warning, General> - Warning: A recursive loop has been encountered while analyzing 'PERFORM a', recursive instruction is ' perform b' at line 9.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't possible to report the full perform chain ?
Like perform a, Line 5 > perform b, Line 9 > perform c, Line 12 > perform a, Line 15?

With the current diagnostics, the Cobol developer have to manualy group error message together to understand what the cycle is.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact this the current way of reporting, but the Perform Call Chain contains all information to do that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DataFlow 🔍 Ready for Review Pull Request is not reviewed yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants