获取方法的调用列表以及通过源代码调用传递的参数

时间:2013-09-12 04:55:35

标签: ruby parsing

问题

鉴于以下代码,我想提取muffinize以及随之传递的所有参数而不运行代码

$ask = false
muffinize("Shoop")
muffinize("Woop") if($ask == true)
if($ask == false)
  muffinize("Pants")
end

这是我期望的输出:

Call#:Args
1:"Shoop"
2:"Woop"
3:"Pants"

到目前为止我正在做什么

我正在用Ripper解析源代码,以找到调用该方法的位置。然后我在调用方法的代码行中找到字符范围。从线上提取方法后,我用Parser gem分析它。一旦我完成它,我会将代码作为答案发布,但我有兴趣听听是否有更好的方法来做到这一点。我不知道代码解析得那么好,所以我在这一点上盲目地徘徊。

为什么不使用正则表达式

正则表达式仍然会捕获注释的代码块。可能还有其他一些我尚未考虑的角落案例。

2 个答案:

答案 0 :(得分:1)

一般来说,静态分析用Ruby这样的动态语言编写的程序很难(读取:不可能),即在没有实际运行程序的情况下找到所有可能的方法调用或分支。其原因在于像Ruby这样的动态语言(以及其他类似Python,Perl或者现在某些甚至Java或.NET的语言)允许基于正在运行的程序中的数据动态生成方法调用。一个例子就是这个

def deliciousness(pastry)
  self.send("#{pastry}ize", "Icing")
end

deliciousness("muffin")

此代码将调用muffinize方法并将"Icing"作为参数传递。但是,由于源代码中没有提到被调用方法的实际名称,因此您无法知道仅使用静态分析器或解析器。您可以使该示例任意地更复杂,并且还涉及代码生成和更多层间接。我的观点是,你不能确定你得到了所有东西,即使你专门报道了其他一些案例。

然而,您可以做的是跟踪代码,因为它实际运行(可能通过测试)并找到您方法的可能调用。您一定要使用静态分析来表达所有这些内容。一旦你使用例如任何网络框架,由于涉及大量的元编程和代码生成,你肯定是运气不好。

答案 1 :(得分:-1)

尽管Holger Just是完全正确的,但对我来说,我有一个用例,没有人会在没有明确输入的情况下调用该方法。这是一个奇怪的角落案例,但我认为无论如何我都会分享我的解决方案。这是我最终使用的代码:

require "parser/current"
require "ripper"

#Get the source code somehow
code = Ripper.lex(source)
callNumber = 0
code.each_with_index do |line, index|
    if(line[1] == :on_ident and line[2][/^muffinize$/] != nil)
        extractedCode = ""
        charEnd = 0
        lineSperated = nil
        charStart = line[0][1]
        lineNumber = line[0][0]
        #Look ahead till you find the first right parenthese
        i = 0
        while(i < code.length-1)
            if(code[index+i] != nil)
                if(code[index+i][1] == :on_rparen)
                    charEnd = code[index+i][0][1]
                    break
                end
            end
            i += 1
        end
        lineSeperated = source.split(/\n/)
        extractedCode = lineSeperated[lineNumber-1]
        extractedCode = extractedCode[charStart,(charEnd-charStart+1)]
        #Use the somewhat crazy Ruby parser gem to interpret the code as the Ruby interpreter would interpret it.
        callArray = Parser::CurrentRuby.parse(extractedCode).to_a
        text = callArray[2].to_a[0].to_s
        callNumber += 1
        puts "#{callNuber}:#{text}"
    end
end