4

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

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

它没有做任何特别的事情,只是检查第一个参数的system名称中是否包含单词。这只是一个 POC 示例。

我稍后要做的:我将这个简单的函数分配给 somebasestats函数,以查看评估的表达式是否包含system单词作为第一个参数。例如:

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.formulamodel.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.formulalmmodel.frame

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

4

2 回答 2

4

尽管您怀疑情况并非如此,但stats:::model.frame.default正在调用,而不是 environment 中的自定义版本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() 调用您的自定义的原因是采用有时称为“非标准评估”的方法。(有关该主题的更多信息,请参阅此 pdf,而不是您可能想要的。)关键是它实际上指示在调用环境中进行评估;在您的情况下,这导致它找到了您的功能版本。lm()lm()lm()model.frame()

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

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

如果您有兴趣,以下是定义中的相关行lm

mf <- match.call(expand.dots = FALSE)
...
mf[[1L]] <- as.name("model.frame")
mf <- eval(mf, parent.frame())
于 2012-04-08T23:03:00.167 回答
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
于 2012-04-08T22:38:21.083 回答