Erlang私有函数的私有程度如何?

今天我在推特上看到一条引起我注意的推文:

出于科学的考虑,Erlang的私有函数是私有的!这与Python中的情况不同,在Python中,你在各处添加了几个下划线,突然之间,两秒钟前是私有的,现在已经公开了。在Erlang,我们的私有函数在保险箱里安全地呆了25年!没有人可以调用我们声明为私有的函数!或者……有办法吗?

我会告诉你

事实证明,在Erlang中调用私有函数的方式并不那么简单,即使假设代码已经在启用debug_info的情况下编译。 让我们看看事情是如何运作的。

我们首先定义一个名为test的模块。 该模块包含两个函数,其中只有一个被导出:

1
2
3
4
5
6
7
8
9
-module(test).

-export([public/0]).

public() ->
i_am_public.

private() ->
i_am_private.

让我们编译模块,并确保包含调试信息。 注意在Erlang中如何包含调试信息是相当常见的,因为包括调试器,交叉引用工具和覆盖分析工具在内的许多工具都需要这些信息才能工作:

1
2
$ erlc +debug_info test.erl
test.erl:8: Warning: function private/0 is unused

编译器通知我们该私有函数不可访问,这是预期到会有的。 现在让我们打开一个shell并尝试调用这两个函数:

1
2
3
4
5
6
7
8
9
10
$ erl
Erlang/OTP 19 [erts-8.3.5.3] [...]

Eshell V8.3.5.3 (abort with ^G)
1> l(test).
{module,test}
2> test:public.
i_am_public
3> test:private().
** exception error: undefined function test:private/0

我们可以调用公共函数,但不能调用私有函数。没什么新鲜的。现在,让我们定义一个小的匿名函数(我马上将解释它的作用),并将其绑定到变量Open:

1
2
3
4
5
6
7
4> Open = fun(Module) ->
Which = code:which(Module),
{ok,{_,[{_,{_,A}}]}} = beam_lib:chunks(Module, [abstract_code]),
{ok, Module, Binary} = compile:forms(A, [export_all]),
code:load_binary(Module, Which, Binary)
end.
#Fun<erl_eval.6.118419387>

然后让我们调用Open函数,将test模块作为参数传递给它:

1
2
5> Open(test).
{module, test}

现在让我们尝试访问私有函数:

1
2
6> test:private().
i_am_private.

保险箱现在大开着。

我的天哪。

那么,Open函数背后有什么样的黑魔法? 实际上并不多。 让我们再看一遍:

1
2
3
4
5
6
fun(Module) ->
Which = code:which(Module),
{ok,{_,[{_,{_,A}}]}} = beam_lib:chunks(Module, [abstract_code]),
{ok, Module, Binary} = compile:forms(A, [export_all]),
code:load_binary(Module, Which, Binary)
end.

在向code server请求test模块的绝对文件名路径之后,我们使用强大的beam_lib接口从test模块的beam文件中包含的调试信息中提取抽象语法形式(还记得我们使用debug_info选项编译它吗?),然后我们从这些语法形式开始重新编译模块,并添加臭名昭著的export_all选项,这将导致导出模块中定义的所有函数。我们重新加载了模块的新版本。

为了简单起见,在上面的例子中有一些情况是Open函数没有考虑的,但你应该明白它的要点。

快乐黑客!

原文链接: https://medium.com/about-erlang/how-private-are-erlang-private-functions-36382c6abfa4