10

Begin a new R session with an empty environment. Write a series of functions with a parameter that is to be used as the value of the times parameter in a call to rep().

f <- function(n) {
  rep("hello", times = n)
}
f(x)

One expect this to fail, and indeed one gets:

# Error in f(x) : object 'x' not found

Modify the function a bit:

f2 <- function(n) {
  ls.str()
  rep("hello", times = n)
}

f2(x)

As expected, it still fails:

# Error in f2(x) : object 'x' not found

Modify a bit more (to see the environment in the console):

f3 <- function(n) {
  print(ls.str())
  rep("hello", times = n)
}

f3(x)

I still expect failure, but instead get:

## n : <missing>
## [1] "hello"

It is as if the call to print() makes rep work as though times were set to 1.

Homer White
  • 733
  • 1
  • 5
  • 14

3 Answers3

6

This is not an answer, but too long to post as a comment. A minimal reproducible example is:

f3 <- function(n) {
  try(get("n", environment(), inherits=FALSE))
  rep("hello", times = n)
}
f3(x)
## Error in get("n", environment(), inherits = FALSE) : object 'x' not found
## [1] "hello"

The following is speculative and based on loosely examining the source for do_rep. get starts the promise evaluation, but upon not finding the "missing" symbol appears to leave the promise partially unevaluated. rep, being a primitive, then attempts to operate on n without realizing that it is a partially evaluated promise and basically that leads implicitly to the assumption that 'n == 1'.

Also, this shows that the promise is in a weird state (have to use browser/debug to see it):

f3a <- function(n) {
  try(get("n", environment(), inherits=FALSE))
  browser()
  rep("hello", times = n)
}
f3a(x)
## Error in get("n", environment(), inherits = FALSE) : object 'x' not found
## Called from: f3a(x)
# Browse[1]> (n)
## Error: object 'x' not found
## In addition: Warning message:
## restarting interrupted promise evaluation 
## Browse[1]> c
## [1] "hello"
BrodieG
  • 50,078
  • 8
  • 87
  • 138
  • I can already reproduce the code with only the `rep` line, no need for `try(get(…))`. In fact, I think `do_rep` simply does its manual argument matching wrong, bails out on a non-existent promise and pretends that *no* argument except `x` is set. The thing about the “partially unevaluated” promise is a red herring. – Konrad Rudolph Sep 19 '17 at 11:10
  • @KonradRudolph I don't follow. `f3a – Roland Sep 19 '17 at 12:04
  • @Roland Argh, indeed. But `f3a()` does. – Konrad Rudolph Sep 19 '17 at 12:20
6

I received earlier today a report that the bug has been fixed in R-devel and R-patched.

The issue was that the test for missingness in the R sources did not consider the case of an interrupted promise evaluation. A fix has been committed by Luke Tierney and can be seen on GitHub.

Roland
  • 122,144
  • 10
  • 182
  • 276
Homer White
  • 733
  • 1
  • 5
  • 14
2
f4 <- function(n) {
  print('test')
  print(ls.str())
  print('end test')
  rep("hello", times = n)
}
f4(x)

## [1] "test"
## n : <missing>
## [1] "end test"
## [1] "hello"

There's something within print.ls_str, from Frank's test on chat the follwing code exhibit the same problem:

f6 <- function(n) {
  z = tryCatch(get("n", new.env(), mode = "any"), error = function(e) e)
  rep("A", n)
}

Digging a little inside R source I found the following code

#     define GET_VALUE(rval)                      \
    /* We need to evaluate if it is a promise */  \ 
    if (TYPEOF(rval) == PROMSXP) {                \
        PROTECT(rval);                            \
        rval = eval(rval, genv);                  \
        UNPROTECT(1);                             \
    }                                             \
                                                  \
    if (!ISNULL(rval) && NAMED(rval) == 0)        \
        SET_NAMED(rval, 1)


    GET_VALUE(rval);
    break;


    case 2: // get0(.)
    if (rval == R_UnboundValue)
        return CAD4R(args);// i.e.  value_if_not_exists
    GET_VALUE(rval);
    break;
    }
    return rval;
}
#undef GET_VALUE

I'm quite surprised this compile properly, as far as I remember (my C is quite far behind) #define doesn't allow spaces between the # and define.

After digging for that, I'm wrong, from gcc doc:

Whitespace is also allowed before and after the `#'.

So there's probably something around this part of code, but that's above my head to pinpoint what exactly.

Tensibai
  • 15,304
  • 1
  • 37
  • 55