【问题标题】:Scoping (functions) in custom environment自定义环境中的范围(功能)
【发布时间】:2012-04-08 20:38:47
【问题描述】:

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

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

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

我稍后要做的:我将这个简单的函数分配给一些 basestats 函数,以查看评估的表达式是否包含 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 调用,该调用在内部找到一个类似字符串的公式:

> 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) 分配给从 model.frame 调用的 as.formula。不幸的是,我还没有做到这一点:

> 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 就像在model.frame 上面调用的lm

非常欢迎任何提示和想法!

【问题讨论】:

    标签: r function scope sandbox


    【解决方案1】:

    虽然您怀疑不是这样,但调用的是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,在第一个示例中,lm() 调用您的自定义 model.frame() 的原因是 lm() 使用了有时称为 '非标准评价”。 (See this pdf 比您可能想要的更多信息。)关键是 lm() 实际上指示 model.frame() 在调用环境中进行评估;在您的情况下,这导致它找到了您的功能版本。

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

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

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

    mf <- match.call(expand.dots = FALSE)
    ...
    mf[[1L]] <- as.name("model.frame")
    mf <- eval(mf, parent.frame())
    

    【讨论】:

    • 谢谢,您的回答非常有用 (+1) - 尽管我已经查看了相关的 R 资源(当然我意识到 model.frame.default 被称为是因为没有其他方法 - 如果我是对的),我只是以某种方式在lm 的来源中传递了eval 调用。这当然让事情变得清晰!我只需要找到一种简单的方法来强制函数使用我的特殊as.formula(如@RichieCotton 的答案),或者对所有出现的as.formula 等进行grep R 源。啊,也谢谢你的pdf
    【解决方案2】:

    如果您不希望人们能够使用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
    

    【讨论】:

    • 谢谢,这个答案也很方便(+1)!事实上,我不想禁止所有system 调用,这只是一个简单的POC 示例,但我确实想实现一个沙盒环境。请看:sandboxr.no-ip.org 反正assignInNamespace 看起来很有希望,复活节后我需要一些时间做一些实验,一定会提供更详细的反馈。
    • 不错的项目。你见过 Live-R,它做类似的事情吗? live-analytics.com
    • 从未听说过 Live-R,感谢您将我的注意力引向那款简洁的软件!我确实在做类似的事情,但有特殊的(预期的)观众。我们将看到 :) 无论如何,关于您的回答:正如?assignInNamespace 建议不要在包中使用此功能(就像我想的那样)并且不允许我启用/禁用沙箱环境,看来我必须找到另一种方法来停用这些呼叫。就像greping R 类似功能的源代码:) 我想做的是在特殊环境中运行调用,除此之外任何东西都是允许的(用于系统端调用)。
    猜你喜欢
    • 2012-09-17
    • 1970-01-01
    • 1970-01-01
    • 2020-03-15
    • 1970-01-01
    • 1970-01-01
    • 2010-12-03
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多