From kevin.hendricks at sympatico.ca  Tue Jun 13 23:45:25 2006
From: kevin.hendricks at sympatico.ca (Kevin B. Hendricks)
Date: Tue, 13 Jun 2006 17:45:25 -0400
Subject: [Rd] Request for Comments: Fix for Bug 8141 (stack overflow)
Message-ID: <DA760531-101D-4B21-9EB2-8DAFDDE66E9F@sympatico.ca>

Hi,

I have a proposed fix for Bug 8141 that passes make check-all on my  
machine and that will actually NOT overflow the C stack even for the  
larger problems than the test case given in 8141.

The basic idea is to not use recursion to walk the list elements and  
instead use a loop building up a new list from the substitution that  
is then returned.

The test case is as follows:

cat test.r

dfn <- rep(list(rep(0,2)),300000)
test <- as.data.frame.list(dfn)


A simple test compares the results with the old implementation in  
R-2.3.1 with my new one


cat testit.sh

# the old implementation
date
R --slave --silent --no-save < test.r
date

# the new one
cd /data/R/src/build/bin
date
./R --slave --silent --no-save < /home/kbhend/test.r
date


Running testit.sh shows that the old case segfaults from the C stack  
overflow while the new case happily proceeds until the end.

Tue Jun 13 17:24:23 EDT 2006
Error: segfault from C stack overflow
Execution halted
Tue Jun 13 17:24:26 EDT 2006
Tue Jun 13 17:24:26 EDT 2006
Tue Jun 13 17:26:22 EDT 2006


My proposed replacement routine for substituteList in coerce.c is  
given by:

/* Work through a list doing substitute on the */
/* elements taking particular care to handle ... */


SEXP attribute_hidden substituteList(SEXP el, SEXP rho)
{
     SEXP h, p, n = R_NilValue;
     p = R_NilValue;
     while (el != R_NilValue) {
	if (CAR(el) == R_DotsSymbol) {
	    if (rho == R_NilValue)
		h = R_UnboundValue;	/* so there is no substitution below */
	    else
		h = findVarInFrame3(rho, CAR(el), TRUE);
	    if (h == R_UnboundValue) {
                 h = lcons(R_DotsSymbol, R_NilValue);
                 if (n == R_NilValue) {
		     PROTECT(n = h);
                 }
                 else {
		     SETCDR(p,h);
		}
                 p = h;
	    } else if (h == R_NilValue  || h == R_MissingArg) {
		/* do not update  the new list pointer n  since we are skipping  
this element */
	    } else if (TYPEOF(h) == DOTSXP) {
	        h = substituteList(h, R_NilValue);
                 if (n == R_NilValue) {
		     PROTECT(n = h);
                 }
                 else {
		     SETCDR(p,h);
		}
                 p = h;
	    } else {
		error(_("... used in an incorrect context"));
                 h = R_NilValue;
                 if (n == R_NilValue)
                      n = h;
                 else {
		     SETCDR(p,h);
		}
                 p = h;
	    }
	}
	else {
	    h = substitute(CAR(el), rho);
	    if (isLanguage(el))
		h = LCONS(h, R_NilValue);
	    else
		h = CONS(h, R_NilValue);
	    SET_TAG(h, TAG(el));
             if (n == R_NilValue) {
	        PROTECT(n=h);
             }
             else {
	        SETCDR(p,h);
	    }
             p = h;
	}
         el = CDR(el);
     }
     if (n != R_NilValue) {
        UNPROTECT(1);
     }
     return n;
}


As I said, with this code in place R passes make check-all with  
flying colors and handles the bug 8141 test case well.


Would someone in the know, please review this piece of code and  
compare it to the original substituteList in coerce.c and let me know  
what you think.  If anyone has access to a large testcase that uses  
substituteList, I would love for them to test this code and report  
any problems.


Hope this helps,

Kevin



[Rd] Request for Comments: Fix for Bug 8141 (stack overflow)
Kevin B. Hendricks