如何根据名称动态调用方法? [复制]
Posted
技术标签:
【中文标题】如何根据名称动态调用方法? [复制]【英文标题】:How to call methods dynamically based on their name? [duplicate] 【发布时间】:2011-07-18 00:04:50 【问题描述】:当方法的名称包含在字符串变量中时,如何动态调用方法?例如:
class MyClass
def foo; end
def bar; end
end
obj = MyClass.new
str = get_data_from_user # e.g. `gets`, `params`, DB access, etc.
str #=> "foo"
# somehow call `foo` on `obj` using the value in `str`.
我该怎么做?这样做有安全风险吗?
【问题讨论】:
这听起来像是代码异味。听起来这些方法可以重构,所以你不必走这条令人困惑的道路 动态参数可以查看***.com/a/21441197/1770571 【参考方案1】:您可以使用respond_to?
检查方法的可用性。如果有空,请致电send
。例如:
if obj.respond_to?(str)
obj.send(str)
end
【讨论】:
【参考方案2】:使用send
动态调用方法:
obj.send(str)
【讨论】:
【参考方案3】:你真的会想要小心这个。使用用户数据通过send
调用任何方法可以为用户留出空间来执行他们想要的任何方法。 send
通常用于动态调用方法名称,但请确保输入值是受信任的,并且不能被用户操纵。
黄金法则是永远不要相信来自用户的任何输入。
【讨论】:
“黄金法则是永远不要相信来自用户的任何输入。”做笔记!【参考方案4】:你想做的事叫做dynamic dispatch。在 Ruby 中非常简单,只需使用 public_send
:
method_name = 'foobar'
obj.public_send(method_name) if obj.respond_to? method_name
如果方法是私有/受保护的,请改用send
,但更喜欢public_send
。
如果method_name
的值来自用户,这会带来潜在的安全风险。为了防止漏洞,您应该验证可以实际调用哪些方法。例如:
if obj.respond_to?(method_name) && %w[foo bar].include?(method_name)
obj.send(method_name)
end
【讨论】:
【参考方案5】:在 Ruby 中有多种实现动态调度的方法,每种方法各有优缺点。应注意根据情况选择最合适的方法。
下表列出了一些更常见的技术:
+---------------+-----------------+-----------------+------------+------------+
| Method | Arbitrary Code? | Access Private? | Dangerous? | Fastest On |
+---------------+-----------------+-----------------+------------+------------+
| eval | Yes | No | Yes | TBD |
| instance_eval | Yes | No | Yes | TBD |
| send | No | Yes | Yes | TBD |
| public_send | No | No | Yes | TBD |
| method | No | Yes | Yes | TBD |
+---------------+-----------------+-----------------+------------+------------+
任意码
某些技术仅限于调用方法,而其他技术基本上可以执行任何操作。允许执行任意代码的方法应该是used with extreme caution, if not avoided altogether。
访问私有
某些技术仅限于调用公共方法,而其他技术可以同时调用公共和私有方法。理想情况下,您应该努力使用满足您要求的可见性最少的方法。
注意:如果一种技术可以执行任意代码,它可以很容易地用于访问它可能无法访问的私有方法。
危险
仅仅因为一项技术不能执行任意代码或调用私有方法并不意味着它是安全的,尤其是在您使用用户提供的值时。 Delete 是一个公共方法。
最快
根据您的 Ruby 版本,其中一些技术可能比其他技术更高效。要遵循的基准....
示例
class MyClass
def foo(*args); end
private
def bar(*args); end
end
obj = MyClass.new
评估
eval('obj.foo') #=> nil
eval('obj.bar') #=> NoMethodError: private method `bar' called
# With arguments:
eval('obj.foo(:arg1, :arg2)') #=> nil
eval('obj.bar(:arg1, :arg2)') #=> NoMethodError: private method `bar' called
instance_eval
obj.instance_eval('foo') #=> nil
obj.instance_eval('bar') #=> nil
# With arguments:
obj.instance_eval('foo(:arg1, :arg2)') #=> nil
obj.instance_eval('bar(:arg1, :arg2)') #=> nil
发送
obj.send('foo') #=> nil
obj.send('bar') #=> nil
# With arguments:
obj.send('foo', :arg1, :arg2) #=> nil
obj.send('bar', :arg1, :arg2) #=> nil
public_send
obj.public_send('foo') #=> nil
obj.public_send('bar') #=> NoMethodError: private method `bar' called
# With arguments:
obj.public_send('foo', :arg1, :arg2) #=> nil
obj.public_send('bar', :arg1, :arg2) #=> NoMethodError: private method `bar' called
方法
obj.method('foo').call #=> nil
obj.method('bar').call #=> nil
# With arguments:
obj.method('foo').call(:arg1, :arg2) #=> nil
obj.method('bar').call(:arg1, :arg2) #=> nil
【讨论】:
我对这个答案很感兴趣。我不太关心基准测试,但我想知道您是否可以从上述描述中假设public_send
是这些可能性中最不危险的。
@MattSchuchard 您可能会提出这个论点,但如果该方法来自未经验证的用户输入,它仍然不是很安全。如果它来自用户输入,则确实应该根据允许的方法的白名单进行检查,除非您可以接受用户提供“delete_all”或类似的东西......
特别值得注意的是,如果用户同时提供方法名和参数,那么public_send
可以用来调用send
,而send
又可以用来调用@ 987654332@ 或 system
: obj.public_send('send','system','rm','-r','-f','/')
会毁了你的一天。
很好的解释,谢谢@BradWerth以上是关于如何根据名称动态调用方法? [复制]的主要内容,如果未能解决你的问题,请参考以下文章