Re: RCS log gets up to large numbers of revisions then RCS will core dump

Paul Eggert <[email protected]> Wed, 26 Oct 2016 12:24:38 -0700
Newsgroups gmane.comp.version-control.rcs.bugs
Organization UCLA Computer Science Department
Message-ID <[email protected]>
Thanks for the bug report and test case. Although RCS 5.7 is two decades 
old and RCS has mutated quite a bit since then, there is a similar 
problem with the current code. I installed the attached patch to fix the 
problem for your test case. Some unbounded recursion still remains 
(marked with FIXME comments), though it should be less likely on 
real-world data.

You can get the latest source code in the 'p' branch here:

http://git.savannah.gnu.org/cgit/rcs.git
0001-int-Fix-stack-crash-for-long-branches.txt (text/plain, 12.4 KB)
From b668481a28bde811ba25fe6d4dee569a736464e3 Mon Sep 17 00:00:00 2001
From: Paul Eggert <[email protected]>
Date: Wed, 26 Oct 2016 12:17:19 -0700
Subject: [PATCH] [int] Fix stack crash for long branches
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This does not fix all recursive calls, just enough to fix
likely cases, including the bug report by Haoming Chu at:
http://lists.gnu.org/archive/html/bug-rcs/2016-10/msg00002.html
* rlog.c (putadelta): Do not check ‘node->selector’; now the
caller’s responsibility.  Remove arg TRUNK; now inferred from
other args.  All callers changed.
(putadelta, putrunk): Omit ‘register’ as the compiler should
figure this stuff out nowadays.
* rcsgen.c (putree):
* rlog.c (putree, exttree, recentdate, extdate):
Avoid recursion in the common case where !root->branches.
* rlog.c (putree): Avoid recursion when outputting the last tree
in the forest.
(putabranch): Do not check whether arg is null; now the
caller’s responsibility.  Avoid recursion in the common case
where !root->selector.
(putforest): Return last tree in the forest.
(extractdelta): Return bool, not char.
(extdate): Return size_t, not int.  All callers changed.
---
 src/ChangeLog |  24 ++++++++
 src/rcsgen.c  |  25 +++++---
 src/rlog.c    | 187 ++++++++++++++++++++++++++++++++--------------------------
 3 files changed, 143 insertions(+), 93 deletions(-)

diff --git a/src/ChangeLog b/src/ChangeLog
index 7f6a1aa..e047a29 100644
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,27 @@
+2016-10-26  Paul Eggert  <[email protected]>
+
+	[int] Fix stack crash for long branches
+
+	This does not fix all recursive calls, just enough to fix
+	likely cases, including the bug report by Haoming Chu at:
+	http://lists.gnu.org/archive/html/bug-rcs/2016-10/msg00002.html
+	* rlog.c (putadelta): Do not check ‘node->selector’; now the
+	caller’s responsibility.  Remove arg TRUNK; now inferred from
+	other args.  All callers changed.
+	(putadelta, putrunk): Omit ‘register’ as the compiler should
+	figure this stuff out nowadays.
+	* rcsgen.c (putree):
+	* rlog.c (putree, exttree, recentdate, extdate):
+	Avoid recursion in the common case where !root->branches.
+	* rlog.c (putree): Avoid recursion when outputting the last tree
+	in the forest.
+	(putabranch): Do not check whether arg is null; now the
+	caller’s responsibility.  Avoid recursion in the common case
+	where !root->selector.
+	(putforest): Return last tree in the forest.
+	(extractdelta): Return bool, not char.
+	(extdate): Return size_t, not int.  All callers changed.
+
 2016-10-25  Paul Eggert  <[email protected]>
 
 	[int] Fix stack crash and port to non-VLA
diff --git a/src/rcsgen.c b/src/rcsgen.c
index 32222ac..ed827c6 100644
--- a/src/rcsgen.c
+++ b/src/rcsgen.c
@@ -461,16 +461,23 @@ void
 puttree (struct delta const *root, register FILE *fout)
 /* Output the delta tree with base ‘root’ in preorder to ‘fout’.  */
 {
-  if (!root)
-    return;
-
-  if (root->selector)
-    putdelta (root, fout);
-
-  puttree (root->ilk, fout);
+  while (root)
+    {
+      if (root->selector)
+        putdelta (root, fout);
 
-  for (struct wlink *ls = root->branches; ls; ls = ls->next)
-    puttree (ls->entry, fout);
+      struct wlink *ls = root->branches;
+      if (! ls)
+        root = root->ilk;
+      else
+        {
+          /* FIXME: This recurses deeply in the worst case.  */
+          puttree (root->ilk, fout);
+          for (; ls->next; ls = ls->next)
+            puttree (ls->entry, fout);
+          root = ls->entry;
+        }
+    }
 }
 
 bool
diff --git a/src/rlog.c b/src/rlog.c
index d1915bf..f5fa370 100644
--- a/src/rlog.c
+++ b/src/rlog.c
@@ -158,22 +158,16 @@ count_a_d (long *a, long *d, struct atat *edits)
 }
 
 static void
-putadelta (register struct delta const *node,
-           register struct delta const *editscript,
-           bool trunk, const char *insDelFormat)
-/* Print delta ‘node’ if ‘node->selector’ is set.
-   ‘editscript’ indicates where the editscript is stored;
-   ‘trunk’ !false indicates this node is in trunk.  */
+putadelta (struct delta const *node, struct delta const *editscript,
+           char const *insDelFormat)
+/* Print delta ‘node’.  ‘editscript’ indicates where the editscript is
+   stored; it equals ‘node’ if this node is not in trunk.  */
 {
-  register FILE *out;
+  FILE *out = stdout;
   char datebuf[datesize + zonelenmax];
   bool pre5 = BE (version) < VERSION (5);
   struct atat *log;
 
-  if (!node->selector)
-    return;
-
-  out = stdout;
   aprintf (out, "----------------------------\nrevision %s%s",
            node->num, pre5 ? "        " : "");
   if (node->lockedby)
@@ -184,6 +178,7 @@ putadelta (register struct delta const *node,
 
   if (editscript && editscript != REPO (tip))
     {
+      bool trunk = node != editscript;
       long a, d;
 
       count_a_d (trunk ? &d : &a,
@@ -218,47 +213,60 @@ static void
 putrunk (const char *insDelFormat)
 /* Print revisions chosen, which are in trunk.  */
 {
-  register struct delta const *ptr;
-
-  for (ptr = REPO (tip); ptr; ptr = ptr->ilk)
-    putadelta (ptr, ptr->ilk, true, insDelFormat);
+  for (struct delta const *ptr = REPO (tip); ptr; ptr = ptr->ilk)
+    if (ptr->selector)
+      putadelta (ptr, ptr->ilk, insDelFormat);
 }
 
-static void putforest (struct wlink const *branchroot, const char *insDelFormat);
+static struct delta const *putforest (struct wlink const *, const char *);
 
 static void
 putree (struct delta const *root, const char *insDelFormat)
 /* Print delta tree from ‘root’ (not including trunk)
    in reverse order on each branch.  */
 {
-  if (!root)
-    return;
-  putree (root->ilk, insDelFormat);
-  putforest (root->branches, insDelFormat);
+  while (root)
+    if (! root->branches)
+      root = root->ilk;
+    else
+      {
+        /* FIXME: This recurses deeply in the worst case.  */
+        putree (root->ilk, insDelFormat);
+        root = putforest (root->branches, insDelFormat);
+      }
 }
 
 static void
 putabranch (struct delta const *root, const char *insDelFormat)
 /* Print one branch from ‘root’.  */
 {
-  if (!root)
-    return;
-  putabranch (root->ilk, insDelFormat);
-  putadelta (root, root, false, insDelFormat);
+  while (!root->selector)
+    {
+      root = root->ilk;
+      if (!root)
+        return;
+    }
+
+  /* FIXME: This recurses deeply in the worst case.  */
+  if (root->ilk)
+    putabranch (root->ilk, insDelFormat);
+
+  putadelta (root, root, insDelFormat);
 }
 
-static void
+static struct delta const *
 putforest (struct wlink const *branchroot, const char *insDelFormat)
 /* Print branches that have the same direct ancestor ‘branchroot’.  */
 {
-  if (!branchroot)
-    return;
-  putforest (branchroot->next, insDelFormat);
+  /* FIXME: This recurses deeply in the worst case.  */
+  if (branchroot->next)
+    putforest (branchroot->next, insDelFormat);
+
   putabranch (branchroot->entry, insDelFormat);
-  putree (branchroot->entry, insDelFormat);
+  return branchroot->entry;
 }
 
-static char
+static bool
 extractdelta (struct delta const *pdelta, bool lockflag,
               struct criteria *criteria)
 /* Return true if ‘pdelta’ matches the selection critera.  */
@@ -301,17 +309,23 @@ exttree (struct delta *root, bool lockflag,
          struct criteria *criteria)
 /* Select revisions, starting with ‘root’.  */
 {
-  if (!root)
-    return;
-
-  root->selector = extractdelta (root, lockflag, criteria);
-  root->pretty_log.string = NULL;
-#define RECURSE(x)  exttree (x, lockflag, criteria)
-  RECURSE (root->ilk);
+  while (root)
+    {
+      root->selector = extractdelta (root, lockflag, criteria);
+      root->pretty_log.string = NULL;
 
-  for (struct wlink *ls = root->branches; ls; ls = ls->next)
-    RECURSE (ls->entry);
-#undef RECURSE
+      if (! root->branches)
+        root = root->ilk;
+      else
+        {
+          /* FIXME: This recurses deeply in the worst case.  */
+          struct wlink *ls;
+          exttree (root->ilk, lockflag, criteria);
+          for (ls = root->branches; ls->next; ls = ls->next)
+            exttree (ls->entry, lockflag, criteria);
+          root = ls->entry;
+        }
+    }
 }
 
 static void
@@ -427,73 +441,78 @@ recentdate (struct delta const *root, struct daterange *r)
    interval given by ‘pd’, and set the ‘strtdate’ of ‘pd’ to the date
    of the selected delta.  */
 {
-  if (!root)
-    return;
-  if (root->selector)
+  while (root)
     {
-      if (!DATE_LT (root->date, r->beg)
+      if (root->selector
+          && !DATE_LT (root->date, r->beg)
           && !DATE_GT (root->date, r->end))
         {
           strncpy (r->beg, root->date, datesize);
           r->beg[datesize - 1] = '\0';
         }
-    }
 
-  recentdate (root->ilk, r);
-  for (struct wlink *ls = root->branches; ls; ls = ls->next)
-    recentdate (ls->entry, r);
+      struct wlink *ls = root->branches;
+      if (!ls)
+        root = root->ilk;
+      else
+        {
+          /* FIXME: This recurses deeply in the worst case.  */
+          for (; ls->next; ls = ls->next)
+            recentdate (ls->entry, r);
+          root = ls->entry;
+        }
+    }
 }
 
-static int
+static size_t
 extdate (struct delta *root, struct date_selection *datesel)
 /* Select revisions which are in the date range specified in ‘datesel->by’
    and ‘datesel->in’, starting at ‘root’.  Return number of revisions
    selected, including those already selected.  */
 {
-  int revno;
+  size_t revno = 0;
 
-  if (!root)
-    return 0;
-
-  if (datesel->in || datesel->by)
+  for (; root; root = root->ilk)
     {
-      struct daterange const *r;
-      bool oep, sel = false;
-
-      for (struct link *ls = datesel->in; ls; ls = ls->next)
+      if (datesel->in || datesel->by)
         {
-          r = ls->entry;
-          oep = r->oep;
-          if ((sel = ((!r->beg[0]
-                       || (oep
-                           ? DATE_LT (r->beg, root->date)
-                           : !DATE_GT (r->beg, root->date)))
-                      &&
-                      (!r->end[0]
-                       || (oep
-                           ? DATE_LT (root->date, r->end)
-                           : !DATE_GT (root->date, r->end))))))
-            break;
-        }
-      if (!sel)
-        {
-          for (struct link *ls = datesel->by; ls; ls = ls->next)
+          struct daterange const *r;
+          bool oep, sel = false;
+
+          for (struct link *ls = datesel->in; ls; ls = ls->next)
             {
               r = ls->entry;
-              if ((sel = DATE_EQ (root->date, r->beg)))
+              oep = r->oep;
+              if ((sel = ((!r->beg[0]
+                           || (oep
+                               ? DATE_LT (r->beg, root->date)
+                               : !DATE_GT (r->beg, root->date)))
+                          &&
+                          (!r->end[0]
+                           || (oep
+                               ? DATE_LT (root->date, r->end)
+                               : !DATE_GT (root->date, r->end))))))
                 break;
             }
           if (!sel)
-            root->selector = false;
+            {
+              for (struct link *ls = datesel->by; ls; ls = ls->next)
+                {
+                  r = ls->entry;
+                  if ((sel = DATE_EQ (root->date, r->beg)))
+                    break;
+                }
+              if (!sel)
+                root->selector = false;
+            }
         }
-    }
 
-#define RECURSE(x)  extdate (x, datesel)
-  revno = root->selector + RECURSE (root->ilk);
+      revno += root->selector;
 
-  for (struct wlink *ls = root->branches; ls; ls = ls->next)
-    revno += RECURSE (ls->entry);
-#undef RECURSE
+      /* FIXME: This recurses deeply in the worst case.  */
+      for (struct wlink *ls = root->branches; ls; ls = ls->next)
+        revno += extdate (ls->entry, datesel);
+    }
 
   return revno;
 }
@@ -750,7 +769,7 @@ rlog_main (const char *cmd, int argc, char **argv)
   bool onlyRCSflag;                    /* print only RCS filename */
   bool pre5;
   bool shownames;
-  int revno;
+  size_t revno;
 
   CHECK_HV (cmd);
   gnurcs_init (&program);
@@ -969,7 +988,7 @@ rlog_main (const char *cmd, int argc, char **argv)
 
             revno = extdate (tip, &datesel);
 
-            aprintf (out, ";\tselected revisions: %d", revno);
+            aprintf (out, ";\tselected revisions: %zu", revno);
           }
 
         newline (out);
-- 
2.7.4