From cfc06321a67ee60f0905c57cf62e589aec6219ac Mon Sep 17 00:00:00 2001 From: Luke Butler Date: Thu, 14 Sep 2023 16:00:34 -0400 Subject: [PATCH] Remove recursion in getclosedlink MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The function getclosedlink in report.c uses recursion to find closed links when reporting on disconnections. In very large networks, it’s possible for the recursion to exhaust the memory on the call stack which then causes EPANET to crash. If a loop is used instead of recursion, EPANET will not crash with very large disconnections --- ReleaseNotes2_3.md | 1 + src/report.c | 50 +++++++++++++++++++++++++++++++++------------- 2 files changed, 37 insertions(+), 14 deletions(-) diff --git a/ReleaseNotes2_3.md b/ReleaseNotes2_3.md index cb8f151..2cf6f87 100644 --- a/ReleaseNotes2_3.md +++ b/ReleaseNotes2_3.md @@ -47,4 +47,5 @@ This document describes the changes and updates that have been made in version 2 - `EN_STATUS_REPORT` can now be used with `EN_getoption` and `EN_setoption` to get or set the type of status report that EPANET will generate (`EN_NO_REPORT`, `EN_NORMAL_REPORT` or `EN_FULL_REPORT`). - A possible parser error that could result in a Trace Node ID in an input file not being recognized was fixed. - Additional API functions for enabling/disabling controls and rules were added. + - Updated the internal function `getclosedlink` in report.c to use a loop instead of recursion to prevent a stack overflow during the analysis of very large disconnections. \ No newline at end of file diff --git a/src/report.c b/src/report.c index 14f55cf..31e0649 100644 --- a/src/report.c +++ b/src/report.c @@ -45,7 +45,7 @@ static void writeenergy(Project *); static int writeresults(Project *); static int disconnected(Project *); static void marknodes(Project *, int, int *, char *); -static void getclosedlink(Project *, int, char *); +static void getclosedlink(Project *, int, char *, int *); static void writelimits(Project *, int, int); static int checklimits(Report *, double *, int, int); static char *fillstr(char *, char, int); @@ -1287,7 +1287,7 @@ int disconnected(Project *pr) clocktime(rpt->Atime, time->Htime)); writeline(pr, pr->Msg); } - getclosedlink(pr, j, marked); + getclosedlink(pr, j, marked, nodelist); } // Free allocated memory @@ -1350,11 +1350,12 @@ void marknodes(Project *pr, int m, int *nodelist, char *marked) } } -void getclosedlink(Project *pr, int i, char *marked) +void getclosedlink(Project *pr, int i, char *marked, int *stack) /* **---------------------------------------------------------------- ** Input: i = junction index ** marked[] = marks nodes already examined +** stack[] = stack to hold nodes to examine ** Output: None. ** Purpose: Determines if a closed link connects to junction i. **---------------------------------------------------------------- @@ -1365,20 +1366,41 @@ void getclosedlink(Project *pr, int i, char *marked) int j, k; Padjlist alink; + int top = 0; + + // Mark the current junction as examined and push onto stack marked[i] = 2; - for (alink = net->Adjlist[i]; alink != NULL; alink = alink->next) - { - k = alink->link; - j = alink->node; - if (marked[j] == 2) continue; - if (marked[j] == 1) - { - sprintf(pr->Msg, WARN03c, net->Link[k].ID); - writeline(pr, pr->Msg); - return; + stack[top] = i; + + while (top >= 0) { + i = stack[top--]; + alink = net->Adjlist[i]; + + // Iterate through each link adjacent to the current node + while (alink != NULL) { + k = alink->link; + j = alink->node; + + // Skip nodes that have already been examined + if (marked[j] == 2) { + alink = alink->next; + continue; + } + + // If a closed link is found, return and display a warning message + if (marked[j] == 1) { + sprintf(pr->Msg, WARN03c, net->Link[k].ID); + writeline(pr, pr->Msg); + return; + } + + // Mark the node as examined and push it onto the stack + marked[j] = 2; + stack[++top] = j; + alink = alink->next; } - else getclosedlink(pr, j, marked); } + } void writelimits(Project *pr, int j1, int j2)