aboutsummaryrefslogtreecommitdiffstats
path: root/lib/stdlib/test/beam_lib_SUITE.erl
diff options
context:
space:
mode:
Diffstat (limited to 'lib/stdlib/test/beam_lib_SUITE.erl')
-rw-r--r--lib/stdlib/test/beam_lib_SUITE.erl761
1 files changed, 761 insertions, 0 deletions
diff --git a/lib/stdlib/test/beam_lib_SUITE.erl b/lib/stdlib/test/beam_lib_SUITE.erl
new file mode 100644
index 0000000000..bc867a3770
--- /dev/null
+++ b/lib/stdlib/test/beam_lib_SUITE.erl
@@ -0,0 +1,761 @@
+%%
+%% %CopyrightBegin%
+%%
+%% %CopyrightEnd%
+%%
+-module(beam_lib_SUITE).
+
+%-define(debug, true).
+
+-ifdef(debug).
+-define(format(S, A), io:format(S, A)).
+-define(line, put(line, ?LINE), ).
+-define(config(X,Y), "./log_dir/").
+-define(t,test_server).
+-define(privdir, "beam_lib_SUITE_priv").
+-else.
+-include("test_server.hrl").
+-define(format(S, A), ok).
+-define(privdir, ?config(priv_dir, Conf)).
+-endif.
+
+-export([all/1, normal/1, error/1, cmp/1, cmp_literals/1, strip/1, otp_6711/1,
+ building/1, md5/1, encrypted_abstr/1, encrypted_abstr_file/1]).
+
+-export([init_per_testcase/2, fin_per_testcase/2]).
+
+all(suite) ->
+ [error, normal, cmp, cmp_literals, strip, otp_6711, building, md5,
+ encrypted_abstr, encrypted_abstr_file].
+
+init_per_testcase(_Case, Config) ->
+ Dog=?t:timetrap(?t:minutes(2)),
+ [{watchdog, Dog}|Config].
+
+fin_per_testcase(_Case, Config) ->
+ Dog=?config(watchdog, Config),
+ test_server:timetrap_cancel(Dog),
+ ok.
+
+normal(suite) -> [];
+normal(doc) -> ["Read correct beam file"];
+normal(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line simple_file(Source),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ CompileFlags = [{outdir,PrivDir}, debug_info],
+ ?line {ok,_} = compile:file(Source, CompileFlags),
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line do_normal(BeamFile),
+ ?line do_normal(Binary),
+
+ ?line {ok,_} = compile:file(Source, [{outdir,PrivDir}, no_debug_info]),
+ ?line {ok, {simple, [{abstract_code, no_abstract_code}]}} =
+ beam_lib:chunks(BeamFile, [abstract_code]),
+
+ %% ?line {ok,_} = compile:file(Source, [compressed | CompileFlags]),
+ %% ?line do_normal(BeamFile),
+
+ ?line file:delete(BeamFile),
+ ?line file:delete(Source),
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ok.
+
+do_normal(BeamFile) ->
+ ?line Imports = {imports, [{erlang, get_module_info, 1},
+ {erlang, get_module_info, 2},
+ {lists, member, 2}]},
+ ?line Exports = {exports, [{module_info, 0}, {module_info, 1}, {t, 0}]},
+ ?line Local = {locals, [{t, 1}]},
+ ?line {ok, {simple, [Imports]}} = beam_lib:chunks(BeamFile, [imports]),
+ ?line {ok, {simple, [{"ImpT",_Bin}]}} =
+ beam_lib:chunks(BeamFile, ["ImpT"]),
+ ?line {ok, {simple, [Exports]}} = beam_lib:chunks(BeamFile, [exports]),
+ ?line {ok, {simple, [{attributes, [{vsn, [_]}]}]}} =
+ beam_lib:chunks(BeamFile, [attributes]),
+ ?line {ok, {simple, [{compile_info, _}=CompileInfo]}} =
+ beam_lib:chunks(BeamFile, [compile_info]),
+ ?line {ok, {simple, [Local]}} = beam_lib:chunks(BeamFile, [locals]),
+ ?line {ok, {simple, [{attributes, [{vsn, [_]}]}, CompileInfo,
+ Exports, Imports, Local]}} =
+ beam_lib:chunks(BeamFile, [attributes, compile_info, exports, imports, locals]),
+ ?line {ok, {simple, [{atoms, _Atoms}]}} =
+ beam_lib:chunks(BeamFile, [atoms]),
+ ?line {ok, {simple, [{labeled_exports, _LExports}]}} =
+ beam_lib:chunks(BeamFile, [labeled_exports]),
+ ?line {ok, {simple, [{labeled_locals, _LLocals}]}} =
+ beam_lib:chunks(BeamFile, [labeled_locals]),
+ ?line {ok, {simple, [_Vsn]}} = beam_lib:version(BeamFile),
+ ?line {ok, {simple, [{abstract_code, _}]}} =
+ beam_lib:chunks(BeamFile, [abstract_code]),
+
+ %% Test reading optional chunks.
+ All = ["Atom", "Code", "StrT", "ImpT", "ExpT", "FunT", "LitT"],
+ ?line {ok,{simple,Chunks}} = beam_lib:chunks(BeamFile, All, [allow_missing_chunks]),
+ ?line verify_simple(Chunks).
+
+verify_simple([{"Atom", AtomBin},
+ {"Code", CodeBin},
+ {"StrT", StrBin},
+ {"ImpT", ImpBin},
+ {"ExpT", ExpBin},
+ {"FunT", missing_chunk},
+ {"LitT", missing_chunk}])
+ when is_binary(AtomBin), is_binary(CodeBin), is_binary(StrBin),
+ is_binary(ImpBin), is_binary(ExpBin) ->
+ ok.
+
+error(suite) -> [];
+error(doc) -> ["Read invalid beam files"];
+error(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line WrongFile = Simple ++ "foo.beam",
+ ?line simple_file(Source),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+ ?line {ok,_} = compile:file(Source, [{outdir,PrivDir},debug_info]),
+ ?line ACopy = filename:join(PrivDir, "a_copy.beam"),
+ ?line copy_file(BeamFile, ACopy),
+
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line copy_file(ACopy, WrongFile),
+ ?line verify(file_error, beam_lib:info("./does_simply_not_exist")),
+
+ ?line do_error(BeamFile, ACopy),
+ ?line do_error(Binary, ACopy),
+
+ ?line copy_file(ACopy, BeamFile),
+ ?line verify(unknown_chunk, beam_lib:chunks(BeamFile, [not_a_chunk])),
+
+ ?line ok = file:write_file(BeamFile, <<>>),
+ ?line verify(not_a_beam_file, beam_lib:info(BeamFile)),
+ ?line verify(not_a_beam_file, beam_lib:info(<<>>)),
+ ?line ok = file:write_file(BeamFile, <<"short">>),
+ ?line verify(not_a_beam_file, beam_lib:info(BeamFile)),
+ ?line verify(not_a_beam_file, beam_lib:info(<<"short">>)),
+
+ ?line {Binary1, _} = split_binary(Binary, byte_size(Binary)-10),
+ ?line verify(chunk_too_big, beam_lib:chunks(Binary1, ["Abst"])),
+ ?line Chunks = chunk_info(Binary),
+ ?line {value, {_, AbstractStart, _}} = lists:keysearch("Abst", 1, Chunks),
+ ?line {Binary2, _} = split_binary(Binary, AbstractStart),
+ ?line verify(chunk_too_big, beam_lib:chunks(Binary2, ["Abst"])),
+ ?line {Binary3, _} = split_binary(Binary, AbstractStart-4),
+ ?line verify(invalid_beam_file, beam_lib:chunks(Binary3, ["Abst"])),
+
+ %% Instead of the 5:32 field below, there used to be control characters
+ %% (including zero bytes) directly in the string. Because inferior programs
+ %% such as sed and clearcasediff don't like zero bytes in text files,
+ %% we have eliminated them.
+ ?line ok = file:write_file(BeamFile, <<"FOR1",5:32,"BEAMfel">>),
+% ?line verify(invalid_beam_file, beam_lib:info(BeamFile)),
+% ?line verify(invalid_beam_file, beam_lib:info(<<"FOR1",5:32,"BEAMfel">>)),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ?line file:delete(Source),
+ ?line file:delete(WrongFile),
+ ?line file:delete(BeamFile),
+ ?line file:delete(ACopy),
+ ok.
+
+do_error(BeamFile, ACopy) ->
+ % evil tests
+ ?line Chunks = chunk_info(BeamFile),
+ ?line {value, {_, AtomStart, _}} = lists:keysearch("Atom", 1, Chunks),
+ ?line {value, {_, ImportStart, _}} = lists:keysearch("ImpT", 1, Chunks),
+ ?line {value, {_, AbstractStart, _}} = lists:keysearch("Abst", 1, Chunks),
+ ?line {value, {_, AttributesStart, _}} =
+ lists:keysearch("Attr", 1, Chunks),
+ ?line {value, {_, CompileInfoStart, _}} =
+ lists:keysearch("CInf", 1, Chunks),
+ ?line verify(missing_chunk, beam_lib:chunks(BeamFile, ["__"])),
+ ?line BF2 = set_byte(ACopy, BeamFile, ImportStart+4, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF2, [imports])),
+ ?line BF3 = set_byte(ACopy, BeamFile, AtomStart-6, 17),
+ ?line verify(missing_chunk, beam_lib:chunks(BF3, [imports])),
+ ?line BF4 = set_byte(ACopy, BeamFile, AbstractStart+10, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF4, [abstract_code])),
+ ?line BF5 = set_byte(ACopy, BeamFile, AttributesStart+10, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF5, [attributes])),
+
+ ?line BF6 = set_byte(ACopy, BeamFile, 1, 17),
+ ?line verify(not_a_beam_file, beam_lib:info(BF6)),
+ ?line BF7 = set_byte(ACopy, BeamFile, 9, 17),
+ ?line verify(not_a_beam_file, beam_lib:info(BF7)),
+
+ ?line BF8 = set_byte(ACopy, BeamFile, 13, 17),
+ ?line verify(missing_chunk, beam_lib:chunks(BF8, ["Atom"])),
+
+ ?line BF9 = set_byte(ACopy, BeamFile, CompileInfoStart+10, 17),
+ ?line verify(invalid_chunk, beam_lib:chunks(BF9, [compile_info])).
+
+
+cmp(suite) -> [];
+cmp(doc) -> ["Compare contents of BEAM files and directories"];
+cmp(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+
+ ?line Dir1 = filename:join(PrivDir, dir1),
+ ?line Dir2 = filename:join(PrivDir, dir2),
+
+ ok = file:make_dir(Dir1),
+ ok = file:make_dir(Dir2),
+
+ ?line {SourceD1, BeamFileD1} = make_beam(Dir1, simple, member),
+ ?line {Source2D1, BeamFile2D1} = make_beam(Dir1, simple2, concat),
+ ?line {SourceD2, BeamFileD2} = make_beam(Dir2, simple, concat),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% cmp
+ ?line ok = beam_lib:cmp(BeamFileD1, BeamFileD1),
+ ?line ver(modules_different, beam_lib:cmp(BeamFileD1, BeamFile2D1)),
+ ?line ver(chunks_different, beam_lib:cmp(BeamFileD1, BeamFileD2)),
+ ?line verify(file_error, beam_lib:cmp(foo, bar)),
+
+ ?line {ok, B1} = file:read_file(BeamFileD1),
+ ?line ok = beam_lib:cmp(B1, BeamFileD1),
+ ?line {ok, B2} = file:read_file(BeamFileD2),
+ ?line ver(chunks_different, beam_lib:cmp(B1, B2)),
+
+ %% cmp_dirs
+ ?line {[],[],[]} = beam_lib:cmp_dirs(Dir1, Dir1),
+ ?line true = {[BeamFile2D1], [], [{BeamFileD1,BeamFileD2}]} ==
+ beam_lib:cmp_dirs(Dir1, Dir2),
+ ?line true = {[], [BeamFile2D1], [{BeamFileD2,BeamFileD1}]} ==
+ beam_lib:cmp_dirs(Dir2, Dir1),
+ ?line ver(not_a_directory, beam_lib:cmp_dirs(foo, bar)),
+
+ %% diff_dirs
+ ?line ok = beam_lib:diff_dirs(Dir1, Dir1),
+ ?line ver(not_a_directory, beam_lib:diff_dirs(foo, bar)),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+ ?line delete_files([SourceD1, BeamFileD1, Source2D1,
+ BeamFile2D1, SourceD2, BeamFileD2]),
+
+ file:del_dir(Dir1),
+ file:del_dir(Dir2),
+ ok.
+
+cmp_literals(suite) -> [];
+cmp_literals(doc) -> ["Compare contents of BEAM files having literals"];
+cmp_literals(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+
+ ?line Dir1 = filename:join(PrivDir, dir1),
+ ?line Dir2 = filename:join(PrivDir, dir2),
+
+ ok = file:make_dir(Dir1),
+ ok = file:make_dir(Dir2),
+
+ ?line {SourceD1, BeamFileD1} = make_beam(Dir1, simple, constant),
+ ?line {SourceD2, BeamFileD2} = make_beam(Dir2, simple, constant2),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% cmp
+ ?line ok = beam_lib:cmp(BeamFileD1, BeamFileD1),
+ ?line ver(chunks_different, beam_lib:cmp(BeamFileD1, BeamFileD2)),
+
+ ?line {ok, B1} = file:read_file(BeamFileD1),
+ ?line ok = beam_lib:cmp(B1, BeamFileD1),
+ ?line {ok, B2} = file:read_file(BeamFileD2),
+ ?line ver(chunks_different, beam_lib:cmp(B1, B2)),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+
+ ?line delete_files([SourceD1, BeamFileD1, SourceD2, BeamFileD2]),
+
+ file:del_dir(Dir1),
+ file:del_dir(Dir2),
+ ok.
+
+strip(suite) -> [];
+strip(doc) -> ["Strip BEAM files"];
+strip(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line {SourceD1, BeamFileD1} = make_beam(PrivDir, simple, member),
+ ?line {Source2D1, BeamFile2D1} = make_beam(PrivDir, simple2, concat),
+ ?line {Source3D1, BeamFile3D1} = make_beam(PrivDir, make_fun, make_fun),
+ ?line {Source4D1, BeamFile4D1} = make_beam(PrivDir, constant, constant),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% strip binary
+ ?line verify(not_a_beam_file, beam_lib:strip(<<>>)),
+ ?line {ok, B1} = file:read_file(BeamFileD1),
+ ?line {ok, {simple, NB1}} = beam_lib:strip(B1),
+ ?line BId1 = chunk_ids(B1),
+ ?line NBId1 = chunk_ids(NB1),
+ ?line true = length(BId1) > length(NBId1),
+ ?line compare_chunks(B1, NB1, NBId1),
+
+ %% strip file
+ ?line verify(file_error, beam_lib:strip(foo)),
+ ?line {ok, {simple, _}} = beam_lib:strip(BeamFileD1),
+ ?line compare_chunks(NB1, BeamFileD1, NBId1),
+
+ %% strip_files
+ ?line {ok, B2} = file:read_file(BeamFile2D1),
+ ?line {ok, [{simple,_},{simple2,_}]} = beam_lib:strip_files([B1, B2]),
+ ?line {ok, [{simple,_},{simple2,_},{make_fun,_},{constant,_}]} =
+ beam_lib:strip_files([BeamFileD1, BeamFile2D1, BeamFile3D1, BeamFile4D1]),
+
+ %% check that each module can be loaded.
+ ?line {module, simple} = code:load_abs(filename:rootname(BeamFileD1)),
+ ?line {module, simple2} = code:load_abs(filename:rootname(BeamFile2D1)),
+ ?line {module, make_fun} = code:load_abs(filename:rootname(BeamFile3D1)),
+ ?line {module, constant} = code:load_abs(filename:rootname(BeamFile4D1)),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+
+ ?line delete_files([SourceD1, BeamFileD1,
+ Source2D1, BeamFile2D1,
+ Source3D1, BeamFile3D1,
+ Source4D1, BeamFile4D1]),
+ ok.
+
+
+otp_6711(Conf) when is_list(Conf) ->
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:info(3)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:chunks(a, b)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:chunks(a,b,c)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:all_chunks(3)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:cmp(3,4)}),
+ ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:strip(3)}),
+ ?line {'EXIT',{function_clause,_}} =
+ (catch {a, beam_lib:strip_files([3])}),
+
+ ?line PrivDir = ?privdir,
+ ?line Dir = filename:join(PrivDir, dir),
+ ?line Lib = filename:join(Dir, "lib"),
+ ?line App = filename:join(Lib, "app"),
+ ?line EBin = filename:join(App, "ebin"),
+
+ ok = file:make_dir(Dir),
+ ok = file:make_dir(Lib),
+ ok = file:make_dir(App),
+ ok = file:make_dir(EBin),
+
+ ?line {SourceD, BeamFileD} = make_beam(EBin, simple, member),
+
+ unwritable(BeamFileD),
+
+ %% There is no way that strip_release can fail with
+ %% function_clause or something like that...
+ ?line {error,_,{file_error,_,_}} = beam_lib:strip_release(Dir),
+
+ ?line delete_files([SourceD, BeamFileD]),
+ file:del_dir(EBin),
+ file:del_dir(App),
+ file:del_dir(Lib),
+ file:del_dir(Dir),
+ ok.
+
+-include_lib("kernel/include/file.hrl").
+
+unwritable(Fname) ->
+ {ok, Info} = file:read_file_info(Fname),
+ Mode = Info#file_info.mode - 8#00200,
+ file:write_file_info(Fname, Info#file_info{mode = Mode}).
+
+building(doc) -> "Testing building of BEAM files.";
+building(Conf) when is_list(Conf) ->
+ ?line PrivDir = ?privdir,
+
+ ?line Dir1 = filename:join(PrivDir, b_dir1),
+ ?line Dir2 = filename:join(PrivDir, b_dir2),
+
+ ok = file:make_dir(Dir1),
+ ok = file:make_dir(Dir2),
+
+ ?line {SourceD1, BeamFileD1} = make_beam(Dir1, building, member),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ %% read all chunks
+ ?line ChunkIds = chunk_ids(BeamFileD1),
+ ?line {ok, _Mod, Chunks} = beam_lib:all_chunks(BeamFileD1),
+ ?line ChunkIds = lists:map(fun ({Id, Data}) when is_binary(Data) -> Id
+ end, Chunks),
+
+ %% write a new beam file, with reversed chunk order
+ ?line BeamFileD2 = filename:join(Dir2, "building.beam"),
+ ?line {ok,RevBeam} = beam_lib:build_module(lists:reverse(Chunks)),
+ ?line file:write_file(BeamFileD2, RevBeam),
+
+ %% compare files
+ ?line compare_chunks(BeamFileD1, BeamFileD2, ChunkIds),
+
+ %% test that we can retrieve a chunk before the atom table
+ %% (actually, try to retrieve all chunks)
+
+ ?line lists:foreach(fun(Id) ->
+ {ok, {building, [{Id, _Data}]}} =
+ beam_lib:chunks(BeamFileD1, [Id])
+ end, ChunkIds),
+ ?line lists:foreach(fun(Id) ->
+ {ok, {building, [{Id, _Data}]}} =
+ beam_lib:chunks(BeamFileD2, [Id])
+ end, ChunkIds),
+
+ ?line true = (P0 == pps()),
+ ?line NoOfTables = length(ets:all()),
+
+ ?line delete_files([SourceD1, BeamFileD1, BeamFileD2]),
+ file:del_dir(Dir1),
+ file:del_dir(Dir2),
+ ok.
+
+md5(suite) -> [];
+md5(doc) -> ["Compare beam_lib:md5/1 and code:module_md5/1."];
+md5(Conf) when is_list(Conf) ->
+ ?line Beams = collect_beams(),
+ io:format("Found ~w beam files", [length(Beams)]),
+ md5_1(Beams).
+
+md5_1([N|Ns]) ->
+ {ok,Beam0} = file:read_file(N),
+ Beam = maybe_uncompress(Beam0),
+ {ok,{Mod,MD5}} = beam_lib:md5(Beam),
+ {Mod,MD5} = {Mod,code:module_md5(Beam)},
+ md5_1(Ns);
+md5_1([]) -> ok.
+
+collect_beams() ->
+ SuperDir = filename:dirname(filename:dirname(code:which(?MODULE))),
+ TestDirs = filelib:wildcard(filename:join([SuperDir,"*_test"])),
+ AbsDirs = [filename:absname(X) || X <- code:get_path()],
+ collect_beams_1(AbsDirs ++ TestDirs).
+
+collect_beams_1([Dir|Dirs]) ->
+ filelib:wildcard(filename:join(Dir, "*.beam")) ++ collect_beams_1(Dirs);
+collect_beams_1([]) -> [].
+
+maybe_uncompress(<<"FOR1",_/binary>>=Beam) -> Beam;
+maybe_uncompress(Beam) -> zlib:gunzip(Beam).
+
+encrypted_abstr(suite) -> [];
+encrypted_abstr(doc) -> ["Test encrypted abstract format"];
+encrypted_abstr(Conf) when is_list(Conf) ->
+ run_if_crypto_works(fun() -> encrypted_abstr_1(Conf) end).
+
+encrypted_abstr_1(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line simple_file(Source),
+
+ %% Avoid getting an extra port when crypto starts erl_ddll.
+ ?line erl_ddll:start(),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ Key = "#a_crypto_key",
+ CompileFlags = [{outdir,PrivDir}, debug_info, {debug_info_key,Key}],
+ ?line {ok,_} = compile:file(Source, CompileFlags),
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line do_encrypted_abstr(BeamFile, Key),
+ ?line do_encrypted_abstr(Binary, Key),
+
+ ?line ok = crypto:stop(), %To get rid of extra ets tables.
+ ?line file:delete(BeamFile),
+ ?line file:delete(Source),
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ok.
+
+do_encrypted_abstr(Beam, Key) ->
+ ?line verify(key_missing_or_invalid, beam_lib:chunks(Beam, [abstract_code])),
+
+ %% The raw chunk "Abst" can still be read even without a key.
+ ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
+ ?line <<0:8,8:8,"des3_cbc",_/binary>> = Abst,
+
+ %% Try som invalid funs.
+ ?line bad_fun(badfun, fun() -> ok end),
+ ?line bad_fun(badfun, {a,b}),
+ ?line bad_fun(blurf),
+ ?line {function_clause,_} = bad_fun(fun(glurf) -> ok end),
+
+ %% Funs that return something strange.
+ ?line bad_fun(badfun, fun(init) -> {ok,fun() -> ok end} end),
+ ?line glurf = bad_fun(fun(init) -> {error,glurf} end),
+
+ %% Try clearing (non-existing fun).
+ ?line undefined = beam_lib:clear_crypto_key_fun(),
+
+ %% Install a fun which cannot retrieve a key.
+ ?line ok = beam_lib:crypto_key_fun(fun(init) -> ok end),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Install a fun which returns an incorrect key.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun("wrong key...")),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Installing a new key fun is not possible without clearing the old.
+ ?line verify(exists, beam_lib:crypto_key_fun(simple_crypto_fun(Key))),
+
+ %% Install the simplest possible working key fun.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun(Key)),
+ ?line verify_abstract(Beam),
+ ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
+
+ %% Installing a new key fun is not possible without clearing the old.
+ verify(exists, beam_lib:crypto_key_fun(ets_crypto_fun(Key))),
+
+ %% Install a key using an ets table.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line ok = beam_lib:crypto_key_fun(ets_crypto_fun(Key)),
+ ?line verify_abstract(Beam),
+ ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
+
+ ?line {ok,cleared} = beam_lib:clear_crypto_key_fun(),
+ ok.
+
+
+bad_fun(F) ->
+ {error,E} = beam_lib:crypto_key_fun(F),
+ E.
+
+bad_fun(S, F) ->
+ verify(S, beam_lib:crypto_key_fun(F)).
+
+
+verify_abstract(Beam) ->
+ {ok,{simple,[Chunk]}} = beam_lib:chunks(Beam, [abstract_code]),
+ {abstract_code,{raw_abstract_v1,_}} = Chunk.
+
+simple_crypto_fun(Key) ->
+ fun(init) -> ok;
+ ({debug_info, des3_cbc, simple, _}) -> Key
+ end.
+
+ets_crypto_fun(Key) ->
+ fun(init) ->
+ T = ets:new(beam_lib_SUITE_keys, [private, set]),
+ true = ets:insert(T, {key,Key}),
+ {ok,fun({debug_info, des3_cbc, simple, _}) ->
+ [{key,Val}] = ets:lookup(T, key),
+ Val;
+ (clear) ->
+ ets:delete(T),
+ cleared
+ end}
+ end.
+
+encrypted_abstr_file(suite) -> [];
+encrypted_abstr_file(doc) ->
+ ["Test encrypted abstract format with the key in .erlang.crypt"];
+encrypted_abstr_file(Conf) when is_list(Conf) ->
+ run_if_crypto_works(fun() -> encrypted_abstr_file_1(Conf) end).
+
+encrypted_abstr_file_1(Conf) ->
+ ?line PrivDir = ?privdir,
+ ?line Simple = filename:join(PrivDir, "simple"),
+ ?line Source = Simple ++ ".erl",
+ ?line BeamFile = Simple ++ ".beam",
+ ?line simple_file(Source),
+
+ %% Avoid getting an extra port when crypto starts erl_ddll.
+ ?line erl_ddll:start(),
+
+ ?line NoOfTables = length(ets:all()),
+ ?line P0 = pps(),
+
+ Key = "Long And niCe 99Krypto Key",
+ CompileFlags = [{outdir,PrivDir}, debug_info, {debug_info_key,Key}],
+ ?line {ok,_} = compile:file(Source, CompileFlags),
+ ?line {ok, Binary} = file:read_file(BeamFile),
+
+ ?line {ok,OldCwd} = file:get_cwd(),
+ ?line ok = file:set_cwd(PrivDir),
+ ?line do_encrypted_abstr_file(BeamFile, Key),
+ ?line do_encrypted_abstr_file(Binary, Key),
+ ?line ok = file:set_cwd(OldCwd),
+
+ ?line ok = crypto:stop(), %To get rid of extra ets tables.
+ ?line file:delete(filename:join(PrivDir, ".erlang.crypt")),
+ ?line file:delete(BeamFile),
+ ?line file:delete(Source),
+ ?line NoOfTables = length(ets:all()),
+ ?line true = (P0 == pps()),
+ ok.
+
+do_encrypted_abstr_file(Beam, Key) ->
+ %% No key.
+ ?line write_crypt_file(""),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% A wrong key.
+ ?line write_crypt_file(["[{debug_info,des3_cbc,simple,\"A Wrong Key\"}].\n"]),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Write correct key...
+ ?line write_crypt_file(["[{debug_info,des3_cbc,simple,\"",Key,"\"}].\n"]),
+
+ %% ... but the fun with the wrong key is still there.
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+
+ %% Clear the fun. Now it should work.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line verify_abstract(Beam),
+ ?line verify_abstract(Beam),
+ ?line ok = file:delete(".erlang.crypt"),
+ ?line verify_abstract(Beam),
+
+ %% Clear, otherwise the second pass will fail.
+ ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
+ ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
+ ok.
+
+write_crypt_file(Contents0) ->
+ Contents = list_to_binary([Contents0]),
+ io:format("~s\n", [binary_to_list(Contents)]),
+ ok = file:write_file(".erlang.crypt", Contents).
+
+compare_chunks(File1, File2, ChunkIds) ->
+ ?line {ok, {_, Chunks1}} = beam_lib:chunks(File1, ChunkIds),
+ ?line {ok, {_, Chunks2}} = beam_lib:chunks(File2, ChunkIds),
+ ?line true = Chunks1 == Chunks2.
+
+chunk_ids(File) ->
+ ?line lists:map(fun({Id,_Start,_Size}) -> Id end, chunk_info(File)).
+
+chunk_info(File) ->
+ ?line {value, {chunks, Chunks}} =
+ lists:keysearch(chunks, 1, beam_lib:info(File)),
+ Chunks.
+
+make_beam(Dir, Module, F) ->
+ ?line FileBase = filename:join(Dir, Module),
+ ?line Source = FileBase ++ ".erl",
+ ?line BeamFile = FileBase ++ ".beam",
+ ?line simple_file(Source, Module, F),
+ ?line {ok, _} = compile:file(Source, [{outdir,Dir}, debug_info, report]),
+ {Source, BeamFile}.
+
+set_byte(_Backup, Binary, Pos, Byte) when is_binary(Binary) ->
+ ?line <<B1:Pos/binary, _:1/binary, B2/binary>> = Binary,
+ NB = <<B1/binary, Byte:8, B2/binary>>,
+ NB;
+set_byte(Backup, File, Pos, Byte) ->
+ ?line copy_file(Backup, File),
+ ?line set_byte(File, Pos, Byte),
+ File.
+
+set_byte(File, Pos, Byte) ->
+ ?line {ok, Fd} = file:open(File, [read, write]),
+ ?line {ok, _} = file:position(Fd, Pos),
+ ?line ok = file:write(Fd, [Byte]),
+ ?line file:close(Fd).
+
+copy_file(Src, Dest) ->
+ % ?t:format("copying from ~p to ~p~n", [Src, Dest]),
+ ?line {ok, _} = file:copy(Src, Dest),
+ ?line ok = file:change_mode(Dest, 8#0666).
+
+delete_files(Files) ->
+ lists:foreach(fun(F) -> file:delete(F) end, Files).
+
+verify(S, {error, beam_lib, R}) ->
+ verify_error(S, R);
+verify(S, {error, R}) ->
+ verify_error(S, R).
+
+verify_error(S, R) ->
+ if
+ S =:= R -> ok;
+ true -> [S|_] = tuple_to_list(R)
+ end,
+
+ %% Most formatted messages begin with "./simple.beam:" or "<<...".
+ FM = string:str(lists:flatten(beam_lib:format_error(R)), "simpl") > 0,
+ BM = string:str(lists:flatten(beam_lib:format_error(R)), "<<") > 0,
+
+ %% Also make sure that formatted message is not just the term printed.
+ Handled = beam_lib:format_error(R) =/= io_lib:format("~p~n", [R]),
+ true = ((FM > 0) or (BM > 0)) and Handled.
+
+ver(S, {error, beam_lib, R}) ->
+ [S|_] = tuple_to_list(R),
+ case lists:flatten(beam_lib:format_error(R)) of
+ [${ | _] ->
+ test_server:fail({bad_format_error, R});
+ _ ->
+ ok
+ end.
+
+pps() ->
+ {erlang:ports()}.
+
+simple_file(File) ->
+ simple_file(File, simple).
+
+simple_file(File, Module) ->
+ simple_file(File, Module, member).
+
+simple_file(File, Module, make_fun) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/1]). "
+ "t(A) -> "
+ " fun(X) -> A+X end. "]),
+ ok = file:write_file(File, B);
+simple_file(File, Module, constant) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/1]). "
+ "t(A) -> "
+ " {a,b,[2,3],c,d}. "]),
+ ok = file:write_file(File, B);
+simple_file(File, Module, constant2) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/1]). "
+ "t(A) -> "
+ " {a,b,[2,3],x,y}. "]),
+ ok = file:write_file(File, B);
+simple_file(File, Module, F) ->
+ B = list_to_binary(["-module(", atom_to_list(Module), "). "
+ "-export([t/0]). "
+ "t() -> "
+ " t([]). "
+ "t(L) -> "
+ " lists:",
+ atom_to_list(F), "(a, L). "]),
+ ok = file:write_file(File, B).
+
+run_if_crypto_works(Test) ->
+ try begin crypto:start(), crypto:info(), crypto:stop(), ok end of
+ ok ->
+ Test()
+ catch
+ error:_ ->
+ {skip,"The crypto application is missing or broken"}
+ end.
+