1: %%
    2: %% %CopyrightBegin%
    3: %%
    4: %% Copyright Ericsson AB 1997-2011. All Rights Reserved.
    5: %%
    6: %% The contents of this file are subject to the Erlang Public License,
    7: %% Version 1.1, (the "License"); you may not use this file except in
    8: %% compliance with the License. You should have received a copy of the
    9: %% Erlang Public License along with this software. If not, it can be
   10: %% retrieved online at http://www.erlang.org/.
   11: %%
   12: %% Software distributed under the License is distributed on an "AS IS"
   13: %% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
   14: %% the License for the specific language governing rights and limitations
   15: %% under the License.
   16: %%
   17: %% %CopyrightEnd%
   18: %%
   19: -module(beam_lib_SUITE).
   20: 
   21: %-define(debug, true).
   22: 
   23: -ifdef(debug).
   24: -define(format(S, A), io:format(S, A)).
   25: -define(line, put(line, ?LINE), ).
   26: -define(config(X,Y), "./log_dir/").
   27: -define(t,test_server).
   28: -define(privdir, "beam_lib_SUITE_priv").
   29: -else.
   30: -include_lib("test_server/include/test_server.hrl").
   31: -define(format(S, A), ok).
   32: -define(privdir, ?config(priv_dir, Conf)).
   33: -endif.
   34: 
   35: -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, 
   36: 	 init_per_group/2,end_per_group/2, 
   37: 	 normal/1, error/1, cmp/1, cmp_literals/1, strip/1, otp_6711/1,
   38:          building/1, md5/1, encrypted_abstr/1, encrypted_abstr_file/1]).
   39: 
   40: -export([init_per_testcase/2, end_per_testcase/2]).
   41: 
   42: suite() -> [{ct_hooks,[ts_install_cth]}].
   43: 
   44: all() -> 
   45:     [error, normal, cmp, cmp_literals, strip, otp_6711,
   46:      building, md5, encrypted_abstr, encrypted_abstr_file].
   47: 
   48: groups() -> 
   49:     [].
   50: 
   51: init_per_suite(Config) ->
   52:     Config.
   53: 
   54: end_per_suite(_Config) ->
   55:     ok.
   56: 
   57: init_per_group(_GroupName, Config) ->
   58:     Config.
   59: 
   60: end_per_group(_GroupName, Config) ->
   61:     Config.
   62: 
   63: 
   64: init_per_testcase(_Case, Config) ->
   65:     Dog=?t:timetrap(?t:minutes(2)),
   66:     [{watchdog, Dog}|Config].
   67: 
   68: end_per_testcase(_Case, Config) ->
   69:     Dog=?config(watchdog, Config),
   70:     test_server:timetrap_cancel(Dog),
   71:     ok.
   72: 
   73: normal(suite) -> [];
   74: normal(doc) -> ["Read correct beam file"];
   75: normal(Conf) when is_list(Conf) ->
   76:     ?line PrivDir = ?privdir,
   77:     ?line Simple = filename:join(PrivDir, "simple"),
   78:     ?line Source = Simple ++ ".erl",
   79:     ?line BeamFile = Simple ++ ".beam",
   80:     ?line simple_file(Source),
   81: 
   82:     ?line NoOfTables = length(ets:all()),
   83:     ?line P0 = pps(),
   84: 
   85:     CompileFlags = [{outdir,PrivDir}, debug_info],
   86:     ?line {ok,_} = compile:file(Source, CompileFlags),
   87:     ?line {ok, Binary} = file:read_file(BeamFile),
   88: 
   89:     ?line do_normal(BeamFile),
   90:     ?line do_normal(Binary),
   91: 
   92:     ?line {ok,_} = compile:file(Source, [{outdir,PrivDir}, no_debug_info]),
   93:     ?line {ok, {simple, [{abstract_code, no_abstract_code}]}} = 
   94: 	beam_lib:chunks(BeamFile, [abstract_code]),
   95: 
   96:     %% ?line {ok,_} = compile:file(Source, [compressed | CompileFlags]),
   97:     %% ?line do_normal(BeamFile),
   98: 
   99:     ?line file:delete(BeamFile),
  100:     ?line file:delete(Source),
  101:     ?line NoOfTables = length(ets:all()),
  102:     ?line true = (P0 == pps()),    
  103:     ok.
  104: 
  105: do_normal(BeamFile) ->
  106:     ?line Imports = {imports, [{erlang, get_module_info, 1},
  107: 			       {erlang, get_module_info, 2},
  108: 			       {lists, member, 2}]},
  109:     ?line Exports = {exports, [{module_info, 0}, {module_info, 1}, {t, 0}]},
  110:     ?line Local = {locals, [{t, 1}]},
  111:     ?line {ok, {simple, [Imports]}} = beam_lib:chunks(BeamFile, [imports]),
  112:     ?line {ok, {simple, [{"ImpT",_Bin}]}} = 
  113: 	beam_lib:chunks(BeamFile, ["ImpT"]),
  114:     ?line {ok, {simple, [Exports]}} = beam_lib:chunks(BeamFile, [exports]),
  115:     ?line {ok, {simple, [{attributes, [{vsn, [_]}]}]}} = 
  116: 	beam_lib:chunks(BeamFile, [attributes]),
  117:     ?line {ok, {simple, [{compile_info, _}=CompileInfo]}} = 
  118: 	beam_lib:chunks(BeamFile, [compile_info]),
  119:     ?line {ok, {simple, [Local]}} = beam_lib:chunks(BeamFile, [locals]),
  120:     ?line {ok, {simple, [{attributes, [{vsn, [_]}]}, CompileInfo,
  121: 			 Exports, Imports, Local]}} =
  122: 	beam_lib:chunks(BeamFile, [attributes, compile_info, exports, imports, locals]),
  123:     ?line {ok, {simple, [{atoms, _Atoms}]}} = 
  124: 	beam_lib:chunks(BeamFile, [atoms]),
  125:     ?line {ok, {simple, [{labeled_exports, _LExports}]}} =
  126: 	beam_lib:chunks(BeamFile, [labeled_exports]),
  127:     ?line {ok, {simple, [{labeled_locals, _LLocals}]}} =
  128: 	beam_lib:chunks(BeamFile, [labeled_locals]),
  129:     ?line {ok, {simple, [_Vsn]}} = beam_lib:version(BeamFile),
  130:     ?line {ok, {simple, [{abstract_code, _}]}} = 
  131: 	beam_lib:chunks(BeamFile, [abstract_code]),
  132:     
  133:     %% Test reading optional chunks.
  134:     All = ["Atom", "Code", "StrT", "ImpT", "ExpT", "FunT", "LitT"],
  135:     ?line {ok,{simple,Chunks}} = beam_lib:chunks(BeamFile, All, [allow_missing_chunks]),
  136:     ?line verify_simple(Chunks).
  137: 
  138: verify_simple([{"Atom", AtomBin},
  139: 	       {"Code", CodeBin},
  140: 	       {"StrT", StrBin},
  141: 	       {"ImpT", ImpBin},
  142: 	       {"ExpT", ExpBin},
  143: 	       {"FunT", missing_chunk},
  144: 	       {"LitT", missing_chunk}])
  145:   when is_binary(AtomBin), is_binary(CodeBin), is_binary(StrBin),
  146:        is_binary(ImpBin), is_binary(ExpBin) ->
  147:     ok.
  148: 
  149: error(suite) -> [];
  150: error(doc) -> ["Read invalid beam files"];
  151: error(Conf) when is_list(Conf) ->
  152:     ?line PrivDir = ?privdir,
  153:     ?line Simple = filename:join(PrivDir, "simple"),
  154:     ?line Source = Simple ++ ".erl",
  155:     ?line BeamFile = Simple ++ ".beam",
  156:     ?line WrongFile = Simple ++ "foo.beam",
  157:     ?line simple_file(Source),
  158: 
  159:     ?line NoOfTables = length(ets:all()),
  160:     ?line P0 = pps(),
  161:     ?line {ok,_} = compile:file(Source, [{outdir,PrivDir},debug_info]),
  162:     ?line ACopy = filename:join(PrivDir, "a_copy.beam"),
  163:     ?line copy_file(BeamFile, ACopy),
  164: 
  165:     ?line {ok, Binary} = file:read_file(BeamFile),
  166: 
  167:     ?line copy_file(ACopy, WrongFile),
  168:     ?line verify(file_error, beam_lib:info("./does_simply_not_exist")),
  169: 
  170:     ?line do_error(BeamFile, ACopy),
  171:     ?line do_error(Binary, ACopy),
  172: 
  173:     ?line copy_file(ACopy, BeamFile),
  174:     ?line verify(unknown_chunk, beam_lib:chunks(BeamFile, [not_a_chunk])),
  175: 
  176:     ?line ok = file:write_file(BeamFile, <<>>),
  177:     ?line verify(not_a_beam_file, beam_lib:info(BeamFile)),
  178:     ?line verify(not_a_beam_file, beam_lib:info(<<>>)),
  179:     ?line ok = file:write_file(BeamFile, <<"short">>),
  180:     ?line verify(not_a_beam_file, beam_lib:info(BeamFile)),
  181:     ?line verify(not_a_beam_file, beam_lib:info(<<"short">>)),
  182: 
  183:     ?line {Binary1, _} = split_binary(Binary, byte_size(Binary)-10),
  184:     LastChunk = last_chunk(Binary),
  185:     ?line verify(chunk_too_big, beam_lib:chunks(Binary1, [LastChunk])),
  186:     ?line Chunks = chunk_info(Binary),
  187:     ?line {value, {_, AbstractStart, _}} = lists:keysearch("Abst", 1, Chunks),
  188:     ?line {Binary2, _} = split_binary(Binary, AbstractStart),
  189:     ?line verify(chunk_too_big, beam_lib:chunks(Binary2, ["Abst"])),
  190:     ?line {Binary3, _} = split_binary(Binary, AbstractStart-4),
  191:     ?line verify(invalid_beam_file, beam_lib:chunks(Binary3, ["Abst"])),
  192: 
  193:     %% Instead of the 5:32 field below, there used to be control characters
  194:     %% (including zero bytes) directly in the string. Because inferior programs
  195:     %% such as sed and clearcasediff don't like zero bytes in text files,
  196:     %% we have eliminated them.
  197:     ?line ok = file:write_file(BeamFile, <<"FOR1",5:32,"BEAMfel">>),
  198: %   ?line verify(invalid_beam_file, beam_lib:info(BeamFile)),
  199: %   ?line verify(invalid_beam_file, beam_lib:info(<<"FOR1",5:32,"BEAMfel">>)),
  200: 
  201:     ?line NoOfTables = length(ets:all()),
  202:     ?line true = (P0 == pps()),    
  203:     ?line file:delete(Source),
  204:     ?line file:delete(WrongFile),
  205:     ?line file:delete(BeamFile),
  206:     ?line file:delete(ACopy),
  207:     ok.
  208: 
  209: last_chunk(Bin) ->
  210:     L = beam_lib:info(Bin),
  211:     {chunks,Chunks} = lists:keyfind(chunks, 1, L),
  212:     {Last,_,_} = lists:last(Chunks),
  213:     Last.
  214: 
  215: do_error(BeamFile, ACopy) ->
  216:     % evil tests
  217:     ?line Chunks = chunk_info(BeamFile),
  218:     ?line {value, {_, AtomStart, _}} = lists:keysearch("Atom", 1, Chunks),
  219:     ?line {value, {_, ImportStart, _}} = lists:keysearch("ImpT", 1, Chunks),
  220:     ?line {value, {_, AbstractStart, _}} = lists:keysearch("Abst", 1, Chunks),
  221:     ?line {value, {_, AttributesStart, _}} = 
  222: 	lists:keysearch("Attr", 1, Chunks),
  223:     ?line {value, {_, CompileInfoStart, _}} = 
  224: 	lists:keysearch("CInf", 1, Chunks),
  225:     ?line verify(missing_chunk, beam_lib:chunks(BeamFile, ["__"])),
  226:     ?line BF2 = set_byte(ACopy, BeamFile, ImportStart+4, 17),
  227:     ?line verify(invalid_chunk, beam_lib:chunks(BF2, [imports])),
  228:     ?line BF3 = set_byte(ACopy, BeamFile, AtomStart-6, 17),
  229:     ?line verify(missing_chunk, beam_lib:chunks(BF3, [imports])),
  230:     ?line BF4 = set_byte(ACopy, BeamFile, AbstractStart+10, 17),
  231:     ?line verify(invalid_chunk, beam_lib:chunks(BF4, [abstract_code])),
  232:     ?line BF5 = set_byte(ACopy, BeamFile, AttributesStart+10, 17),
  233:     ?line verify(invalid_chunk, beam_lib:chunks(BF5, [attributes])),
  234: 
  235:     ?line BF6 = set_byte(ACopy, BeamFile, 1, 17),
  236:     ?line verify(not_a_beam_file, beam_lib:info(BF6)),
  237:     ?line BF7 = set_byte(ACopy, BeamFile, 9, 17),
  238:     ?line verify(not_a_beam_file, beam_lib:info(BF7)),
  239: 
  240:     ?line BF8 = set_byte(ACopy, BeamFile, 13, 17),
  241:     ?line verify(missing_chunk, beam_lib:chunks(BF8, ["Atom"])),
  242:     
  243:     ?line BF9 = set_byte(ACopy, BeamFile, CompileInfoStart+10, 17),
  244:     ?line verify(invalid_chunk, beam_lib:chunks(BF9, [compile_info])).
  245:     
  246: 
  247: cmp(suite) -> [];
  248: cmp(doc) -> ["Compare contents of BEAM files and directories"];
  249: cmp(Conf) when is_list(Conf) ->
  250:     ?line PrivDir = ?privdir,
  251: 
  252:     ?line Dir1 = filename:join(PrivDir, "dir1"),
  253:     ?line Dir2 = filename:join(PrivDir, "dir2"),
  254: 
  255:     ok = file:make_dir(Dir1),
  256:     ok = file:make_dir(Dir2),
  257: 
  258:     ?line {SourceD1, BeamFileD1} = make_beam(Dir1, simple, member),
  259:     ?line {Source2D1, BeamFile2D1} = make_beam(Dir1, simple2, concat),
  260:     ?line {SourceD2, BeamFileD2} = make_beam(Dir2, simple, concat),
  261: 
  262:     ?line NoOfTables = length(ets:all()),
  263:     ?line P0 = pps(),
  264: 
  265:     %% cmp
  266:     ?line ok = beam_lib:cmp(BeamFileD1, BeamFileD1),
  267:     ?line ver(modules_different, beam_lib:cmp(BeamFileD1, BeamFile2D1)),
  268:     ?line ver(chunks_different, beam_lib:cmp(BeamFileD1, BeamFileD2)),
  269:     ?line verify(file_error, beam_lib:cmp(foo, bar)),
  270:     
  271:     ?line {ok, B1} = file:read_file(BeamFileD1),
  272:     ?line ok = beam_lib:cmp(B1, BeamFileD1),
  273:     ?line {ok, B2} = file:read_file(BeamFileD2),
  274:     ?line ver(chunks_different, beam_lib:cmp(B1, B2)),
  275: 
  276:     %% cmp_dirs
  277:     ?line {[],[],[]} = beam_lib:cmp_dirs(Dir1, Dir1),
  278:     ?line true = {[BeamFile2D1], [], [{BeamFileD1,BeamFileD2}]} ==
  279: 	         beam_lib:cmp_dirs(Dir1, Dir2),
  280:     ?line true = {[], [BeamFile2D1], [{BeamFileD2,BeamFileD1}]} ==
  281:                  beam_lib:cmp_dirs(Dir2, Dir1),
  282:     ?line ver(not_a_directory, beam_lib:cmp_dirs(foo, bar)),
  283:     
  284:     %% diff_dirs
  285:     ?line ok = beam_lib:diff_dirs(Dir1, Dir1),
  286:     ?line ver(not_a_directory, beam_lib:diff_dirs(foo, bar)),
  287: 
  288:     ?line true = (P0 == pps()),
  289:     ?line NoOfTables = length(ets:all()),
  290:     ?line delete_files([SourceD1, BeamFileD1, Source2D1, 
  291: 			BeamFile2D1, SourceD2, BeamFileD2]),
  292: 
  293:     file:del_dir(Dir1),
  294:     file:del_dir(Dir2),
  295:     ok.
  296: 
  297: cmp_literals(suite) -> [];
  298: cmp_literals(doc) -> ["Compare contents of BEAM files having literals"];
  299: cmp_literals(Conf) when is_list(Conf) ->
  300:     ?line PrivDir = ?privdir,
  301: 
  302:     ?line Dir1 = filename:join(PrivDir, "dir1"),
  303:     ?line Dir2 = filename:join(PrivDir, "dir2"),
  304: 
  305:     ok = file:make_dir(Dir1),
  306:     ok = file:make_dir(Dir2),
  307: 
  308:     ?line {SourceD1, BeamFileD1} = make_beam(Dir1, simple, constant),
  309:     ?line {SourceD2, BeamFileD2} = make_beam(Dir2, simple, constant2),
  310: 
  311:     ?line NoOfTables = length(ets:all()),
  312:     ?line P0 = pps(),
  313: 
  314:     %% cmp
  315:     ?line ok = beam_lib:cmp(BeamFileD1, BeamFileD1),
  316:     ?line ver(chunks_different, beam_lib:cmp(BeamFileD1, BeamFileD2)),
  317:     
  318:     ?line {ok, B1} = file:read_file(BeamFileD1),
  319:     ?line ok = beam_lib:cmp(B1, BeamFileD1),
  320:     ?line {ok, B2} = file:read_file(BeamFileD2),
  321:     ?line ver(chunks_different, beam_lib:cmp(B1, B2)),
  322: 
  323:     ?line true = (P0 == pps()),
  324:     ?line NoOfTables = length(ets:all()),
  325: 
  326:     ?line delete_files([SourceD1, BeamFileD1, SourceD2, BeamFileD2]),
  327: 
  328:     file:del_dir(Dir1),
  329:     file:del_dir(Dir2),
  330:     ok.
  331: 
  332: strip(suite) -> [];
  333: strip(doc) -> ["Strip BEAM files"];
  334: strip(Conf) when is_list(Conf) ->
  335:     ?line PrivDir = ?privdir,
  336:     ?line {SourceD1, BeamFileD1} = make_beam(PrivDir, simple, member),
  337:     ?line {Source2D1, BeamFile2D1} = make_beam(PrivDir, simple2, concat),
  338:     ?line {Source3D1, BeamFile3D1} = make_beam(PrivDir, make_fun, make_fun),
  339:     ?line {Source4D1, BeamFile4D1} = make_beam(PrivDir, constant, constant),
  340:     ?line {Source5D1, BeamFile5D1} = make_beam(PrivDir, lines, lines),
  341: 
  342:     ?line NoOfTables = length(ets:all()),
  343:     ?line P0 = pps(),
  344: 
  345:     %% strip binary
  346:     ?line verify(not_a_beam_file, beam_lib:strip(<<>>)),
  347:     ?line {ok, B1} = file:read_file(BeamFileD1),
  348:     ?line {ok, {simple, NB1}} = beam_lib:strip(B1),
  349:     ?line BId1 = chunk_ids(B1),
  350:     ?line NBId1 = chunk_ids(NB1),
  351:     ?line true = length(BId1) > length(NBId1),
  352:     ?line compare_chunks(B1, NB1, NBId1),
  353: 
  354:     %% strip file
  355:     ?line verify(file_error, beam_lib:strip(foo)),
  356:     ?line {ok, {simple, _}} = beam_lib:strip(BeamFileD1),
  357:     ?line compare_chunks(NB1, BeamFileD1, NBId1),
  358: 
  359:     %% strip_files
  360:     ?line {ok, B2} = file:read_file(BeamFile2D1),
  361:     ?line {ok, [{simple,_},{simple2,_}]} = beam_lib:strip_files([B1, B2]),
  362:     ?line {ok, [{simple,_},{simple2,_},{make_fun,_},{constant,_}]} = 
  363: 	beam_lib:strip_files([BeamFileD1, BeamFile2D1, BeamFile3D1, BeamFile4D1]),
  364: 
  365:     %% check that each module can be loaded.
  366:     ?line {module, simple} = code:load_abs(filename:rootname(BeamFileD1)),
  367:     ?line {module, simple2} = code:load_abs(filename:rootname(BeamFile2D1)),
  368:     ?line {module, make_fun} = code:load_abs(filename:rootname(BeamFile3D1)),
  369:     ?line {module, constant} = code:load_abs(filename:rootname(BeamFile4D1)),
  370: 
  371:     %% check that line number information is still present after stripping
  372:     ?line {module, lines} = code:load_abs(filename:rootname(BeamFile5D1)),
  373:     ?line {'EXIT',{badarith,[{lines,t,1,Info}|_]}} =
  374: 	(catch lines:t(atom)),
  375:     ?line true = code:delete(lines),
  376:     ?line false = code:purge(lines),
  377:     ?line {ok, {lines,BeamFile5D1}} = beam_lib:strip(BeamFile5D1),
  378:     ?line {module, lines} = code:load_abs(filename:rootname(BeamFile5D1)),
  379:     ?line {'EXIT',{badarith,[{lines,t,1,Info}|_]}} =
  380: 	(catch lines:t(atom)),
  381: 
  382:     ?line true = (P0 == pps()),
  383:     ?line NoOfTables = length(ets:all()),
  384: 
  385:     ?line delete_files([SourceD1, BeamFileD1, 
  386: 			Source2D1, BeamFile2D1, 
  387: 			Source3D1, BeamFile3D1,
  388: 			Source4D1, BeamFile4D1,
  389: 			Source5D1, BeamFile5D1]),
  390:     ok.
  391: 
  392: 
  393: otp_6711(Conf) when is_list(Conf) ->
  394:     ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:info(3)}),
  395:     ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:chunks(a, b)}),
  396:     ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:chunks(a,b,c)}),
  397:     ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:all_chunks(3)}),
  398:     ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:cmp(3,4)}),
  399:     ?line {'EXIT',{function_clause,_}} = (catch {a, beam_lib:strip(3)}),
  400:     ?line {'EXIT',{function_clause,_}} = 
  401:         (catch {a, beam_lib:strip_files([3])}),
  402: 
  403:     ?line PrivDir = ?privdir,
  404:     ?line Dir = filename:join(PrivDir, "dir"),
  405:     ?line Lib = filename:join(Dir, "lib"),
  406:     ?line App = filename:join(Lib, "app"),
  407:     ?line EBin = filename:join(App, "ebin"),
  408: 
  409:     ok = file:make_dir(Dir),
  410:     ok = file:make_dir(Lib),
  411:     ok = file:make_dir(App),
  412:     ok = file:make_dir(EBin),
  413:     
  414:     ?line {SourceD, BeamFileD} = make_beam(EBin, simple, member),
  415: 
  416:     unwritable(BeamFileD),
  417: 
  418:     %% There is no way that strip_release can fail with
  419:     %% function_clause or something like that...
  420:     ?line {error,_,{file_error,_,_}} = beam_lib:strip_release(Dir),
  421: 
  422:     ?line delete_files([SourceD, BeamFileD]),
  423:     file:del_dir(EBin),
  424:     file:del_dir(App),
  425:     file:del_dir(Lib),
  426:     file:del_dir(Dir),
  427:     ok.
  428: 
  429: -include_lib("kernel/include/file.hrl").
  430: 
  431: unwritable(Fname) ->
  432:     {ok, Info} = file:read_file_info(Fname),
  433:     Mode = Info#file_info.mode - 8#00200,
  434:     file:write_file_info(Fname, Info#file_info{mode = Mode}).
  435: 
  436: building(doc) -> "Testing building of BEAM files.";
  437: building(Conf) when is_list(Conf) ->
  438:     ?line PrivDir = ?privdir,
  439: 
  440:     ?line Dir1 = filename:join(PrivDir, "b_dir1"),
  441:     ?line Dir2 = filename:join(PrivDir, "b_dir2"),
  442: 
  443:     ok = file:make_dir(Dir1),
  444:     ok = file:make_dir(Dir2),
  445: 
  446:     ?line {SourceD1, BeamFileD1} = make_beam(Dir1, building, member),
  447: 
  448:     ?line NoOfTables = length(ets:all()),
  449:     ?line P0 = pps(),
  450: 
  451:     %% read all chunks
  452:     ?line ChunkIds = chunk_ids(BeamFileD1),
  453:     ?line {ok, _Mod, Chunks} = beam_lib:all_chunks(BeamFileD1),
  454:     ?line ChunkIds = lists:map(fun ({Id, Data}) when is_binary(Data) -> Id 
  455:                                end, Chunks),
  456: 
  457:     %% write a new beam file, with reversed chunk order
  458:     ?line BeamFileD2 = filename:join(Dir2, "building.beam"),
  459:     ?line {ok,RevBeam} = beam_lib:build_module(lists:reverse(Chunks)),
  460:     ?line file:write_file(BeamFileD2, RevBeam),
  461: 
  462:     %% compare files
  463:     ?line compare_chunks(BeamFileD1, BeamFileD2, ChunkIds),
  464: 
  465:     %% test that we can retrieve a chunk before the atom table
  466:     %% (actually, try to retrieve all chunks)
  467: 
  468:     ?line lists:foreach(fun(Id) ->
  469: 				{ok, {building, [{Id, _Data}]}} =
  470: 				    beam_lib:chunks(BeamFileD1, [Id])
  471: 			end, ChunkIds),
  472:     ?line lists:foreach(fun(Id) ->
  473: 				{ok, {building, [{Id, _Data}]}} =
  474: 				    beam_lib:chunks(BeamFileD2, [Id])
  475: 			end, ChunkIds),
  476: 
  477:     ?line true = (P0 == pps()),
  478:     ?line NoOfTables = length(ets:all()),
  479: 
  480:     ?line delete_files([SourceD1, BeamFileD1, BeamFileD2]),
  481:     file:del_dir(Dir1),
  482:     file:del_dir(Dir2),
  483:     ok.
  484: 
  485: md5(suite) -> [];
  486: md5(doc) -> ["Compare beam_lib:md5/1 and code:module_md5/1."];
  487: md5(Conf) when is_list(Conf) ->
  488:     ?line Beams = collect_beams(),
  489:     io:format("Found ~w beam files", [length(Beams)]),
  490:     md5_1(Beams).
  491: 
  492: md5_1([N|Ns]) ->
  493:     {ok,Beam0} = file:read_file(N),
  494:     Beam = maybe_uncompress(Beam0),
  495:     {ok,{Mod,MD5}} = beam_lib:md5(Beam),
  496:     {Mod,MD5} = {Mod,code:module_md5(Beam)},
  497:     md5_1(Ns);
  498: md5_1([]) -> ok.
  499:      
  500: collect_beams() ->
  501:     SuperDir = filename:dirname(filename:dirname(code:which(?MODULE))),
  502:     TestDirs = filelib:wildcard(filename:join([SuperDir,"*_test"])),
  503:     AbsDirs = [filename:absname(X) || X <- code:get_path()],
  504:     collect_beams_1(AbsDirs ++ TestDirs).
  505: 
  506: collect_beams_1([Dir|Dirs]) ->
  507:     filelib:wildcard(filename:join(Dir, "*.beam")) ++ collect_beams_1(Dirs);
  508: collect_beams_1([]) -> [].
  509: 
  510: maybe_uncompress(<<"FOR1",_/binary>>=Beam) -> Beam;
  511: maybe_uncompress(Beam) -> zlib:gunzip(Beam).
  512: 
  513: encrypted_abstr(suite) -> [];
  514: encrypted_abstr(doc) -> ["Test encrypted abstract format"];
  515: encrypted_abstr(Conf) when is_list(Conf) ->
  516:     run_if_crypto_works(fun() -> encrypted_abstr_1(Conf) end).
  517: 
  518: encrypted_abstr_1(Conf) ->
  519:     ?line PrivDir = ?privdir,
  520:     ?line Simple = filename:join(PrivDir, "simple"),
  521:     ?line Source = Simple ++ ".erl",
  522:     ?line BeamFile = Simple ++ ".beam",
  523:     ?line simple_file(Source),
  524: 
  525:     %% Avoid getting an extra port when crypto starts erl_ddll.
  526:     ?line erl_ddll:start(),
  527: 
  528:     ?line NoOfTables = length(ets:all()),
  529:     ?line P0 = pps(),
  530: 
  531:     Key = "#a_crypto_key",
  532:     CompileFlags = [{outdir,PrivDir}, debug_info, {debug_info_key,Key}],
  533:     ?line {ok,_} = compile:file(Source, CompileFlags),
  534:     ?line {ok, Binary} = file:read_file(BeamFile),
  535: 
  536:     ?line do_encrypted_abstr(BeamFile, Key),
  537:     ?line do_encrypted_abstr(Binary, Key),
  538: 
  539:     ?line ok = crypto:stop(),			%To get rid of extra ets tables.
  540:     ?line file:delete(BeamFile),
  541:     ?line file:delete(Source),
  542:     ?line NoOfTables = length(ets:all()),
  543:     ?line true = (P0 == pps()),    
  544:     ok.
  545: 
  546: do_encrypted_abstr(Beam, Key) ->
  547:     ?line verify(key_missing_or_invalid, beam_lib:chunks(Beam, [abstract_code])),
  548: 
  549:     %% The raw chunk "Abst" can still be read even without a key.
  550:     ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
  551:     ?line <<0:8,8:8,"des3_cbc",_/binary>> = Abst,
  552: 
  553:     %% Try som invalid funs.
  554:     ?line bad_fun(badfun, fun() -> ok end),
  555:     ?line bad_fun(badfun, {a,b}),
  556:     ?line bad_fun(blurf),
  557:     ?line {function_clause,_} = bad_fun(fun(glurf) -> ok end),
  558: 
  559:     %% Funs that return something strange.
  560:     ?line bad_fun(badfun, fun(init) -> {ok,fun() -> ok end} end),
  561:     ?line glurf = bad_fun(fun(init) -> {error,glurf} end),
  562: 
  563:     %% Try clearing (non-existing fun).
  564:     ?line undefined = beam_lib:clear_crypto_key_fun(),
  565: 
  566:     %% Install a fun which cannot retrieve a key.
  567:     ?line ok = beam_lib:crypto_key_fun(fun(init) -> ok end),
  568:     ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
  569: 
  570:     %% Install a fun which returns an incorrect key.
  571:     ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
  572:     ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun("wrong key...")),
  573:     ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
  574:     
  575:     %% Installing a new key fun is not possible without clearing the old.
  576:     ?line verify(exists, beam_lib:crypto_key_fun(simple_crypto_fun(Key))),
  577: 
  578:     %% Install the simplest possible working key fun.
  579:     ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
  580:     ?line ok = beam_lib:crypto_key_fun(simple_crypto_fun(Key)),
  581:     ?line verify_abstract(Beam),
  582:     ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
  583: 
  584:     %% Installing a new key fun is not possible without clearing the old.
  585:     verify(exists, beam_lib:crypto_key_fun(ets_crypto_fun(Key))),
  586: 
  587:     %% Install a key using an ets table.
  588:     ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
  589:     ?line ok = beam_lib:crypto_key_fun(ets_crypto_fun(Key)),
  590:     ?line verify_abstract(Beam),
  591:     ?line {ok,{simple,[{"Abst",Abst}]}} = beam_lib:chunks(Beam, ["Abst"]),
  592: 
  593:     ?line {ok,cleared} = beam_lib:clear_crypto_key_fun(),
  594: 
  595:     %% Try to force a stop/start race.
  596:     ?line start_stop_race(10000),
  597: 
  598:     ok.
  599: 
  600: start_stop_race(0) ->
  601:     ok;
  602: start_stop_race(N) ->
  603:     {error,_} = beam_lib:crypto_key_fun(bad_fun),
  604:     undefined = beam_lib:clear_crypto_key_fun(),
  605:     start_stop_race(N-1).
  606: 
  607: bad_fun(F) ->
  608:     {error,E} = beam_lib:crypto_key_fun(F),
  609:     E.
  610: 
  611: bad_fun(S, F) ->
  612:     verify(S, beam_lib:crypto_key_fun(F)).
  613: 
  614: 
  615: verify_abstract(Beam) ->
  616:     {ok,{simple,[Chunk]}} = beam_lib:chunks(Beam, [abstract_code]),
  617:     {abstract_code,{raw_abstract_v1,_}} = Chunk.
  618: 
  619: simple_crypto_fun(Key) ->
  620:     fun(init) -> ok;
  621:        ({debug_info, des3_cbc, simple, _}) -> Key
  622:     end.
  623: 
  624: ets_crypto_fun(Key) ->
  625:     fun(init) ->
  626: 	    T = ets:new(beam_lib_SUITE_keys, [private, set]),
  627: 	    true = ets:insert(T, {key,Key}),
  628: 	    {ok,fun({debug_info, des3_cbc, simple, _}) ->
  629: 			[{key,Val}] = ets:lookup(T, key),
  630: 			Val;
  631: 		   (clear) ->
  632: 			ets:delete(T),
  633: 			cleared
  634: 		end}
  635:     end.
  636: 
  637: encrypted_abstr_file(suite) -> [];
  638: encrypted_abstr_file(doc) ->
  639:     ["Test encrypted abstract format with the key in .erlang.crypt"];
  640: encrypted_abstr_file(Conf) when is_list(Conf) ->
  641:     run_if_crypto_works(fun() -> encrypted_abstr_file_1(Conf) end).
  642: 
  643: encrypted_abstr_file_1(Conf) ->
  644:     ?line PrivDir = ?privdir,
  645:     ?line Simple = filename:join(PrivDir, "simple"),
  646:     ?line Source = Simple ++ ".erl",
  647:     ?line BeamFile = Simple ++ ".beam",
  648:     ?line simple_file(Source),
  649: 
  650:     %% Avoid getting an extra port when crypto starts erl_ddll.
  651:     ?line erl_ddll:start(),
  652: 
  653:     ?line NoOfTables = length(ets:all()),
  654:     ?line P0 = pps(),
  655: 
  656:     Key = "Long And niCe 99Krypto Key",
  657:     CompileFlags = [{outdir,PrivDir}, debug_info, {debug_info_key,Key}],
  658:     ?line {ok,_} = compile:file(Source, CompileFlags),
  659:     ?line {ok, Binary} = file:read_file(BeamFile),
  660: 
  661:     ?line {ok,OldCwd} = file:get_cwd(),
  662:     ?line ok = file:set_cwd(PrivDir),
  663:     ?line do_encrypted_abstr_file(BeamFile, Key),
  664:     ?line do_encrypted_abstr_file(Binary, Key),
  665:     ?line ok = file:set_cwd(OldCwd),
  666: 
  667:     ?line ok = crypto:stop(),			%To get rid of extra ets tables.
  668:     ?line file:delete(filename:join(PrivDir, ".erlang.crypt")),
  669:     ?line file:delete(BeamFile),
  670:     ?line file:delete(Source),
  671:     ?line NoOfTables = length(ets:all()),
  672:     ?line true = (P0 == pps()),    
  673:     ok.
  674: 
  675: do_encrypted_abstr_file(Beam, Key) ->
  676:     %% No key.
  677:     ?line write_crypt_file(""),
  678:     ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
  679: 
  680:     %% A wrong key.
  681:     ?line write_crypt_file(["[{debug_info,des3_cbc,simple,\"A Wrong Key\"}].\n"]),
  682:     ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
  683: 
  684:     %% Write correct key...
  685:     ?line write_crypt_file(["[{debug_info,des3_cbc,simple,\"",Key,"\"}].\n"]),
  686: 
  687:     %% ... but the fun with the wrong key is still there.
  688:     ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
  689: 
  690:     %% Clear the fun. Now it should work.
  691:     ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
  692:     ?line verify_abstract(Beam),
  693:     ?line verify_abstract(Beam),
  694:     ?line ok = file:delete(".erlang.crypt"),
  695:     ?line verify_abstract(Beam),
  696: 
  697:     %% Clear, otherwise the second pass will fail.
  698:     ?line {ok,_} = beam_lib:clear_crypto_key_fun(),
  699:     ?line {error,beam_lib,Error} = beam_lib:chunks(Beam, [abstract_code]),
  700:     ok.
  701: 
  702: write_crypt_file(Contents0) ->
  703:     Contents = list_to_binary([Contents0]),
  704:     io:format("~s\n", [binary_to_list(Contents)]),
  705:     ok = file:write_file(".erlang.crypt", Contents).
  706: 
  707: compare_chunks(File1, File2, ChunkIds) ->
  708:     ?line {ok, {_, Chunks1}} = beam_lib:chunks(File1, ChunkIds),
  709:     ?line {ok, {_, Chunks2}} = beam_lib:chunks(File2, ChunkIds),
  710:     ?line true = Chunks1 == Chunks2.
  711: 
  712: chunk_ids(File) ->
  713:     ?line lists:map(fun({Id,_Start,_Size}) -> Id end, chunk_info(File)).
  714:     
  715: chunk_info(File) ->
  716:     ?line {value, {chunks, Chunks}} = 
  717: 	lists:keysearch(chunks, 1, beam_lib:info(File)),
  718:     Chunks.
  719:     
  720: make_beam(Dir, Module, F) ->
  721:     ?line FileBase = filename:join(Dir, atom_to_list(Module)),
  722:     ?line Source = FileBase ++ ".erl",
  723:     ?line BeamFile = FileBase ++ ".beam",
  724:     ?line simple_file(Source, Module, F),
  725:     ?line {ok, _} = compile:file(Source, [{outdir,Dir}, debug_info, report]),
  726:     {Source, BeamFile}.
  727: 
  728: set_byte(_Backup, Binary, Pos, Byte) when is_binary(Binary) ->
  729:     ?line <<B1:Pos/binary, _:1/binary, B2/binary>> = Binary,
  730:     NB = <<B1/binary, Byte:8, B2/binary>>,
  731:     NB;
  732: set_byte(Backup, File, Pos, Byte) ->
  733:     ?line copy_file(Backup, File),
  734:     ?line set_byte(File, Pos, Byte),
  735:     File.
  736: 
  737: set_byte(File, Pos, Byte) ->
  738:     ?line {ok, Fd} = file:open(File, [read, write]),
  739:     ?line {ok, _} = file:position(Fd, Pos),
  740:     ?line ok = file:write(Fd, [Byte]),
  741:     ?line file:close(Fd).
  742: 
  743: copy_file(Src, Dest) ->
  744:     % ?t:format("copying from ~p to ~p~n", [Src, Dest]),
  745:     ?line {ok, _} = file:copy(Src, Dest),
  746:     ?line ok = file:change_mode(Dest, 8#0666).
  747: 
  748: delete_files(Files) ->
  749:     lists:foreach(fun(F) -> file:delete(F) end, Files).
  750: 
  751: verify(S, {error, beam_lib, R}) ->
  752:     verify_error(S, R);
  753: verify(S, {error, R}) ->
  754:     verify_error(S, R).
  755: 
  756: verify_error(S, R) ->
  757:     if
  758: 	S =:= R -> ok;
  759: 	true -> [S|_] = tuple_to_list(R)
  760:     end,
  761: 
  762:     %% Most formatted messages begin with "./simple.beam:" or "<<...".
  763:     FM = string:str(lists:flatten(beam_lib:format_error(R)), "simpl") > 0,
  764:     BM = string:str(lists:flatten(beam_lib:format_error(R)), "<<") > 0,
  765: 
  766:     %% Also make sure that formatted message is not just the term printed.
  767:     Handled = beam_lib:format_error(R) =/= io_lib:format("~p~n", [R]),
  768:     true = ((FM > 0) or (BM > 0)) and Handled.
  769: 
  770: ver(S, {error, beam_lib, R}) ->
  771:     [S|_] = tuple_to_list(R),
  772:     case lists:flatten(beam_lib:format_error(R)) of
  773: 	[${ | _] ->
  774: 	    test_server:fail({bad_format_error, R});
  775: 	_ ->
  776: 	    ok
  777:     end.
  778: 
  779: pps() ->
  780:     {erlang:ports()}.
  781: 
  782: simple_file(File) ->
  783:     simple_file(File, simple).
  784: 
  785: simple_file(File, Module) ->
  786:     simple_file(File, Module, member).
  787: 
  788: simple_file(File, Module, make_fun) ->
  789:     B = list_to_binary(["-module(", atom_to_list(Module), "). "
  790: 			"-export([t/1]). "
  791: 			"t(A) -> "
  792: 			"    fun(X) -> A+X end. "]),
  793:     ok = file:write_file(File, B);
  794: simple_file(File, Module, constant) ->
  795:     B = list_to_binary(["-module(", atom_to_list(Module), "). "
  796: 			"-export([t/1]). "
  797: 			"t(A) -> "
  798: 			"    {a,b,[2,3],c,d}. "]),
  799:     ok = file:write_file(File, B);
  800: simple_file(File, Module, constant2) ->
  801:     B = list_to_binary(["-module(", atom_to_list(Module), "). "
  802: 			"-export([t/1]). "
  803: 			"t(A) -> "
  804: 			"    {a,b,[2,3],x,y}. "]),
  805:     ok = file:write_file(File, B);
  806: simple_file(File, Module, lines) ->
  807:     B = list_to_binary(["-module(", atom_to_list(Module), ").\n"
  808: 			"-export([t/1]).\n"
  809: 			"t(A) ->\n"
  810: 			"    A+1.\n"]),
  811:     ok = file:write_file(File, B);
  812: simple_file(File, Module, F) ->
  813:     B = list_to_binary(["-module(", atom_to_list(Module), "). "
  814: 			"-export([t/0]). "
  815: 			"t() -> "
  816: 			"    t([]). "
  817: 			"t(L) -> "
  818: 			"    lists:", 
  819: 			atom_to_list(F), "(a, L). "]),
  820:     ok = file:write_file(File, B).
  821: 
  822: run_if_crypto_works(Test) ->
  823:     try	begin crypto:start(), crypto:info(), crypto:stop(), ok end of
  824: 	ok ->
  825: 	    Test()
  826:     catch
  827: 	error:_ ->
  828: 	    {skip,"The crypto application is missing or broken"}
  829:     end.
  830: