自定义环境中的范围(功能)

时间:2012-04-08 20:38:48

标签: r function scope sandbox

我有一个特殊的(虚拟)函数,我想在沙盒环境中使用它:

disable.system.call <- function(...) {
    mc <- match.call()
    if (grepl('system', deparse(mc[[2]])))
        stop('NONO')
    eval(mc, env = .GlobalEnv)        
}

它没有什么特别的,只检查第一个参数的名称中是否有system个字。这只是一个POC的例子。

我稍后会做什么:我将这些简单函数分配给某些basestats函数,以查看求值表达式是否包含system字作为第一个参数。 E.g:

e <- new.env()
eval(parse(text = 'model.frame <- disable.system.call'), envir = e)

这非常酷,因为没有system内部的调用就像魅力,但过滤器有效:

> eval(parse(text = 'model.frame("1 ~ 1")'), envir = e)
  1
1 1
> eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
Error in model.frame("1 ~ system(\"ls -la\")") : NONO

它甚至使用lm调用,调用model.frame内部找到了一个类似字符串的公式:

> eval(parse(text = 'lm(\'1 ~ system("ls -la")\')'), envir = e)
Error in model.frame(formula = "1 ~ system(\"ls -la\")", drop.unused.levels = TRUE) : 
  NONO

我尝试更进一步,并将这个非常简单的函数(disable.system.call)分配给从as.formula调用的model.frame。不幸的是我到目前为止没有:

> e <- new.env()
> eval(parse(text = 'as.formula <- disable.system.call'), envir = e)
> eval(parse(text = 'as.formula("1 ~ 1")'), envir = e)
1 ~ 1
> eval(parse(text = 'as.formula(\'1 ~ system("ls -la")\')'), envir = e)
Error in as.formula("1 ~ system(\"ls -la\")") : NONO
> eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
  1 system("ls -la")
1 1                0
> eval(parse(text = 'lm(\'1 ~ system("ls -la")\')'), envir = e)

Call:
lm(formula = "1 ~ system(\"ls -la\")")

Coefficients:
     (Intercept)  system("ls -la")  
           1                NA  

据我所知model.frame正在调用as.formula,但这不起作用(正如您从上面的输出中看到的那样)。我很确定这不是因为model.frame在自定义环境中调用stats::as.formula作为lm调用model.frame

任何提示和想法都会受到欢迎!

2 个答案:

答案 0 :(得分:4)

虽然您怀疑并非如此,但正在调用stats:::model.frame.default,而不是环境e中的自定义版本。 (这当然是你通常期望的打包函数的行为。由于lm()使用'非标准评估',你的第一个例子中看到的奇怪范围是特殊情况,这是讨论的在我的答案的底部)。

如下所示,您可以使用trace()查看每个案例中调用的as.formula()版本:

disable.system.call <- function(...) {
    mc <- match.call()
    if (grepl('system', deparse(mc[[2]])))
        stop('NONO')
    eval(mc, env = .GlobalEnv)        
}
e <- new.env()
eval(parse(text = 'as.formula <- disable.system.call'), envir = e)


# (1) trace custom 'as.formula()' in environment e
trace(e$as.formula)


# Calling model.frame() **does not** call the the custom as.formula()
eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
#   1 system("ls -la")
# 1 1              127

# (2) trace stats:::as.formula()
trace(stats:::as.formula)

# Calling model.frame() **does** call stats:::as.formula()
eval(parse(text = 'model.frame(\'1 ~ system("ls -la")\')'), envir = e)
# trace: as.formula
#   1 system("ls -la")
# 1 1              127

修改:FWIW,第一个示例中model.frame()调用您的自定义lm() 的原因是lm()采用有时被称为“非标准评估”的方法。 (See this pdf有关此主题的更多信息,而不是您可能想要的内容。)关键位是lm()实际上指示model.frame()在调用环境中进行评估;在你的情况下,这导致它找到你的功能版本。

lm()使用非标准评估的原因是model.frame()可以访问公式中命名的变量,即使它们在调用环境中找到(而不是仅仅能够访问传递的变量)通过data参数lm())。正如托马斯·拉姆利在链接的pdf中所说:

  

如果公式中的变量需要在数据参数中,那么生命会更加简单,但是在引入公式时没有提出这个要求。

如果您感兴趣,以下是lm

定义中的相关行
mf <- match.call(expand.dots = FALSE)
...
mf[[1L]] <- as.name("model.frame")
mf <- eval(mf, parent.frame())

答案 1 :(得分:3)

如果您不希望人们使用system,则覆盖定义会更容易。

assignInNamespace(
  "system", 
  function(...) stop("system calls are not allowed"), 
  getNamespace("base")
)

system("pwd")  #throws an error

我非常猜测你的用例,但是你是否允许用户将任意R代码传递给其他应用程序?在这种情况下,您可能想要编译自己的R版本,删除危险功能或用虚拟替换。


调用函数时执行自定义代码的另一种可能性是trace。例如,

trace(system, quote(stop("You have called system")))  #you may also want print = FALSE