1%% 2%% %CopyrightBegin% 3%% 4%% Copyright Ericsson AB 1999-2020. All Rights Reserved. 5%% 6%% Licensed under the Apache License, Version 2.0 (the "License"); 7%% you may not use this file except in compliance with the License. 8%% You may obtain a copy of the License at 9%% 10%% http://www.apache.org/licenses/LICENSE-2.0 11%% 12%% Unless required by applicable law or agreed to in writing, software 13%% distributed under the License is distributed on an "AS IS" BASIS, 14%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15%% See the License for the specific language governing permissions and 16%% limitations under the License. 17%% 18%% %CopyrightEnd% 19%% 20 21-module(code_SUITE). 22-export([all/0, suite/0, init_per_suite/1, end_per_suite/1, 23 versions/1,new_binary_types/1, 24 bad_beam_file/1, 25 literal_leak/1, 26 call_purged_fun_code_gone/1, 27 call_purged_fun_code_reload/1, 28 call_purged_fun_code_there/1, 29 multi_proc_purge/1, t_check_old_code/1, 30 external_fun/1,get_chunk/1,module_md5/1, 31 constant_pools/1,constant_refc_binaries/1, 32 fake_literals/1, 33 false_dependency/1,coverage/1,fun_confusion/1, 34 t_copy_literals/1, t_copy_literals_frags/1, 35 erl_544/1, max_heap_size/1, 36 check_process_code_signal_order/1, 37 check_process_code_dirty_exec_proc/1]). 38 39-define(line_trace, 1). 40-include_lib("common_test/include/ct.hrl"). 41 42suite() -> [{ct_hooks,[ts_install_cth]}]. 43 44all() -> 45 [versions, new_binary_types, 46 bad_beam_file, literal_leak, 47 call_purged_fun_code_gone, 48 call_purged_fun_code_reload, call_purged_fun_code_there, 49 multi_proc_purge, t_check_old_code, external_fun, get_chunk, 50 module_md5, 51 constant_pools, constant_refc_binaries, fake_literals, 52 false_dependency, 53 coverage, fun_confusion, t_copy_literals, t_copy_literals_frags, 54 erl_544, max_heap_size, check_process_code_signal_order, 55 check_process_code_dirty_exec_proc]. 56 57init_per_suite(Config) -> 58 erts_debug:set_internal_state(available_internal_state, true), 59 Config. 60 61end_per_suite(_Config) -> 62 catch erts_debug:set_internal_state(available_internal_state, false), 63 ok. 64 65%% Make sure that only two versions of a module can be loaded. 66versions(Config) when is_list(Config) -> 67 V1 = compile_version(1, Config), 68 V2 = compile_version(2, Config), 69 V3 = compile_version(3, Config), 70 71 {ok,P1,1} = load_version(V1, 1), 72 {ok,P2,2} = load_version(V2, 2), 73 {error,not_purged} = load_version(V2, 2), 74 {error,not_purged} = load_version(V3, 3), 75 76 1 = check_version(P1), 77 2 = check_version(P2), 78 2 = versions:version(), 79 80 %% Kill processes, unload code. 81 _ = monitor(process, P1), 82 _ = monitor(process, P2), 83 P1 ! P2 ! done, 84 receive 85 {'DOWN',_,process,P1,normal} -> ok 86 end, 87 receive 88 {'DOWN',_,process,P2,normal} -> ok 89 end, 90 true = erlang:purge_module(versions), 91 true = erlang:delete_module(versions), 92 true = erlang:purge_module(versions), 93 ok. 94 95compile_version(Version, Config) -> 96 Data = proplists:get_value(data_dir, Config), 97 File = filename:join(Data, "versions"), 98 {ok,versions,Bin} = compile:file(File, [{d,'VERSION',Version}, 99 binary,report]), 100 Bin. 101 102load_version(Code, Ver) -> 103 case erlang:load_module(versions, Code) of 104 {module,versions} -> 105 Pid = spawn_link(versions, loop, []), 106 Ver = versions:version(), 107 Ver = check_version(Pid), 108 {ok,Pid,Ver}; 109 Error -> 110 Error 111 end. 112 113check_version(Pid) -> 114 Pid ! {self(),version}, 115 receive 116 {Pid,version,Version} -> 117 Version 118 end. 119 120new_binary_types(Config) when is_list(Config) -> 121 Data = proplists:get_value(data_dir, Config), 122 File = filename:join(Data, "my_code_test"), 123 {ok,my_code_test,Bin} = compile:file(File, [binary]), 124 {module,my_code_test} = erlang:load_module(my_code_test, 125 make_sub_binary(Bin)), 126 true = erlang:delete_module(my_code_test), 127 true = erlang:purge_module(my_code_test), 128 129 {module,my_code_test} = erlang:load_module(my_code_test, 130 make_unaligned_sub_binary(Bin)), 131 true = erlang:delete_module(my_code_test), 132 true = erlang:purge_module(my_code_test), 133 134 %% Try heap binaries and bad binaries. 135 {error,badfile} = erlang:load_module(my_code_test, <<1,2>>), 136 {error,badfile} = erlang:load_module(my_code_test, 137 make_sub_binary(<<1,2>>)), 138 {error,badfile} = erlang:load_module(my_code_test, 139 make_unaligned_sub_binary(<<1,2>>)), 140 {'EXIT',{badarg,_}} = (catch erlang:load_module(my_code_test, 141 bit_sized_binary(Bin))), 142 ok. 143 144%% Ensure that the loader doesn't crash or leak memory when attempting 145%% to load bad BEAM files. We depend on valgrind to notice leaks. 146bad_beam_file(_Config) -> 147 Mod = ?FUNCTION_NAME, 148 149 BadBeam1 = bad_beam_file_1(Mod), 150 {error,badfile} = code:load_binary(Mod, atom_to_list(Mod), BadBeam1), 151 152 BadBeam2 = bad_beam_file_2(Mod), 153 {error,badfile} = code:load_binary(Mod, atom_to_list(Mod), BadBeam2), 154 155 ok. 156 157%% Build a BEAM file with an invalid instruction in the code chunk. 158bad_beam_file_1(Mod) -> 159 Exp = [{term,0}], 160 Attr = [], 161 Fs = [{function,term,0,3, 162 [{label,1}, 163 {line,[]}, 164 {func_info,{atom,Mod},{atom,term},0}, 165 {label,2}, 166 {move,nil,nil}, %Illegal destination. 167 return]}], 168 Asm = {Mod,Exp,Attr,Fs,3}, 169 170 %% Bypass beam_validator. 171 {ok,BadBeam} = beam_asm:module(Asm, [], [], []), 172 BadBeam. 173 174%% Build a BEAM file with an invalid attributes chunk. 175bad_beam_file_2(Mod) -> 176 Asm = {Mod,[],[],[],1}, 177 {ok,BadBeam0} = beam_asm:module(Asm, [], [], []), 178 {ok, Mod, Chunks0} = beam_lib:all_chunks(BadBeam0), 179 Chunks1 = lists:keydelete("Attr", 1, Chunks0), 180 Chunks = [{"Attr",<<"bad_attribute_chunk">>} | Chunks1], 181 {ok,BadBeam} = beam_lib:build_module(Chunks), 182 BadBeam. 183 184%% Ensure that literal areas don't leak when erlang:prepare_loading/2 185%% is not followed by erlang:finish_loading/1. 186literal_leak(_Config) -> 187 Mod = ?FUNCTION_NAME, 188 HugeLiteral = binary_to_list(<<0:(1024*1024)/unit:8>>), 189 Exp = [{term,0}], 190 Attr = [], 191 Fs = [{function,term,0,3, 192 [{label,1}, 193 {line,[]}, 194 {func_info,{atom,Mod},{atom,term},0}, 195 {label,2}, 196 {move,{literal,HugeLiteral},{x,0}}, 197 return]}], 198 Asm = {Mod,Exp,Attr,Fs,3}, 199 {ok,Beam} = beam_asm:module(Asm, [], [], []), 200 201 %% valgrind cannot help us find leak of literals because literal 202 %% areas are allocated using mmap(). 203 %% 204 %% Instead we will prepare for loading a BEAM file with a 16Mb 205 %% literal N times so that the reserved literal memory range will 206 %% overflow. 207 %% 208 %% Setting N to 64 is sufficient for overflowing a 1 GB literal 209 %% area (which is the size at the time of writing). Just to be 210 %% sure, we will go a little bit higher... 211 212 N = 128, 213 _ = [begin 214 _ = erlang:prepare_loading(Mod, Beam), 215 _ = erlang:garbage_collect() 216 end || _ <- lists:seq(1, N)], 217 ok. 218 219call_purged_fun_code_gone(Config) when is_list(Config) -> 220 Priv = proplists:get_value(priv_dir, Config), 221 Data = proplists:get_value(data_dir, Config), 222 call_purged_fun_test(Priv, Data, code_gone), 223 ok. 224 225call_purged_fun_code_reload(Config) when is_list(Config) -> 226 Priv = proplists:get_value(priv_dir, Config), 227 Data = proplists:get_value(data_dir, Config), 228 Path = code:get_path(), 229 true = code:add_path(Priv), 230 try 231 call_purged_fun_test(Priv, Data, code_reload) 232 after 233 code:set_path(Path) 234 end, 235 ok. 236 237call_purged_fun_code_there(Config) when is_list(Config) -> 238 Priv = proplists:get_value(priv_dir, Config), 239 Data = proplists:get_value(data_dir, Config), 240 call_purged_fun_test(Priv, Data, code_there), 241 ok. 242 243call_purged_fun_test(Priv, Data, Type) -> 244 SrcFile = filename:join(Data, "call_purged_fun_tester.erl"), 245 ObjFile = filename:join(Priv, "call_purged_fun_tester.beam"), 246 {ok,Mod,Code} = compile:file(SrcFile, [binary, report]), 247 {module,Mod} = code:load_binary(Mod, ObjFile, Code), 248 249 call_purged_fun_tester:do(Priv, Data, Type, []). 250 251 252multi_proc_purge(Config) when is_list(Config) -> 253 %% 254 %% Make sure purge requests aren't lost when 255 %% purger process is working. 256 %% 257 Priv = proplists:get_value(priv_dir, Config), 258 Data = proplists:get_value(data_dir, Config), 259 File1 = filename:join(Data, "my_code_test"), 260 File2 = filename:join(Data, "my_code_test2"), 261 262 {ok,my_code_test} = c:c(File1, [{outdir,Priv}]), 263 {ok,my_code_test2} = c:c(File2, [{outdir,Priv}]), 264 erlang:delete_module(my_code_test), 265 erlang:delete_module(my_code_test2), 266 267 Self = self(), 268 269 Fun1 = fun () -> 270 erts_code_purger:purge(my_code_test), 271 Self ! {self(), done} 272 end, 273 Fun2 = fun () -> 274 erts_code_purger:soft_purge(my_code_test2), 275 Self ! {self(), done} 276 end, 277 Fun3 = fun () -> 278 erts_code_purger:purge('__nonexisting_module__'), 279 Self ! {self(), done} 280 end, 281 Fun4 = fun () -> 282 erts_code_purger:soft_purge('__another_nonexisting_module__'), 283 Self ! {self(), done} 284 end, 285 286 Pid1 = spawn_link(Fun1), 287 Pid2 = spawn_link(Fun2), 288 Pid3 = spawn_link(Fun3), 289 Pid4 = spawn_link(Fun4), 290 Pid5 = spawn_link(Fun1), 291 Pid6 = spawn_link(Fun2), 292 Pid7 = spawn_link(Fun3), 293 receive after 50 -> ok end, 294 Pid8 = spawn_link(Fun4), 295 Pid9 = spawn_link(Fun1), 296 Pid10 = spawn_link(Fun2), 297 Pid11 = spawn_link(Fun3), 298 Pid12 = spawn_link(Fun4), 299 Pid13 = spawn_link(Fun1), 300 receive after 50 -> ok end, 301 Pid14 = spawn_link(Fun2), 302 Pid15 = spawn_link(Fun3), 303 Pid16 = spawn_link(Fun4), 304 305 lists:foreach(fun (P) -> receive {P, done} -> ok end end, 306 [Pid1, Pid2, Pid3, Pid4, Pid5, Pid6, Pid7, Pid8, 307 Pid9, Pid10, Pid11, Pid12, Pid13, Pid14, Pid15, Pid16]), 308 ok. 309 310%% Test the erlang:check_old_code/1 BIF. 311t_check_old_code(Config) when is_list(Config) -> 312 Data = proplists:get_value(data_dir, Config), 313 File = filename:join(Data, "my_code_test"), 314 315 catch erlang:purge_module(my_code_test), 316 catch erlang:delete_module(my_code_test), 317 catch erlang:purge_module(my_code_test), 318 319 false = erlang:check_old_code(my_code_test), 320 321 {ok,my_code_test,Code} = compile:file(File, [binary]), 322 {module,my_code_test} = code:load_binary(my_code_test, File, Code), 323 324 false = erlang:check_old_code(my_code_test), 325 {module,my_code_test} = code:load_binary(my_code_test, File, Code), 326 true = erlang:check_old_code(my_code_test), 327 328 true = erlang:purge_module(my_code_test), 329 true = erlang:delete_module(my_code_test), 330 true = erlang:purge_module(my_code_test), 331 332 {'EXIT',_} = (catch erlang:check_old_code([])), 333 334 ok. 335 336external_fun(Config) when is_list(Config) -> 337 false = erlang:function_exported(another_code_test, x, 1), 338 AnotherCodeTest = id(another_code_test), 339 ExtFun = fun AnotherCodeTest:x/1, 340 {'EXIT',{undef,_}} = (catch ExtFun(answer)), 341 false = erlang:function_exported(another_code_test, x, 1), 342 false = lists:member(another_code_test, erlang:loaded()), 343 Data = proplists:get_value(data_dir, Config), 344 File = filename:join(Data, "another_code_test"), 345 {ok,another_code_test,Code} = compile:file(File, [binary,report]), 346 {module,another_code_test} = erlang:load_module(another_code_test, Code), 347 42 = ExtFun(answer), 348 ok. 349 350get_chunk(Config) when is_list(Config) -> 351 Data = proplists:get_value(data_dir, Config), 352 File = filename:join(Data, "my_code_test"), 353 {ok,my_code_test,Code} = compile:file(File, [binary]), 354 355 %% Should work. 356 Chunk = get_chunk_ok("AtU8", Code), 357 Chunk = get_chunk_ok("AtU8", make_sub_binary(Code)), 358 Chunk = get_chunk_ok("AtU8", make_unaligned_sub_binary(Code)), 359 360 %% Should fail. 361 {'EXIT',{badarg,_}} = (catch code:get_chunk(bit_sized_binary(Code), "AtU8")), 362 {'EXIT',{badarg,_}} = (catch code:get_chunk(Code, "bad chunk id")), 363 364 %% Invalid beam code or missing chunk should return 'undefined'. 365 undefined = code:get_chunk(<<"not a beam module">>, "AtU8"), 366 undefined = code:get_chunk(Code, "XXXX"), 367 368 ok. 369 370get_chunk_ok(Chunk, Code) -> 371 case code:get_chunk(Code, Chunk) of 372 Bin when is_binary(Bin) -> Bin 373 end. 374 375module_md5(Config) when is_list(Config) -> 376 Data = proplists:get_value(data_dir, Config), 377 File = filename:join(Data, "my_code_test"), 378 {ok,my_code_test,Code} = compile:file(File, [binary]), 379 380 %% Should work. 381 Chunk = module_md5_ok(Code), 382 Chunk = module_md5_ok(make_sub_binary(Code)), 383 Chunk = module_md5_ok(make_unaligned_sub_binary(Code)), 384 385 %% Should fail. 386 {'EXIT',{badarg,_}} = (catch code:module_md5(bit_sized_binary(Code))), 387 388 %% Invalid beam code should return 'undefined'. 389 undefined = code:module_md5(<<"not a beam module">>), 390 ok. 391 392module_md5_ok(Code) -> 393 case code:module_md5(Code) of 394 Bin when is_binary(Bin), size(Bin) =:= 16 -> Bin 395 end. 396 397 398constant_pools(Config) when is_list(Config) -> 399 Data = proplists:get_value(data_dir, Config), 400 File = filename:join(Data, "literals"), 401 {ok,literals,Code} = compile:file(File, [report,binary]), 402 {module,literals} = erlang:load_module(literals, 403 make_sub_binary(Code)), 404 405 %% Initialize. 406 A = literals:a(), 407 B = literals:b(), 408 C = literals:huge_bignum(), 409 D = literals:funs(), 410 process_flag(trap_exit, true), 411 Self = self(), 412 413 %% Have a process WITHOUT old heap that references the literals 414 %% in the 'literals' module. 415 NoOldHeap = spawn_link(fun() -> no_old_heap(Self) end), 416 receive go -> ok end, 417 true = erlang:delete_module(literals), 418 false = erlang:check_process_code(NoOldHeap, literals), 419 erlang:check_process_code(self(), literals), 420 true = erlang:purge_module(literals), 421 NoOldHeap ! done, 422 receive 423 {'EXIT',NoOldHeap,{A,B,C,D}} -> 424 ok; 425 Other_NoOldHeap -> 426 ct:fail({unexpected,Other_NoOldHeap}) 427 end, 428 {module,literals} = erlang:load_module(literals, Code), 429 430 %% Have a process with an inconsistent heap (legal while GC is disabled) 431 %% that references the literals in the 'literals' module. 432 InconsistentHeap = spawn_link(fun() -> inconsistent_heap(Self) end), 433 receive go -> ok end, 434 true = erlang:delete_module(literals), 435 false = erlang:check_process_code(InconsistentHeap, literals), 436 erlang:check_process_code(self(), literals), 437 true = erlang:purge_module(literals), 438 InconsistentHeap ! done, 439 receive 440 {'EXIT',InconsistentHeap,{A,B,C}} -> 441 ok; 442 Other_InconsistentHeap -> 443 ct:fail({unexpected,Other_InconsistentHeap}) 444 end, 445 {module,literals} = erlang:load_module(literals, Code), 446 447 %% Have a process WITH an old heap that references the literals 448 %% in the 'literals' module. 449 OldHeap = spawn_link(fun() -> old_heap(Self) end), 450 receive go -> ok end, 451 true = erlang:delete_module(literals), 452 false = erlang:check_process_code(OldHeap, literals), 453 erlang:check_process_code(self(), literals), 454 erlang:purge_module(literals), 455 OldHeap ! done, 456 receive 457 {'EXIT',OldHeap,{A,B,C,D,[1,2,3|_]=Seq}} when length(Seq) =:= 16 -> 458 ok 459 end, 460 461 {module,literals} = erlang:load_module(literals, Code), 462 %% Have a hibernated process that references the literals 463 %% in the 'literals' module. 464 {Hib, Mon} = spawn_monitor(fun() -> hibernated(Self) end), 465 receive go -> ok end, 466 [{heap_size,OldHeapSz}, 467 {total_heap_size,OldTotHeapSz}] = process_info(Hib, [heap_size, 468 total_heap_size]), 469 OldHeapSz = OldTotHeapSz, 470 io:format("OldHeapSz=~p OldTotHeapSz=~p~n", [OldHeapSz, OldTotHeapSz]), 471 true = erlang:delete_module(literals), 472 false = erlang:check_process_code(Hib, literals), 473 erlang:check_process_code(self(), literals), 474 erlang:purge_module(literals), 475 receive after 1000 -> ok end, 476 [{heap_size,HeapSz}, 477 {total_heap_size,TotHeapSz}] = process_info(Hib, [heap_size, 478 total_heap_size]), 479 io:format("HeapSz=~p TotHeapSz=~p~n", [HeapSz, TotHeapSz]), 480 Hib ! hej, 481 receive 482 {'DOWN', Mon, process, Hib, Reason} -> 483 {undef, [{no_module, 484 no_function, 485 [{A,B,C,D,[1,2,3|_]=Seq}], _}]} = Reason, 486 16 = length(Seq) 487 end, 488 HeapSz = TotHeapSz, %% Ensure restored to hibernated state... 489 true = HeapSz > OldHeapSz, 490 literal_area_collector_test:check_idle(5000), 491 ok. 492 493no_old_heap(Parent) -> 494 A = literals:a(), 495 B = literals:b(), 496 C = literals:huge_bignum(), 497 D = literals:funs(), 498 Res = {A,B,C,D}, 499 Parent ! go, 500 receive 501 done -> 502 exit(Res) 503 end. 504 505old_heap(Parent) -> 506 A = literals:a(), 507 B = literals:b(), 508 C = literals:huge_bignum(), 509 D = literals:funs(), 510 Res = {A,B,C,D,lists:seq(1, 16)}, 511 create_old_heap(), 512 Parent ! go, 513 receive 514 done -> 515 exit(Res) 516 end. 517 518inconsistent_heap(Parent) -> 519 A = literals:a(), 520 B = literals:b(), 521 C = literals:huge_bignum(), 522 Res = {A,B,C}, 523 Parent ! go, 524 525 %% Disable the GC and return a tuple whose arity and contents are broken 526 BrokenTerm = erts_debug:set_internal_state(inconsistent_heap, start), 527 receive 528 after 5000 -> 529 %% Fix the tuple and enable the GC again 530 ok = erts_debug:set_internal_state(inconsistent_heap, BrokenTerm), 531 erlang:garbage_collect() 532 end, 533 534 receive 535 done -> 536 exit(Res) 537 end. 538 539hibernated(Parent) -> 540 A = literals:a(), 541 B = literals:b(), 542 C = literals:huge_bignum(), 543 D = literals:funs(), 544 Res = {A,B,C,D,lists:seq(1, 16)}, 545 Parent ! go, 546 erlang:hibernate(no_module, no_function, [Res]). 547 548create_old_heap() -> 549 case process_info(self(), [heap_size,total_heap_size]) of 550 [{heap_size,Sz},{total_heap_size,Total}] when Sz < Total -> 551 ok; 552 _ -> 553 create_old_heap() 554 end. 555 556constant_refc_binaries(Config) when is_list(Config) -> 557 wait_for_memory_deallocations(), 558 Bef = memory_binary(), 559 io:format("Binary data (bytes) before test: ~p\n", [Bef]), 560 561 %% Compile the the literals module. 562 Data = proplists:get_value(data_dir, Config), 563 File = filename:join(Data, "literals"), 564 {ok,literals,Code} = compile:file(File, [report,binary]), 565 566 %% Load the code and make sure that the binary is a refc binary. 567 {module,literals} = erlang:load_module(literals, Code), 568 Bin = literals:binary(), 569 Sz = byte_size(Bin), 570 Check = erlang:md5(Bin), 571 io:format("Size of literal refc binary: ~p\n", [Sz]), 572 {refc_binary,Sz,_,_} = erts_debug:get_internal_state({binary_info,Bin}), 573 true = erlang:delete_module(literals), 574 false = erlang:check_process_code(self(), literals), 575 true = erlang:purge_module(literals), 576 577 %% Now try to provoke a memory leak. 578 provoke_mem_leak(10, Code, Check), 579 580 %% Calculate the change in allocated binary data. 581 erlang:garbage_collect(), 582 wait_for_memory_deallocations(), 583 Aft = memory_binary(), 584 io:format("Binary data (bytes) after test: ~p", [Aft]), 585 Diff = Aft - Bef, 586 if 587 Diff < 0 -> 588 io:format("~p less bytes", [abs(Diff)]); 589 Diff > 0 -> 590 io:format("~p more bytes", [Diff]); 591 true -> 592 ok 593 end, 594 595 %% Test for leaks. We must accept some natural variations in 596 %% the size of allocated binaries. 597 if 598 Diff > 64*1024 -> 599 ct:fail(binary_leak); 600 true -> 601 ok 602 end. 603 604memory_binary() -> 605 try 606 erlang:memory(binary) 607 catch 608 error:notsup -> 609 0 610 end. 611 612provoke_mem_leak(0, _, _) -> ok; 613provoke_mem_leak(N, Code, Check) -> 614 {module,literals} = erlang:load_module(literals, Code), 615 616 %% Create several processes with references to the literal binary. 617 Self = self(), 618 Pids = [spawn_link(fun() -> 619 create_binaries(Self, NumRefs, Check) 620 end) || NumRefs <- lists:seq(1, 10)], 621 [receive {started,Pid} -> ok end || Pid <- Pids], 622 623 %% Make the code old and remove references to the constant pool 624 %% in all processes. 625 true = erlang:delete_module(literals), 626 Ms = [spawn_monitor(fun() -> 627 false = erlang:check_process_code(Pid, literals) 628 end) || Pid <- Pids], 629 [receive 630 {'DOWN',R,process,P,normal} -> 631 ok 632 end || {P,R} <- Ms], 633 634 %% Purge the code. 635 true = erlang:purge_module(literals), 636 637 %% Tell the processes that the code has been purged. 638 [begin 639 monitor(process, Pid), 640 Pid ! purged 641 end || Pid <- Pids], 642 643 %% Wait for all processes to terminate. 644 [receive 645 {'DOWN',_,process,Pid,normal} -> 646 ok 647 end || Pid <- Pids], 648 649 %% We now expect that the binary has been deallocated. 650 provoke_mem_leak(N-1, Code, Check). 651 652create_binaries(Parent, NumRefs, Check) -> 653 Bin = literals:binary(), 654 Bins = lists:duplicate(NumRefs, Bin), 655 {bits,Bits} = literals:bits(), 656 Parent ! {started,self()}, 657 receive 658 purged -> 659 %% The code has been purged. Now make sure that 660 %% the binaries haven't been corrupted. 661 Check = erlang:md5(Bin), 662 [Bin = B || B <- Bins], 663 <<42:13,Bin/binary>> = Bits, 664 665 %% Remove all references to the binaries 666 %% Doing it explicitly like this ensures that 667 %% the binaries are gone when the parent process 668 %% receives the 'DOWN' message. 669 erlang:garbage_collect() 670 end. 671 672wait_for_memory_deallocations() -> 673 try 674 erts_debug:set_internal_state(wait, deallocations) 675 catch 676 error:undef -> 677 erts_debug:set_internal_state(available_internal_state, true), 678 wait_for_memory_deallocations() 679 end. 680 681fake_literals(_Config) -> 682 Mod = fake__literals__module, 683 try 684 do_fake_literals(Mod) 685 after 686 _ = code:purge(Mod), 687 _ = code:delete(Mod), 688 _ = code:purge(Mod), 689 _ = code:delete(Mod) 690 end, 691 ok. 692 693do_fake_literals(Mod) -> 694 Tid = ets:new(test, []), 695 ExtTerms = get_external_terms(), 696 Term0 = {self(),make_ref(),Tid,fun() -> ok end,ExtTerms}, 697 Terms = [begin 698 make_literal_module(Mod, Term0), 699 Mod:term() 700 end || _ <- lists:seq(1, 10)], 701 verify_lit_terms(Terms, Term0), 702 true = ets:delete(Tid), 703 ok. 704 705make_literal_module(Mod, Term) -> 706 Exp = [{term,0}], 707 Attr = [], 708 Fs = [{function,term,0,2, 709 [{label,1}, 710 {line,[]}, 711 {func_info,{atom,Mod},{atom,term},0}, 712 {label,2}, 713 {move,{literal,Term},{x,0}}, 714 return]}], 715 Asm = {Mod,Exp,Attr,Fs,3}, 716 {ok,Mod,Beam} = compile:forms(Asm, [from_asm,binary,report]), 717 code:load_binary(Mod, atom_to_list(Mod), Beam). 718 719verify_lit_terms([H|T], Term) -> 720 case H =:= Term of 721 true -> 722 verify_lit_terms(T, Term); 723 false -> 724 error({bad_term,H}) 725 end; 726verify_lit_terms([], _) -> 727 ok. 728 729get_external_terms() -> 730 {ok,Node} = test_server:start_node(?FUNCTION_NAME, slave, []), 731 Ref = rpc:call(Node, erlang, make_ref, []), 732 Ports = rpc:call(Node, erlang, ports, []), 733 Pid = rpc:call(Node, erlang, self, []), 734 _ = test_server:stop_node(Node), 735 {Ref,hd(Ports),Pid}. 736 737%% OTP-7559: c_p->cp could contain garbage and create a false dependency 738%% to a module in a process. (Thanks to Richard Carlsson.) 739false_dependency(Config) when is_list(Config) -> 740 Data = proplists:get_value(data_dir, Config), 741 File = filename:join(Data, "cpbugx"), 742 {ok,cpbugx,Code} = compile:file(File, [binary,report]), 743 744 do_false_dependency(fun cpbugx:before/0, Code), 745 do_false_dependency(fun cpbugx:before2/0, Code), 746 do_false_dependency(fun cpbugx:before3/0, Code), 747 748 ok. 749 750do_false_dependency(Init, Code) -> 751 {module,cpbugx} = erlang:load_module(cpbugx, Code), 752 753 %% Spawn process. Make sure it has the appropriate init function 754 %% and returned. CP should not contain garbage after the return. 755 Parent = self(), 756 Pid = spawn_link(fun() -> false_dependency_loop(Parent, Init, true) end), 757 receive initialized -> ok end, 758 759 %% Reload the module. Make sure the process is still alive. 760 {module,cpbugx} = erlang:load_module(cpbugx, Code), 761 io:put_chars(binary_to_list(element(2, process_info(Pid, backtrace)))), 762 true = is_process_alive(Pid), 763 764 %% There should not be any dependency to cpbugx. 765 false = erlang:check_process_code(Pid, cpbugx), 766 767 %% Kill the process and completely unload the code. 768 unlink(Pid), exit(Pid, kill), 769 true = erlang:purge_module(cpbugx), 770 true = erlang:delete_module(cpbugx), 771 true = erlang:purge_module(cpbugx), 772 ok. 773 774false_dependency_loop(Parent, Init, SendInitAck) -> 775 Init(), 776 case SendInitAck of 777 true -> Parent ! initialized; 778 false -> void 779 %% Just send one init-ack. I guess the point of this test 780 %% wasn't to fill parents msg-queue (?). Seen to cause 781 %% out-of-mem (on halfword-vm for some reason) by 782 %% 91 million msg in queue. /sverker 783 end, 784 receive 785 _ -> false_dependency_loop(Parent, Init, false) 786 end. 787 788coverage(Config) when is_list(Config) -> 789 {'EXIT',{badarg,_}} = (catch erlang:purge_module({a,b,c})), 790 {'EXIT',{badarg,_}} = (catch erlang:check_process_code(not_a_pid, ?MODULE)), 791 {'EXIT',{badarg,_}} = (catch erlang:check_process_code(self(), [not_a_module])), 792 {'EXIT',{badarg,_}} = (catch erlang:delete_module([a,b,c])), 793 {'EXIT',{badarg,_}} = (catch erlang:module_loaded(42)), 794 ok. 795 796fun_confusion(Config) when is_list(Config) -> 797 Data = proplists:get_value(data_dir, Config), 798 Src = filename:join(Data, "fun_confusion"), 799 Mod = fun_confusion, 800 801 %% Load first version of module. 802 compile_load(Mod, Src, 1), 803 F1 = Mod:f(), 804 1 = F1(), 805 806 %% Load second version of module. 807 compile_load(Mod, Src, 2), 808 F2 = Mod:f(), 809 810 %% F1 should refer to the old code, not the newly loaded code. 811 1 = F1(), 812 2 = F2(), 813 ok. 814 815compile_load(Mod, Src, Ver) -> 816 {ok,Mod,Code1} = compile:file(Src, [binary,{d,version,Ver}]), 817 {module,Mod} = code:load_binary(Mod, "fun_confusion.beam", Code1), 818 ok. 819 820 821t_copy_literals(Config) when is_list(Config) -> 822 %% Compile the the literals module. 823 Data = proplists:get_value(data_dir, Config), 824 File = filename:join(Data, "literals"), 825 {ok,literals,Code} = compile:file(File, [report,binary]), 826 {module,literals} = erlang:load_module(literals, Code), 827 828 N = 30, 829 Me = self(), 830 %% reload literals code every 567 ms 831 Rel = spawn_link(fun() -> reloader(literals,Code,567) end), 832 %% add new literal msgs to the loop every 789 ms 833 Sat = spawn_link(fun() -> saturate(Me,789) end), 834 %% run for 10s 835 _ = spawn_link(fun() -> receive after 10000 -> Me ! done end end), 836 ok = chase_msg(N, Me), 837 %% cleanup 838 Rel ! done, 839 Sat ! done, 840 ok = flush(), 841 ok. 842 843-define(mod, t_copy_literals_frags). 844t_copy_literals_frags(Config) when is_list(Config) -> 845 Bin = gen_lit(?mod,[{a,{1,2,3,4,5,6,7}}, 846 {b,"hello world"}, 847 {c, <<"hello world">>}, 848 {d, {"hello world", {1.0, 2.0, <<"some">>, "string"}}}, 849 {e, <<"off heap", 0, 1, 2, 3, 4, 5, 6, 7, 850 8, 9,10,11,12,13,14,15, 851 0, 1, 2, 3, 4, 5, 6, 7, 852 8, 9,10,11,12,13,14,15, 853 0, 1, 2, 3, 4, 5, 6, 7, 854 8, 9,10,11,12,13,14,15, 855 0, 1, 2, 3, 4, 5, 6, 7, 856 8, 9,10,11,12,13,14,15>>}, 857 {f, fun ?MODULE:all/0}]), 858 859 {module, ?mod} = erlang:load_module(?mod, Bin), 860 N = 6000, 861 Recv = spawn_opt(fun() -> receive 862 read -> 863 io:format("reading"), 864 literal_receiver() 865 end 866 end, [link,{min_heap_size, 10000}]), 867 Switcher = spawn_link(fun() -> literal_switcher() end), 868 Pids = [spawn_opt(fun() -> receive 869 {Pid, go, Recv, N} -> 870 io:format("sender batch (~w) start ~w~n",[N,self()]), 871 literal_sender(N,Recv), 872 Pid ! {self(), ok} 873 end 874 end, [link,{min_heap_size,800}]) || _ <- lists:seq(1,100)], 875 _ = [Pid ! {self(), go, Recv, N} || Pid <- Pids], 876 %% don't read immediately 877 timer:sleep(5), 878 Recv ! read, 879 Switcher ! {switch,?mod,Bin,[Recv|Pids],200}, 880 _ = [receive {Pid, ok} -> ok end || Pid <- Pids], 881 Switcher ! {self(), done}, 882 receive {Switcher, ok} -> ok end, 883 Recv ! {self(), done}, 884 receive {Recv, ok} -> ok end, 885 ok. 886 887literal_receiver() -> 888 receive 889 {Pid, done} -> 890 io:format("reader_done~n"), 891 Pid ! {self(), ok}; 892 {_Pid, msg, [A,B,C,D,E]} -> 893 A = ?mod:a(), 894 B = ?mod:b(), 895 C = ?mod:c(), 896 D = ?mod:d(), 897 E = ?mod:e(), 898 F = ?mod:f(), 899 literal_receiver(); 900 {Pid, sender_confirm} -> 901 io:format("sender confirm ~w~n", [Pid]), 902 Pid ! {self(), ok}, 903 literal_receiver() 904 end. 905 906literal_sender(0, Recv) -> 907 Recv ! {self(), sender_confirm}, 908 receive {Recv, ok} -> ok end; 909literal_sender(N, Recv) -> 910 Recv ! {self(), msg, [?mod:a(), 911 ?mod:b(), 912 ?mod:c(), 913 ?mod:d(), 914 ?mod:e(), 915 ?mod:f()]}, 916 literal_sender(N - 1, Recv). 917 918literal_switcher() -> 919 receive 920 {switch,Mod,Bin,Pids,Tmo} -> 921 literal_switcher(Mod,Bin,Pids,Tmo) 922 end. 923literal_switcher(Mod,Bin,Pids,Tmo) -> 924 receive 925 {Pid,done} -> 926 Pid ! {self(),ok} 927 after Tmo -> 928 io:format("load module ~w~n", [Mod]), 929 {module, Mod} = erlang:load_module(Mod,Bin), 930 ok = check_and_purge(Pids,Mod), 931 io:format("purge complete ~w~n", [Mod]), 932 literal_switcher(Mod,Bin,Pids,Tmo+Tmo) 933 end. 934 935check_and_purge([],Mod) -> 936 erlang:purge_module(Mod), 937 ok; 938check_and_purge(Pids,Mod) -> 939 io:format("purge ~w~n", [Mod]), 940 Tag = make_ref(), 941 _ = [begin 942 erlang:check_process_code(Pid,Mod,[{async,{Tag,Pid}}]) 943 end || Pid <- Pids], 944 Retry = check_and_purge_receive(Pids,Tag,[]), 945 check_and_purge(Retry,Mod). 946 947check_and_purge_receive([Pid|Pids],Tag,Retry) -> 948 receive 949 {check_process_code, {Tag, Pid}, false} -> 950 check_and_purge_receive(Pids,Tag,Retry); 951 {check_process_code, {Tag, Pid}, true} -> 952 check_and_purge_receive(Pids,Tag,[Pid|Retry]) 953 end; 954check_and_purge_receive([],_,Retry) -> 955 Retry. 956 957 958gen_lit(Module,Terms) -> 959 FunStrings = [lists:flatten(io_lib:format("~w() -> ~w.~n", [F,Term]))||{F,Term}<-Terms], 960 FunForms = function_forms(FunStrings), 961 Forms = [{attribute,erl_anno:new(1),module,Module}, 962 {attribute,erl_anno:new(2),export,[FA || {FA,_} <- FunForms]}] ++ 963 [Function || {_, Function} <- FunForms], 964 {ok, Module, Bin} = compile:forms(Forms), 965 Bin. 966 967function_forms([]) -> []; 968function_forms([S|Ss]) -> 969 {ok, Ts,_} = erl_scan:string(S), 970 {ok, Form} = erl_parse:parse_form(Ts), 971 Fun = element(3, Form), 972 Arity = element(4, Form), 973 [{{Fun,Arity}, Form}|function_forms(Ss)]. 974 975chase_msg(0, Pid) -> 976 chase_loop(Pid); 977chase_msg(N, Master) -> 978 Pid = spawn_link(fun() -> chase_msg(N - 1,Master) end), 979 chase_loop(Pid). 980 981chase_loop(Pid) -> 982 receive 983 done -> 984 Pid ! done, 985 ok; 986 {_From,Msg} -> 987 Pid ! {self(), Msg}, 988 ok = traverse(Msg), 989 chase_loop(Pid) 990 end. 991 992saturate(Pid,Time) -> 993 Es = [msg1,msg2,msg3,msg4,msg5], 994 Msg = [literals:E()||E <- Es], 995 Pid ! {self(), Msg}, 996 receive 997 done -> ok 998 after Time -> 999 saturate(Pid,Time) 1000 end. 1001 1002traverse([]) -> ok; 1003traverse([H|T]) -> 1004 ok = traverse(H), 1005 traverse(T); 1006traverse(T) when is_tuple(T) -> ok; 1007traverse(B) when is_binary(B) -> ok; 1008traverse(I) when is_integer(I) -> ok; 1009traverse(#{ 1 := V1, b := V2 }) -> 1010 ok = traverse(V1), 1011 ok = traverse(V2), 1012 ok. 1013 1014 1015reloader(Mod,Code,Time) -> 1016 receive 1017 done -> ok 1018 after Time -> 1019 code:purge(Mod), 1020 {module,Mod} = erlang:load_module(Mod, Code), 1021 reloader(Mod,Code,Time) 1022 end. 1023 1024erl_544(Config) when is_list(Config) -> 1025 case file:native_name_encoding() of 1026 utf8 -> 1027 {ok, CWD} = file:get_cwd(), 1028 try 1029 Mod = erl_544, 1030 FileName = atom_to_list(Mod) ++ ".erl", 1031 Priv = proplists:get_value(priv_dir, Config), 1032 Data = proplists:get_value(data_dir, Config), 1033 {ok, FileContent} = file:read_file(filename:join(Data, 1034 FileName)), 1035 Dir = filename:join(Priv, [16#2620,16#2620,16#2620]), 1036 File = filename:join(Dir, FileName), 1037 io:format("~ts~n", [File]), 1038 ok = file:make_dir(Dir), 1039 ok = file:set_cwd(Dir), 1040 ok = file:write_file(File, [FileContent]), 1041 {ok, Mod} = compile:file(File), 1042 Res1 = (catch Mod:err()), 1043 io:format("~p~n", [Res1]), 1044 {'EXIT', {err, [{Mod, err, 0, Info1}|_]}} = Res1, 1045 File = proplists:get_value(file, Info1), 1046 Me = self(), 1047 Go = make_ref(), 1048 Tester = spawn_link(fun () -> 1049 Mod:wait(Me, Go), 1050 Mod:err() 1051 end), 1052 receive Go -> ok end, 1053 Res2 = process_info(Tester, current_stacktrace), 1054 io:format("~p~n", [Res2]), 1055 {current_stacktrace, Stack} = Res2, 1056 [{Mod, wait, 2, Info2}|_] = Stack, 1057 File = proplists:get_value(file, Info2), 1058 StackFun = fun(_, _, _) -> false end, 1059 FormatFun = fun (Term, _) -> io_lib:format("~tp", [Term]) end, 1060 Formated = 1061 erl_error:format_stacktrace(1, Stack, StackFun, FormatFun), 1062 true = is_list(Formated), 1063 ok 1064 after 1065 ok = file:set_cwd(CWD) 1066 end, 1067 ok; 1068 _Enc -> 1069 {skipped, "Only run when native file name encoding is utf8"} 1070 end. 1071 1072%% Test that the copying of literals to a process during purging of 1073%% literals will cause the process to be killed if the max heap size 1074%% is exceeded. 1075max_heap_size(_Config) -> 1076 Mod = ?FUNCTION_NAME, 1077 Value = [I || I <- lists:seq(1, 5000)], 1078 Code = gen_lit(Mod, [{term,Value}]), 1079 {module,Mod} = erlang:load_module(Mod, Code), 1080 SpawnOpts = [monitor, 1081 {max_heap_size, 1082 #{size=>1024, 1083 kill=>true, 1084 error_logger=>true}}], 1085 {Pid,Ref} = spawn_opt(fun() -> 1086 max_heap_size_proc(Mod) 1087 end, SpawnOpts), 1088 receive 1089 {'DOWN',Ref,process,Pid,Reason} -> 1090 killed = Reason; 1091 Other -> 1092 ct:fail({unexpected_message,Other}) 1093 after 10000 -> 1094 ct:fail({process_did_not_die, Pid, erlang:process_info(Pid)}) 1095 end. 1096 1097max_heap_size_proc(Mod) -> 1098 Value = Mod:term(), 1099 code:delete(Mod), 1100 code:purge(Mod), 1101 receive 1102 _ -> Value 1103 end. 1104 1105check_process_code_signal_order(Config) when is_list(Config) -> 1106 process_flag(scheduler, 1), 1107 process_flag(priority, high), 1108 {module,versions} = erlang:load_module(versions, compile_version(1, Config)), 1109 Pid = spawn_opt(versions, loop, [], [{scheduler, 1}]), 1110 true = erlang:delete_module(versions), 1111 true = erlang:check_process_code(Pid, versions), 1112 Ref = make_ref(), 1113 spam_signals(Pid, 10000), 1114 %% EXIT signal *should* arrive... 1115 exit(Pid, kill), 1116 %% ... before CPC signal... 1117 async = erlang:check_process_code(Pid, versions, [{async, Ref}]), 1118 %% ... which means that the result of the check_process_code *should* be 'false'... 1119 false = busy_wait_cpc_res(Ref), 1120 ok. 1121 1122busy_wait_cpc_res(Ref) -> 1123 receive 1124 {check_process_code, Ref, Res} -> 1125 Res 1126 after 0 -> 1127 busy_wait_cpc_res(Ref) 1128 end. 1129 1130spam_signals(P, N) when N =< 0 -> 1131 ok; 1132spam_signals(P, N) -> 1133 link(P), 1134 unlink(P), 1135 spam_signals(P, N-2). 1136 1137check_process_code_dirty_exec_proc(Config) when is_list(Config) -> 1138 Pid = spawn(fun () -> 1139 erts_debug:dirty_io(wait, 10000) 1140 end), 1141 receive after 100 -> ok end, 1142 false = erlang:check_process_code(Pid, non_existing_module), 1143 {status, running} = process_info(Pid, status), 1144 exit(Pid, kill), 1145 false = is_process_alive(Pid), 1146 ok. 1147 1148%% Utilities. 1149 1150make_sub_binary(Bin) when is_binary(Bin) -> 1151 {_,B1} = split_binary(list_to_binary([0,1,3,Bin,4,5,6,7]), 3), 1152 {B,_} = split_binary(B1, size(Bin)), 1153 B; 1154make_sub_binary(List) -> 1155 make_sub_binary(list_to_binary(List)). 1156 1157make_unaligned_sub_binary(Bin0) -> 1158 Bin1 = <<0:3,Bin0/binary,31:5>>, 1159 Sz = size(Bin0), 1160 <<0:3,Bin:Sz/binary,31:5>> = id(Bin1), 1161 Bin. 1162 1163%% Add 1 bit to the size of the binary. 1164bit_sized_binary(Bin0) -> 1165 Bin = <<Bin0/binary,1:1>>, 1166 BitSize = bit_size(Bin), 1167 BitSize = 8*size(Bin) + 1, 1168 Bin. 1169 1170flush() -> 1171 receive _ -> flush() after 0 -> ok end. 1172 1173id(I) -> I. 1174 1175