使用静态代码分析检测“package_name::function_name()”
Detecting `package_name::function_name()` with static code analysis
我正在尝试深入研究 codetools
和 CodeDepends
等静态代码分析包的内部结构,我的近期目标是了解如何检测写成 package_name::function_name()
的函数调用或 package_name:::function_name()
。我本来想只使用 codetools
中的 findGlobals()
,但这并不是那么简单。
要分析的示例函数:
f <- function(n){
tmp <- digest::digest(n)
stats::rnorm(n)
}
所需功能:
analyze_function(f)
## [1] "digest::digest" "stats::rnorm"
尝试 codetools
:
library(codetools)
f = function(n) stats::rnorm(n)
findGlobals(f, merge = FALSE)
## $functions
## [1] "::"
##
## $variables
## character(0)
CodeDepends
更接近了,但我不确定我是否总是可以使用输出来将函数与包匹配。我正在寻找将 rnorm()
连接到 stats
并将 digest()
连接到 digest
的自动规则。
library(CodeDepends)
getInputs(body(f)
## An object of class "ScriptNodeInfo"
## Slot "files":
## character(0)
##
## Slot "strings":
## character(0)
##
## Slot "libraries":
## [1] "digest" "stats"
##
## Slot "inputs":
## [1] "n"
##
## Slot "outputs":
## [1] "tmp"
##
## Slot "updates":
## character(0)
##
## Slot "functions":
## { :: digest rnorm
## NA NA NA NA
##
## Slot "removes":
## character(0)
##
## Slot "nsevalVars":
## character(0)
##
## Slot "sideEffects":
## character(0)
##
## Slot "code":
## {
## tmp <- digest::digest(n)
## stats::rnorm(n)
## }
编辑 公平地说 CodeDepends
,对于那些了解内部结构的人来说,它具有如此多的可定制性和强大功能。目前,我只是想集中精力了解收集器、处理程序、步行者等。显然,可以修改标准 ::
收集器以特别注意每个命名空间调用。现在,这是对类似事情的幼稚尝试。
col <- inputCollector(`::` = function(e, collector, ...){
collector$call(paste0(e[[2]], "::", e[[3]]))
})
getInputs(quote(stats::rnorm(x)), collector = col)@functions
Browse[1]> getInputs(quote(stats::rnorm(x)), collector = col)@functions
stats::rnorm rnorm
NA NA
如果你想从函数中提取命名空间函数,试试这样的方法
find_ns_functions <- function(f, found=c()) {
if( is.function(f) ) {
# function, begin search on body
return(find_ns_functions(body(f), found))
} else if (is.call(f) && deparse(f[[1]]) %in% c("::", ":::")) {
found <- c(found, deparse(f))
} else if (is.recursive(f)) {
# compound object, iterate through sub-parts
v <- lapply(as.list(f), find_ns_functions, found)
found <- unique( c(found, unlist(v) ))
}
found
}
我们可以用
进行测试
f <- function(n){
tmp <- digest::digest(n)
stats::rnorm(n)
}
find_ns_functions(f)
# [1] "digest::digest" "stats::rnorm"
好的,所以这在以前使用 CodeDepends 是可能的,但比应该的要难一些。我刚刚将版本 0.5-4 提交给 github,现在这真的是 "easy"。本质上你只需要修改默认的 colonshandlers ("::" and/or ":::") 如下:
library(CodeDepends) # version >= 0.5-4
handler = function(e, collector, ..., iscall = FALSE) {
collector$library(asVarName(e[[2]]))
## :: or ::: name, remove if you don't want to count those as functions called
collector$call(asVarName(e[[1]]))
if(iscall)
collector$call(deparse((e))) #whole expr ie stats::norm
else
collector$vars(deparse((e)), input=TRUE) #whole expr ie stats::norm
}
getInputs(quote(stats::rnorm(x,y,z)), collector = inputCollector("::" = handler))
getInputs(quote(lapply( 1:10, stats::rnorm)), collector = inputCollector("::" = handler))
上面的第一个 getInputs 调用给出了结果:
An object of class "ScriptNodeInfo"
Slot "files":
character(0)
Slot "strings":
character(0)
Slot "libraries":
[1] "stats"
Slot "inputs":
[1] "x" "y" "z"
Slot "outputs":
character(0)
Slot "updates":
character(0)
Slot "functions":
:: stats::rnorm
NA NA
Slot "removes":
character(0)
Slot "nsevalVars":
character(0)
Slot "sideEffects":
character(0)
Slot "code":
stats::rnorm(x, y, z)
如我所愿。
这里要注意的一件事是我添加到冒号处理程序中的 iscall 参数。默认处理程序和 applyhandlerfactory 现在具有特殊的逻辑,因此当它们在被调用函数的情况下调用其中一个冒号处理程序时,它被设置为 TRUE。
我还没有对当 "stats::rnorm" 代替符号出现时会发生什么进行广泛的测试,特别是在计算依赖性时出现在输入槽中,但我希望所有这些都应该继续工作出色地。如果它不让我知道。
~G
我正在尝试深入研究 codetools
和 CodeDepends
等静态代码分析包的内部结构,我的近期目标是了解如何检测写成 package_name::function_name()
的函数调用或 package_name:::function_name()
。我本来想只使用 codetools
中的 findGlobals()
,但这并不是那么简单。
要分析的示例函数:
f <- function(n){
tmp <- digest::digest(n)
stats::rnorm(n)
}
所需功能:
analyze_function(f)
## [1] "digest::digest" "stats::rnorm"
尝试 codetools
:
library(codetools)
f = function(n) stats::rnorm(n)
findGlobals(f, merge = FALSE)
## $functions
## [1] "::"
##
## $variables
## character(0)
CodeDepends
更接近了,但我不确定我是否总是可以使用输出来将函数与包匹配。我正在寻找将 rnorm()
连接到 stats
并将 digest()
连接到 digest
的自动规则。
library(CodeDepends)
getInputs(body(f)
## An object of class "ScriptNodeInfo"
## Slot "files":
## character(0)
##
## Slot "strings":
## character(0)
##
## Slot "libraries":
## [1] "digest" "stats"
##
## Slot "inputs":
## [1] "n"
##
## Slot "outputs":
## [1] "tmp"
##
## Slot "updates":
## character(0)
##
## Slot "functions":
## { :: digest rnorm
## NA NA NA NA
##
## Slot "removes":
## character(0)
##
## Slot "nsevalVars":
## character(0)
##
## Slot "sideEffects":
## character(0)
##
## Slot "code":
## {
## tmp <- digest::digest(n)
## stats::rnorm(n)
## }
编辑 公平地说 CodeDepends
,对于那些了解内部结构的人来说,它具有如此多的可定制性和强大功能。目前,我只是想集中精力了解收集器、处理程序、步行者等。显然,可以修改标准 ::
收集器以特别注意每个命名空间调用。现在,这是对类似事情的幼稚尝试。
col <- inputCollector(`::` = function(e, collector, ...){
collector$call(paste0(e[[2]], "::", e[[3]]))
})
getInputs(quote(stats::rnorm(x)), collector = col)@functions
Browse[1]> getInputs(quote(stats::rnorm(x)), collector = col)@functions
stats::rnorm rnorm
NA NA
如果你想从函数中提取命名空间函数,试试这样的方法
find_ns_functions <- function(f, found=c()) {
if( is.function(f) ) {
# function, begin search on body
return(find_ns_functions(body(f), found))
} else if (is.call(f) && deparse(f[[1]]) %in% c("::", ":::")) {
found <- c(found, deparse(f))
} else if (is.recursive(f)) {
# compound object, iterate through sub-parts
v <- lapply(as.list(f), find_ns_functions, found)
found <- unique( c(found, unlist(v) ))
}
found
}
我们可以用
进行测试f <- function(n){
tmp <- digest::digest(n)
stats::rnorm(n)
}
find_ns_functions(f)
# [1] "digest::digest" "stats::rnorm"
好的,所以这在以前使用 CodeDepends 是可能的,但比应该的要难一些。我刚刚将版本 0.5-4 提交给 github,现在这真的是 "easy"。本质上你只需要修改默认的 colonshandlers ("::" and/or ":::") 如下:
library(CodeDepends) # version >= 0.5-4
handler = function(e, collector, ..., iscall = FALSE) {
collector$library(asVarName(e[[2]]))
## :: or ::: name, remove if you don't want to count those as functions called
collector$call(asVarName(e[[1]]))
if(iscall)
collector$call(deparse((e))) #whole expr ie stats::norm
else
collector$vars(deparse((e)), input=TRUE) #whole expr ie stats::norm
}
getInputs(quote(stats::rnorm(x,y,z)), collector = inputCollector("::" = handler))
getInputs(quote(lapply( 1:10, stats::rnorm)), collector = inputCollector("::" = handler))
上面的第一个 getInputs 调用给出了结果:
An object of class "ScriptNodeInfo"
Slot "files":
character(0)
Slot "strings":
character(0)
Slot "libraries":
[1] "stats"
Slot "inputs":
[1] "x" "y" "z"
Slot "outputs":
character(0)
Slot "updates":
character(0)
Slot "functions":
:: stats::rnorm
NA NA
Slot "removes":
character(0)
Slot "nsevalVars":
character(0)
Slot "sideEffects":
character(0)
Slot "code":
stats::rnorm(x, y, z)
如我所愿。
这里要注意的一件事是我添加到冒号处理程序中的 iscall 参数。默认处理程序和 applyhandlerfactory 现在具有特殊的逻辑,因此当它们在被调用函数的情况下调用其中一个冒号处理程序时,它被设置为 TRUE。
我还没有对当 "stats::rnorm" 代替符号出现时会发生什么进行广泛的测试,特别是在计算依赖性时出现在输入槽中,但我希望所有这些都应该继续工作出色地。如果它不让我知道。
~G