aboutsummaryrefslogtreecommitdiffstats
path: root/erts/emulator/test/code_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'erts/emulator/test/code_SUITE.erl')
-rw-r--r--erts/emulator/test/code_SUITE.erl520
1 files changed, 520 insertions, 0 deletions
diff --git a/erts/emulator/test/code_SUITE.erl b/erts/emulator/test/code_SUITE.erl
new file mode 100644
index 0000000000..33351a3cc9
--- /dev/null
+++ b/erts/emulator/test/code_SUITE.erl
@@ -0,0 +1,520 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 1999-2009. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+
+-module(code_SUITE).
+-export([all/1,
+ new_binary_types/1,t_check_process_code/1,t_check_process_code_ets/1,
+ external_fun/1,get_chunk/1,module_md5/1,make_stub/1,
+ make_stub_many_funs/1,constant_pools/1,
+ false_dependency/1,coverage/1]).
+
+-include("test_server.hrl").
+
+all(suite) ->
+ [new_binary_types,t_check_process_code,t_check_process_code_ets,
+ external_fun,get_chunk,module_md5,make_stub,make_stub_many_funs,
+ constant_pools,false_dependency,coverage].
+
+new_binary_types(Config) when is_list(Config) ->
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "my_code_test"),
+ ?line {ok,my_code_test,Bin} = compile:file(File, [binary]),
+ ?line {module,my_code_test} = erlang:load_module(my_code_test,
+ make_sub_binary(Bin)),
+ ?line true = erlang:delete_module(my_code_test),
+ ?line true = erlang:purge_module(my_code_test),
+
+ ?line {module,my_code_test} = erlang:load_module(my_code_test,
+ make_unaligned_sub_binary(Bin)),
+ ?line true = erlang:delete_module(my_code_test),
+ ?line true = erlang:purge_module(my_code_test),
+
+ %% Try heap binaries and bad binaries.
+ ?line {error,badfile} = erlang:load_module(my_code_test, <<1,2>>),
+ ?line {error,badfile} = erlang:load_module(my_code_test,
+ make_sub_binary(<<1,2>>)),
+ ?line {error,badfile} = erlang:load_module(my_code_test,
+ make_unaligned_sub_binary(<<1,2>>)),
+ ?line {'EXIT',{badarg,_}} = (catch erlang:load_module(my_code_test,
+ bit_sized_binary(Bin))),
+ ok.
+
+t_check_process_code(doc) -> "Test check_process_code/2.";
+t_check_process_code(Config) when is_list(Config) ->
+ case erlang:system_info(heap_type) of
+ private -> t_check_process_code_1(Config);
+ hybrid -> {skip,"Hybrid heap"}
+ end.
+
+t_check_process_code_1(Config) ->
+ ?line Priv = ?config(priv_dir, Config),
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "my_code_test"),
+ ?line Code = filename:join(Priv, "my_code_test"),
+
+ ?line {ok,my_code_test} = c:c(File, [{outdir,Priv}]),
+
+ ?line MyFun = fun(X, Y) -> X + Y end, %Confuse things.
+ ?line F = my_code_test:make_fun(42),
+ ?line 2 = fun_refc(F),
+ ?line MyFun2 = fun(X, Y) -> X * Y end, %Confuse things.
+ ?line 44 = F(2),
+
+ %% Delete the module and call the fun again.
+ ?line true = erlang:delete_module(my_code_test),
+ ?line 2 = fun_refc(F),
+ ?line 45 = F(3),
+ ?line {'EXIT',{undef,_}} = (catch my_code_test:make_fun(33)),
+
+ %% The fun should still be there, preventing purge.
+ ?line true = erlang:check_process_code(self(), my_code_test),
+ gc(),
+ gc(), %Place funs on the old heap.
+ ?line true = erlang:check_process_code(self(), my_code_test),
+
+ %% Using the funs here guarantees that they will not be prematurely garbed.
+ ?line 48 = F(6),
+ ?line 3 = MyFun(1, 2),
+ ?line 12 = MyFun2(3, 4),
+
+ %% Kill all funs.
+ t_check_process_code1(Code, []).
+
+%% The real fun was killed, but we have some fakes which look similar.
+
+t_check_process_code1(Code, Fakes) ->
+ ?line MyFun = fun(X, Y) -> X + Y + 1 end, %Confuse things.
+ ?line false = erlang:check_process_code(self(), my_code_test),
+ ?line 4 = MyFun(1, 2),
+ t_check_process_code2(Code, Fakes).
+
+t_check_process_code2(Code, _) ->
+ ?line false = erlang:check_process_code(self(), my_code_test),
+ ?line true = erlang:purge_module(my_code_test),
+
+ %% In the next test we will load the same module twice.
+ ?line {module,my_code_test} = code:load_abs(Code),
+ ?line F = my_code_test:make_fun(37),
+ ?line 2 = fun_refc(F),
+ ?line false = erlang:check_process_code(self(), my_code_test),
+ ?line {module,my_code_test} = code:load_abs(Code),
+ ?line 2 = fun_refc(F),
+
+ %% Still false because the fun with the same identify is found
+ %% in the current code.
+ ?line false = erlang:check_process_code(self(), my_code_test),
+
+ %% Some fake funs in the same module should not do any difference.
+ ?line false = erlang:check_process_code(self(), my_code_test),
+
+ 38 = F(1),
+ t_check_process_code3(Code, F, []).
+
+t_check_process_code3(Code, F, Fakes) ->
+ Pid = spawn_link(fun() -> body(F, Fakes) end),
+ ?line true = erlang:purge_module(my_code_test),
+ ?line false = erlang:check_process_code(self(), my_code_test),
+ ?line false = erlang:check_process_code(Pid, my_code_test),
+
+ ?line true = erlang:delete_module(my_code_test),
+ ?line true = erlang:check_process_code(self(), my_code_test),
+ ?line true = erlang:check_process_code(Pid, my_code_test),
+ 39 = F(2),
+ t_check_process_code4(Code, Pid).
+
+t_check_process_code4(_Code, Pid) ->
+ Pid ! drop_funs,
+ receive after 1 -> ok end,
+ ?line false = erlang:check_process_code(Pid, my_code_test),
+ ok.
+
+body(F, Fakes) ->
+ receive
+ jog ->
+ 40 = F(3),
+ erlang:garbage_collect(),
+ body(F, Fakes);
+ drop_funs ->
+ dropped_body()
+ end.
+
+dropped_body() ->
+ receive
+ X -> exit(X)
+ end.
+
+gc() ->
+ erlang:garbage_collect(),
+ gc1().
+gc1() -> ok.
+
+t_check_process_code_ets(doc) ->
+ "Test check_process_code/2 in combination with a fun obtained from an ets table.";
+t_check_process_code_ets(Config) when is_list(Config) ->
+ case {test_server:is_native(?MODULE),erlang:system_info(heap_type)} of
+ {true,_} ->
+ {skipped,"Native code"};
+ {_,hybrid} ->
+ {skipped,"Hybrid heap"};
+ {false,private} ->
+ do_check_process_code_ets(Config)
+ end.
+
+do_check_process_code_ets(Config) ->
+ ?line Priv = ?config(priv_dir, Config),
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "my_code_test"),
+
+ ?line erlang:purge_module(my_code_test),
+ ?line erlang:delete_module(my_code_test),
+ ?line {ok,my_code_test} = c:c(File, [{outdir,Priv}]),
+
+ ?line T = ets:new(my_code_test, []),
+ ?line ets:insert(T, {7,my_code_test:make_fun(107)}),
+ ?line ets:insert(T, {8,my_code_test:make_fun(108)}),
+ ?line erlang:delete_module(my_code_test),
+ ?line false = erlang:check_process_code(self(), my_code_test),
+ Body = fun() ->
+ [{7,F1}] = ets:lookup(T, 7),
+ [{8,F2}] = ets:lookup(T, 8),
+ IdleLoop = fun() -> receive _X -> ok end end,
+ RecLoop = fun(Again) ->
+ receive
+ call -> 110 = F1(3),
+ 100 = F2(-8),
+ Again(Again);
+ {drop_funs,To} ->
+ To ! funs_dropped,
+ IdleLoop()
+ end
+ end,
+ true = erlang:check_process_code(self(), my_code_test),
+ RecLoop(RecLoop)
+ end,
+ ?line Pid = spawn_link(Body),
+ receive after 1 -> ok end,
+ ?line true = erlang:check_process_code(Pid, my_code_test),
+ Pid ! call,
+ Pid ! {drop_funs,self()},
+
+ receive
+ funs_dropped -> ok;
+ Other -> ?t:fail({unexpected,Other})
+ after 10000 ->
+ ?line ?t:fail(no_funs_dropped_answer)
+ end,
+
+ ?line false = erlang:check_process_code(Pid, my_code_test),
+ ok.
+
+fun_refc(F) ->
+ {refc,Count} = erlang:fun_info(F, refc),
+ Count.
+
+
+external_fun(Config) when is_list(Config) ->
+ ?line false = erlang:function_exported(another_code_test, x, 1),
+ ?line ExtFun = erlang:make_fun(id(another_code_test), x, 1),
+ ?line {'EXIT',{undef,_}} = (catch ExtFun(answer)),
+ ?line false = erlang:function_exported(another_code_test, x, 1),
+ ?line false = lists:member(another_code_test, erlang:loaded()),
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "another_code_test"),
+ ?line {ok,another_code_test,Code} = compile:file(File, [binary,report]),
+ ?line {module,another_code_test} = erlang:load_module(another_code_test, Code),
+ ?line 42 = ExtFun(answer),
+ ok.
+
+get_chunk(Config) when is_list(Config) ->
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "my_code_test"),
+ ?line {ok,my_code_test,Code} = compile:file(File, [binary]),
+
+ %% Should work.
+ ?line Chunk = get_chunk_ok("Atom", Code),
+ ?line Chunk = get_chunk_ok("Atom", make_sub_binary(Code)),
+ ?line Chunk = get_chunk_ok("Atom", make_unaligned_sub_binary(Code)),
+
+ %% Should fail.
+ ?line {'EXIT',{badarg,_}} = (catch code:get_chunk(bit_sized_binary(Code), "Atom")),
+ ?line {'EXIT',{badarg,_}} = (catch code:get_chunk(Code, "bad chunk id")),
+
+ %% Invalid beam code or missing chunk should return 'undefined'.
+ ?line undefined = code:get_chunk(<<"not a beam module">>, "Atom"),
+ ?line undefined = code:get_chunk(Code, "XXXX"),
+
+ ok.
+
+get_chunk_ok(Chunk, Code) ->
+ case code:get_chunk(Code, Chunk) of
+ Bin when is_binary(Bin) -> Bin
+ end.
+
+module_md5(Config) when is_list(Config) ->
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "my_code_test"),
+ ?line {ok,my_code_test,Code} = compile:file(File, [binary]),
+
+ %% Should work.
+ ?line Chunk = module_md5_ok(Code),
+ ?line Chunk = module_md5_ok(make_sub_binary(Code)),
+ ?line Chunk = module_md5_ok(make_unaligned_sub_binary(Code)),
+
+ %% Should fail.
+ ?line {'EXIT',{badarg,_}} = (catch code:module_md5(bit_sized_binary(Code))),
+
+ %% Invalid beam code should return 'undefined'.
+ ?line undefined = code:module_md5(<<"not a beam module">>),
+ ok.
+
+module_md5_ok(Code) ->
+ case code:module_md5(Code) of
+ Bin when is_binary(Bin), size(Bin) =:= 16 -> Bin
+ end.
+
+
+make_stub(Config) when is_list(Config) ->
+ %% No old code to purge if hybrid heap because of skipped test cases,
+ %% so we'll need a catch here.
+ ?line (catch erlang:purge_module(my_code_test)),
+
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "my_code_test"),
+ ?line {ok,my_code_test,Code} = compile:file(File, [binary]),
+
+ ?line my_code_test = code:make_stub_module(my_code_test, Code, {[],[]}),
+ ?line true = erlang:delete_module(my_code_test),
+ ?line true = erlang:purge_module(my_code_test),
+
+ ?line my_code_test = code:make_stub_module(my_code_test,
+ make_unaligned_sub_binary(Code),
+ {[],[]}),
+ ?line true = erlang:delete_module(my_code_test),
+ ?line true = erlang:purge_module(my_code_test),
+
+ ?line my_code_test = code:make_stub_module(my_code_test, zlib:gzip(Code),
+ {[],[]}),
+ ?line true = erlang:delete_module(my_code_test),
+ ?line true = erlang:purge_module(my_code_test),
+
+ %% Should fail.
+ ?line {'EXIT',{badarg,_}} =
+ (catch code:make_stub_module(my_code_test, <<"bad">>, {[],[]})),
+ ?line {'EXIT',{badarg,_}} =
+ (catch code:make_stub_module(my_code_test,
+ bit_sized_binary(Code),
+ {[],[]})),
+ ok.
+
+make_stub_many_funs(Config) when is_list(Config) ->
+ %% No old code to purge if hybrid heap because of skipped test cases,
+ %% so we'll need a catch here.
+ ?line (catch erlang:purge_module(many_funs)),
+
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "many_funs"),
+ ?line {ok,many_funs,Code} = compile:file(File, [binary]),
+
+ ?line many_funs = code:make_stub_module(many_funs, Code, {[],[]}),
+ ?line true = erlang:delete_module(many_funs),
+ ?line true = erlang:purge_module(many_funs),
+ ?line many_funs = code:make_stub_module(many_funs,
+ make_unaligned_sub_binary(Code),
+ {[],[]}),
+ ?line true = erlang:delete_module(many_funs),
+ ?line true = erlang:purge_module(many_funs),
+
+ %% Should fail.
+ ?line {'EXIT',{badarg,_}} =
+ (catch code:make_stub_module(many_funs, <<"bad">>, {[],[]})),
+ ?line {'EXIT',{badarg,_}} =
+ (catch code:make_stub_module(many_funs,
+ bit_sized_binary(Code),
+ {[],[]})),
+ ok.
+
+constant_pools(Config) when is_list(Config) ->
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "literals"),
+ ?line {ok,literals,Code} = compile:file(File, [report,binary,constant_pool]),
+ ?line {module,literals} = erlang:load_module(literals,
+ make_sub_binary(Code)),
+
+ %% Initialize.
+ ?line A = literals:a(),
+ ?line B = literals:b(),
+ ?line C = literals:huge_bignum(),
+ ?line process_flag(trap_exit, true),
+ Self = self(),
+
+ %% Have a process WITHOUT old heap that references the literals
+ %% in the 'literals' module.
+ ?line NoOldHeap = spawn_link(fun() -> no_old_heap(Self) end),
+ receive go -> ok end,
+ ?line true = erlang:delete_module(literals),
+ ?line false = erlang:check_process_code(NoOldHeap, literals),
+ ?line erlang:check_process_code(self(), literals),
+ ?line true = erlang:purge_module(literals),
+ ?line NoOldHeap ! done,
+ ?line receive
+ {'EXIT',NoOldHeap,{A,B,C}} ->
+ ok;
+ Other ->
+ ?line ?t:fail({unexpected,Other})
+ end,
+ ?line {module,literals} = erlang:load_module(literals, Code),
+
+ %% Have a process WITH an old heap that references the literals
+ %% in the 'literals' module.
+ ?line OldHeap = spawn_link(fun() -> old_heap(Self) end),
+ receive go -> ok end,
+ ?line true = erlang:delete_module(literals),
+ ?line false = erlang:check_process_code(OldHeap, literals),
+ ?line erlang:check_process_code(self(), literals),
+ ?line erlang:purge_module(literals),
+ ?line OldHeap ! done,
+ receive
+ {'EXIT',OldHeap,{A,B,C,[1,2,3|_]=Seq}} when length(Seq) =:= 16 ->
+ ok
+ end.
+
+no_old_heap(Parent) ->
+ A = literals:a(),
+ B = literals:b(),
+ Res = {A,B,literals:huge_bignum()},
+ Parent ! go,
+ receive
+ done ->
+ exit(Res)
+ end.
+
+old_heap(Parent) ->
+ A = literals:a(),
+ B = literals:b(),
+ Res = {A,B,literals:huge_bignum(),lists:seq(1, 16)},
+ create_old_heap(),
+ Parent ! go,
+ receive
+ done ->
+ exit(Res)
+ end.
+
+create_old_heap() ->
+ case process_info(self(), [heap_size,total_heap_size]) of
+ [{heap_size,Sz},{total_heap_size,Total}] when Sz < Total ->
+ ok;
+ _ ->
+ create_old_heap()
+ end.
+
+%% OTP-7559: c_p->cp could contain garbage and create a false dependency
+%% to a module in a process. (Thanks to Richard Carlsson.)
+false_dependency(Config) when is_list(Config) ->
+ ?line Data = ?config(data_dir, Config),
+ ?line File = filename:join(Data, "cpbugx"),
+ ?line {ok,cpbugx,Code} = compile:file(File, [binary,report]),
+
+ do_false_dependency(fun cpbugx:before/0, Code),
+ do_false_dependency(fun cpbugx:before2/0, Code),
+ do_false_dependency(fun cpbugx:before3/0, Code),
+
+%% %% Spawn process. Make sure it has called cpbugx:before/0 and returned.
+%% Parent = self(),
+%% ?line Pid = spawn_link(fun() -> false_dependency_loop(Parent) end),
+%% ?line receive initialized -> ok end,
+
+%% %% Reload the module. Make sure the process is still alive.
+%% ?line {module,cpbugx} = erlang:load_module(cpbugx, Bin),
+%% ?line io:put_chars(binary_to_list(element(2, process_info(Pid, backtrace)))),
+%% ?line true = is_process_alive(Pid),
+
+%% %% There should not be any dependency to cpbugx.
+%% ?line false = erlang:check_process_code(Pid, cpbugx),
+
+
+
+
+%% %% Kill the process.
+%% ?line unlink(Pid), exit(Pid, kill),
+ ok.
+
+do_false_dependency(Init, Code) ->
+ ?line {module,cpbugx} = erlang:load_module(cpbugx, Code),
+
+ %% Spawn process. Make sure it has the appropriate init function
+ %% and returned. CP should not contain garbage after the return.
+ Parent = self(),
+ ?line Pid = spawn_link(fun() -> false_dependency_loop(Parent, Init) end),
+ ?line receive initialized -> ok end,
+
+ %% Reload the module. Make sure the process is still alive.
+ ?line {module,cpbugx} = erlang:load_module(cpbugx, Code),
+ ?line io:put_chars(binary_to_list(element(2, process_info(Pid, backtrace)))),
+ ?line true = is_process_alive(Pid),
+
+ %% There should not be any dependency to cpbugx.
+ ?line false = erlang:check_process_code(Pid, cpbugx),
+
+ %% Kill the process and completely unload the code.
+ ?line unlink(Pid), exit(Pid, kill),
+ ?line true = erlang:purge_module(cpbugx),
+ ?line true = erlang:delete_module(cpbugx),
+ ?line true = erlang:purge_module(cpbugx),
+ ok.
+
+false_dependency_loop(Parent, Init) ->
+ Init(),
+ Parent ! initialized,
+ receive
+ _ -> false_dependency_loop(Parent, Init)
+ end.
+
+coverage(Config) when is_list(Config) ->
+ ?line code:is_module_native(?MODULE),
+ ?line {'EXIT',{badarg,_}} = (catch erlang:purge_module({a,b,c})),
+ ?line {'EXIT',{badarg,_}} = (catch code:is_module_native({a,b,c})),
+ ?line {'EXIT',{badarg,_}} = (catch erlang:check_process_code(not_a_pid, ?MODULE)),
+ ?line {'EXIT',{badarg,_}} = (catch erlang:check_process_code(self(), [not_a_module])),
+ ?line {'EXIT',{badarg,_}} = (catch erlang:delete_module([a,b,c])),
+ ?line {'EXIT',{badarg,_}} = (catch erlang:module_loaded(42)),
+ ok.
+
+%% Utilities.
+
+make_sub_binary(Bin) when is_binary(Bin) ->
+ {_,B1} = split_binary(list_to_binary([0,1,3,Bin,4,5,6,7]), 3),
+ {B,_} = split_binary(B1, size(Bin)),
+ B;
+make_sub_binary(List) ->
+ make_sub_binary(list_to_binary(List)).
+
+make_unaligned_sub_binary(Bin0) ->
+ Bin1 = <<0:3,Bin0/binary,31:5>>,
+ Sz = size(Bin0),
+ <<0:3,Bin:Sz/binary,31:5>> = id(Bin1),
+ Bin.
+
+%% Add 1 bit to the size of the binary.
+bit_sized_binary(Bin0) ->
+ Bin = <<Bin0/binary,1:1>>,
+ BitSize = bit_size(Bin),
+ BitSize = 8*size(Bin) + 1,
+ Bin.
+
+id(I) -> I.