diff options
Diffstat (limited to 'lib/stdlib/src')
35 files changed, 1215 insertions, 2465 deletions
diff --git a/lib/stdlib/src/Makefile b/lib/stdlib/src/Makefile index 302834f9d0..d6c0ff8d8d 100644 --- a/lib/stdlib/src/Makefile +++ b/lib/stdlib/src/Makefile @@ -51,7 +51,6 @@ MODULES= \  	dets_server \  	dets_sup \  	dets_utils \ -	dets_v8 \  	dets_v9 \  	dict \  	digraph \ @@ -225,7 +224,6 @@ $(EBIN)/beam_lib.beam: ../include/erl_compile.hrl ../../kernel/include/file.hrl  $(EBIN)/dets.beam: dets.hrl ../../kernel/include/file.hrl  $(EBIN)/dets_server.beam: dets.hrl  $(EBIN)/dets_utils.beam: dets.hrl -$(EBIN)/dets_v8.beam: dets.hrl  $(EBIN)/dets_v9.beam: dets.hrl  $(EBIN)/erl_bits.beam: ../include/erl_bits.hrl  $(EBIN)/erl_compile.beam: ../include/erl_compile.hrl ../../kernel/include/file.hrl diff --git a/lib/stdlib/src/array.erl b/lib/stdlib/src/array.erl index d5757dda5b..079b761463 100644 --- a/lib/stdlib/src/array.erl +++ b/lib/stdlib/src/array.erl @@ -1,8 +1,3 @@ -%% -%% %CopyrightBegin% -%%  -%% Copyright Ericsson AB 2007-2016. All Rights Reserved. -%%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License.  %% You may obtain a copy of the License at @@ -14,13 +9,12 @@  %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  %% See the License for the specific language governing permissions and  %% limitations under the License. -%%  -%% %CopyrightEnd%  %% -%% @author Richard Carlsson <[email protected]> +%% Copyright (C) 2006-2016 Richard Carlsson and Ericsson AB +%% +%% @author Richard Carlsson <[email protected]>  %% @author Dan Gudmundsson <[email protected]> -%% @version 1.0 - +%%  %% @doc Functional, extendible arrays. Arrays can have fixed size, or  %% can grow automatically as needed. A default value is used for entries  %% that have not been explicitly set. diff --git a/lib/stdlib/src/beam_lib.erl b/lib/stdlib/src/beam_lib.erl index d7ee5c1f5d..461acf03be 100644 --- a/lib/stdlib/src/beam_lib.erl +++ b/lib/stdlib/src/beam_lib.erl @@ -63,7 +63,7 @@  -type label()     :: integer().  -type chunkid()   :: nonempty_string(). % approximation of the strings below -%% "Abst" | "Attr" | "CInf" | "ExpT" | "ImpT" | "LocT" | "Atom". +%% "Abst" | "Attr" | "CInf" | "ExpT" | "ImpT" | "LocT" | "Atom" | "AtU8".  -type chunkname() :: 'abstract_code' | 'attributes' | 'compile_info'                     | 'exports' | 'labeled_exports'                     | 'imports' | 'indexed_imports' @@ -520,6 +520,8 @@ read_chunk_data(File0, ChunkNames0, Options)      end.  %% -> {ok, list()} | throw(Error) +check_chunks([atoms | Ids], File, IL, L) -> +    check_chunks(Ids, File, ["Atom", "AtU8" | IL], [{atom_chunk, atoms} | L]);  check_chunks([ChunkName | Ids], File, IL, L) when is_atom(ChunkName) ->      ChunkId = chunk_name_to_id(ChunkName, File),      check_chunks(Ids, File, [ChunkId | IL], [{ChunkId, ChunkName} | L]); @@ -537,6 +539,10 @@ scan_beam(File, What0, AllowMissingChunks) ->      case scan_beam1(File, What0) of  	{missing, _FD, Mod, Data, What} when AllowMissingChunks ->  	    {ok, Mod, [{Id, missing_chunk} || Id <- What] ++ Data}; +	{missing, _FD, Mod, Data, ["Atom"]} -> +	    {ok, Mod, Data}; +	{missing, _FD, Mod, Data, ["AtU8"]} -> +	    {ok, Mod, Data};  	{missing, FD, _Mod, _Data, What} ->  	    error({missing_chunk, filename(FD), hd(What)});  	R -> @@ -581,18 +587,23 @@ scan_beam(FD, Pos, What, Mod, Data) ->  	    error({invalid_beam_file, filename(FD), Pos})      end. -get_data(Cs, "Atom"=Id, FD, Size, Pos, Pos2, _Mod, Data) -> +get_atom_data(Cs, Id, FD, Size, Pos, Pos2, Data, Encoding) ->      NewCs = del_chunk(Id, Cs),      {NFD, Chunk} = get_chunk(Id, Pos, Size, FD),      <<_Num:32, Chunk2/binary>> = Chunk, -    {Module, _} = extract_atom(Chunk2), +    {Module, _} = extract_atom(Chunk2, Encoding),      C = case Cs of  	    info ->   		{Id, Pos, Size};  	    _ ->   		{Id, Chunk}  	end, -    scan_beam(NFD, Pos2, NewCs, Module, [C | Data]); +    scan_beam(NFD, Pos2, NewCs, Module, [C | Data]). + +get_data(Cs, "Atom" = Id, FD, Size, Pos, Pos2, _Mod, Data) -> +    get_atom_data(Cs, Id, FD, Size, Pos, Pos2, Data, latin1); +get_data(Cs, "AtU8" = Id, FD, Size, Pos, Pos2, _Mod, Data) -> +    get_atom_data(Cs, Id, FD, Size, Pos, Pos2, Data, utf8);  get_data(info, Id, FD, Size, Pos, Pos2, Mod, Data) ->      scan_beam(FD, Pos2, info, Mod, [{Id, Pos, Size} | Data]);  get_data(Chunks, Id, FD, Size, Pos, Pos2, Mod, Data) -> @@ -624,6 +635,9 @@ get_chunk(Id, Pos, Size, FD) ->  	    {NFD, Chunk}      end. +chunks_to_data([{atom_chunk, Name} | CNs], Chunks, File, Cs, Module, Atoms, L) -> +    {NewAtoms, Ret} = chunk_to_data(Name, <<"">>, File, Cs, Atoms, Module), +    chunks_to_data(CNs, Chunks, File, Cs, Module, NewAtoms, [Ret | L]);  chunks_to_data([{Id, Name} | CNs], Chunks, File, Cs, Module, Atoms, L) ->      {_Id, Chunk} = lists:keyfind(Id, 1, Chunks),      {NewAtoms, Ret} = chunk_to_data(Name, Chunk, File, Cs, Atoms, Module), @@ -651,7 +665,7 @@ chunk_to_data(abstract_code=Id, Chunk, File, _Cs, AtomTable, Mod) ->  	<<>> ->  	    {AtomTable, {Id, no_abstract_code}};  	<<0:8,N:8,Mode0:N/binary,Rest/binary>> -> -	    Mode = list_to_atom(binary_to_list(Mode0)), +	    Mode = binary_to_atom(Mode0, utf8),  	    decrypt_abst(Mode, Mod, File, Id, AtomTable, Rest);  	_ ->  	    case catch binary_to_term(Chunk) of @@ -683,7 +697,6 @@ chunk_to_data(ChunkId, Chunk, _File,  	      _Cs, AtomTable, _Module) when is_list(ChunkId) ->      {AtomTable, {ChunkId, Chunk}}. % Chunk is a binary -chunk_name_to_id(atoms, _)           -> "Atom";  chunk_name_to_id(indexed_imports, _) -> "ImpT";  chunk_name_to_id(imports, _)         -> "ImpT";  chunk_name_to_id(exports, _)         -> "ExpT"; @@ -738,25 +751,30 @@ atm(AT, N) ->  %% AT is updated.  ensure_atoms({empty, AT}, Cs) -> -    {_Id, AtomChunk} = lists:keyfind("Atom", 1, Cs), -    extract_atoms(AtomChunk, AT), +    case lists:keyfind("AtU8", 1, Cs) of +	{_Id, AtomChunk} when is_binary(AtomChunk) -> +	    extract_atoms(AtomChunk, AT, utf8); +	_ -> +	    {_Id, AtomChunk} = lists:keyfind("Atom", 1, Cs), +	    extract_atoms(AtomChunk, AT, latin1) +    end,      AT;  ensure_atoms(AT, _Cs) ->      AT. -extract_atoms(<<_Num:32, B/binary>>, AT) -> -    extract_atoms(B, 1, AT). +extract_atoms(<<_Num:32, B/binary>>, AT, Encoding) -> +    extract_atoms(B, 1, AT, Encoding). -extract_atoms(<<>>, _I, _AT) -> +extract_atoms(<<>>, _I, _AT, _Encoding) ->      true; -extract_atoms(B, I, AT) -> -    {Atom, B1} = extract_atom(B), +extract_atoms(B, I, AT, Encoding) -> +    {Atom, B1} = extract_atom(B, Encoding),      true = ets:insert(AT, {I, Atom}), -    extract_atoms(B1, I+1, AT). +    extract_atoms(B1, I+1, AT, Encoding). -extract_atom(<<Len, B/binary>>) -> +extract_atom(<<Len, B/binary>>, Encoding) ->      <<SB:Len/binary, Tail/binary>> = B, -    {list_to_atom(binary_to_list(SB)), Tail}. +    {binary_to_atom(SB, Encoding), Tail}.  %%% Utils. @@ -856,12 +874,12 @@ significant_chunks() ->  %% for a module. They are listed in the order that they should be MD5:ed.  md5_chunks() -> -    ["Atom", "Code", "StrT", "ImpT", "ExpT", "FunT", "LitT"]. +    ["Atom", "AtU8", "Code", "StrT", "ImpT", "ExpT", "FunT", "LitT"].  %% The following chunks are mandatory in every Beam file.  mandatory_chunks() -> -    ["Code", "ExpT", "ImpT", "StrT", "Atom"]. +    ["Code", "ExpT", "ImpT", "StrT"].  %%% ====================================================================  %%% The rest of the file handles encrypted debug info. diff --git a/lib/stdlib/src/c.erl b/lib/stdlib/src/c.erl index ad4915eabe..d36630214c 100644 --- a/lib/stdlib/src/c.erl +++ b/lib/stdlib/src/c.erl @@ -26,7 +26,7 @@  -export([help/0,lc/1,c/1,c/2,nc/1,nc/2, nl/1,l/1,i/0,i/1,ni/0,           y/1, y/2,  	 lc_batch/0, lc_batch/1, -	 i/3,pid/3,m/0,m/1, +	 i/3,pid/3,m/0,m/1,mm/0,lm/0,  	 bt/1, q/0,  	 erlangrc/0,erlangrc/1,bi/1, flush/0, regs/0, uptime/0,  	 nregs/0,pwd/0,ls/0,ls/1,cd/1,memory/1,memory/0, xm/1]). @@ -52,11 +52,13 @@ help() ->  		   "ni()       -- information about the networked system\n"  		   "i(X,Y,Z)   -- information about pid <X,Y,Z>\n"  		   "l(Module)  -- load or reload module\n" +		   "lm()       -- load all modified modules\n"  		   "lc([File]) -- compile a list of Erlang modules\n"  		   "ls()       -- list files in the current directory\n"  		   "ls(Dir)    -- list files in directory <Dir>\n"  		   "m()        -- which modules are loaded\n"  		   "m(Mod)     -- information about module <Mod>\n" +		   "mm()       -- list all modified modules\n"  		   "memory()   -- memory allocation information\n"  		   "memory(T)  -- memory allocation information of type <T>\n"  		   "nc(File)   -- compile and load code in <File> on all nodes\n" @@ -459,6 +461,16 @@ m() ->  mformat(A1, A2) ->      format("~-20s  ~ts\n", [A1,A2]). +-spec mm() -> [module()]. + +mm() -> +    code:modified_modules(). + +-spec lm() -> [code:load_ret()]. + +lm() -> +    [l(M) || M <- mm()]. +  %% erlangrc(Home)  %%  Try to run a ".erlang" file, first in the current directory  %%  else in home directory. diff --git a/lib/stdlib/src/dets.erl b/lib/stdlib/src/dets.erl index bf22949870..5bc9475fc8 100644 --- a/lib/stdlib/src/dets.erl +++ b/lib/stdlib/src/dets.erl @@ -105,9 +105,6 @@  %%% the file with the split indicator, size etc is held in ram by the  %%% server at all times.  %%% -%%% The parts specific for formats up to and including 8(c) are -%%% implemented in dets_v8.erl, parts specific for format 9 are -%%% implemented in dets_v9.erl.  %%  The method of hashing is the so called linear hashing algorithm  %%  with segments.  @@ -140,28 +137,33 @@  %%% written, and a repair is forced next time the file is opened.  -record(dets_cont, { -         what,        % object | bindings | select | bchunk -	 no_objs,     % requested number of objects: default | integer() > 0 -	 bin,         % small chunk not consumed, or 'eof' at end-of-file -	 alloc,       % the part of the file not yet scanned, mostly a binary -	 tab, -         proc,        % the pid of the Dets process -         match_program % true | compiled_match_spec() | undefined +          what :: 'undefined' | 'bchunk' | 'bindings' | 'object' | 'select', +          no_objs :: 'default' | pos_integer(), % requested number of objects +          bin :: 'eof' | binary(), % small chunk not consumed, +                                  % or 'eof' at end-of-file +          alloc :: binary() % the part of the file not yet scanned +                 | {From :: non_neg_integer(), +                    To :: non_neg_integer, +                    binary()}, +          tab :: tab_name(), +          proc :: 'undefined' | pid(), % the pid of the Dets process +          match_program :: 'true' +                         | 'undefined' +                         | {'match_spec', ets:comp_match_spec()}  	 }).  -record(open_args, { -          file, -          type, -          keypos, -          repair, -          min_no_slots, -	  max_no_slots, -          ram_file, -          delayed_write, -          auto_save, -          access, -          version, -          debug +          file :: list(), +          type :: type(), +          keypos :: keypos(), +          repair :: 'force' | boolean(), +          min_no_slots :: no_slots(), +	  max_no_slots :: no_slots(), +          ram_file :: boolean(), +          delayed_write :: cache_parms(), +          auto_save :: auto_save(), +          access :: access(), +          debug :: boolean()           }).  -define(PATTERN_TO_OBJECT_MATCH_SPEC(Pat), [{Pat,[],['$_']}]). @@ -177,20 +179,13 @@  %%-define(PROFILE(C), C).  -define(PROFILE(C), void). --type access()    :: 'read' | 'read_write'. --type auto_save() :: 'infinity' | non_neg_integer().  -opaque bindings_cont() :: #dets_cont{}.  -opaque cont()    :: #dets_cont{}. --type keypos()    :: pos_integer().  -type match_spec()  :: ets:match_spec().  -type object()    :: tuple(). --type no_slots()  :: non_neg_integer() | 'default'.  -opaque object_cont() :: #dets_cont{}.  -type pattern()   :: atom() | tuple().  -opaque select_cont() :: #dets_cont{}. --type tab_name() :: term(). --type type()      :: 'bag' | 'duplicate_bag' | 'set'. --type version()   :: 8 | 9 | 'default'.  %%% Some further debug code was added in R12B-1 (stdlib-1.15.1):  %%% - there is a new open_file() option 'debug'; @@ -273,19 +268,20 @@ delete_all_objects(Tab) ->  delete_object(Tab, O) ->      badarg(treq(Tab, {delete_object, [O]}), [Tab, O]). +%% Backwards compatibility. +fsck(Fname, _Version) -> +    fsck(Fname). +  %% Given a filename, fsck it. Debug.  fsck(Fname) -> -    fsck(Fname, default). - -fsck(Fname, Version) ->      catch begin        {ok, Fd, FH} = read_file_header(Fname, read, false),        ?DEBUGF("FileHeader: ~p~n", [FH]),	     -      case (FH#fileheader.mod):check_file_header(FH, Fd) of +      case dets_v9:check_file_header(FH, Fd) of            {error, not_closed} -> -              fsck(Fd, make_ref(), Fname, FH, default, default, Version); -          {ok, _Head, _Extra} -> -              fsck(Fd, make_ref(), Fname, FH, default, default, Version); +              fsck(Fd, make_ref(), Fname, FH, default, default); +          {ok, _Head} -> +              fsck(Fd, make_ref(), Fname, FH, default, default);            Error ->                Error        end @@ -372,7 +368,7 @@ info(Tab) ->        Item :: 'access' | 'auto_save' | 'bchunk_format'              | 'hash' | 'file_size' | 'filename' | 'keypos' | 'memory'              | 'no_keys' | 'no_objects' | 'no_slots' | 'owner' | 'ram_file' -            | 'safe_fixed' | 'safe_fixed_monotonic_time' | 'size' | 'type' | 'version', +            | 'safe_fixed' | 'safe_fixed_monotonic_time' | 'size' | 'type',        Value :: term().  info(Tab, owner) -> @@ -640,8 +636,7 @@ open_file(File) ->                  | {'keypos', keypos()}                  | {'ram_file', boolean()}                  | {'repair', boolean() | 'force'} -                | {'type', type()} -                | {'version', version()}, +                | {'type', type()},        Reason :: term().  open_file(Tab, Args) when is_list(Args) -> @@ -674,13 +669,13 @@ remove_user(Pid, From) ->        Continuation2 :: select_cont(),        MatchSpec :: match_spec(). -repair_continuation(#dets_cont{match_program = B}=Cont, MS)  -    when is_binary(B) -> +repair_continuation(#dets_cont{match_program = {match_spec, B}}=Cont, MS) ->      case ets:is_compiled_ms(B) of  	true ->  	    Cont;  	false -> -            Cont#dets_cont{match_program = ets:match_spec_compile(MS)} +            Cont#dets_cont{match_program = {match_spec, +                                            ets:match_spec_compile(MS)}}      end;  repair_continuation(#dets_cont{}=Cont, _MS) ->      Cont; @@ -999,7 +994,9 @@ init_chunk_match(Tab, Pat, What, N, Safe) when is_integer(N), N >= 0;                      case req(Proc, {match, MP, Spec, N, Safe}) of                          {done, L} ->                              {L, #dets_cont{tab = Tab, proc = Proc, -                                           what = What, bin = eof}}; +                                           what = What, bin = eof, +                                           no_objs = default, +                                           alloc = <<>>}};                          {cont, State} ->                              chunk_match(State#dets_cont{what = What,                                                          tab = Tab, @@ -1041,17 +1038,17 @@ chunk_match(#dets_cont{proc = Proc}=State, Safe) ->  do_foldl_bins(Bins, true) ->      foldl_bins(Bins, []); -do_foldl_bins(Bins, MP) -> +do_foldl_bins(Bins, {match_spec, MP}) ->      foldl_bins(Bins, MP, []).  foldl_bins([], Terms) -> -    %% Preserve time order (version 9).  +    %% Preserve time order.      Terms;  foldl_bins([Bin | Bins], Terms) ->          foldl_bins(Bins, [binary_to_term(Bin) | Terms]).  foldl_bins([], _MP, Terms) -> -    %% Preserve time order (version 9). +    %% Preserve time order.      Terms;  foldl_bins([Bin | Bins], MP, Terms) ->      Term = binary_to_term(Bin), @@ -1068,7 +1065,7 @@ compile_match_spec(select, ?PATTERN_TO_OBJECT_MATCH_SPEC('_') = Spec) ->  compile_match_spec(select, Spec) ->      case catch ets:match_spec_compile(Spec) of  	X when is_binary(X) -> -	    {Spec, X}; +	    {Spec, {match_spec, X}};  	_ ->  	    badarg      end; @@ -1091,16 +1088,10 @@ defaults(Tab, Args) ->                             delayed_write = ?DEFAULT_CACHE,                             auto_save = timer:minutes(?DEFAULT_AUTOSAVE),                             access = read_write, -                           version = default,                             debug = false},      Fun = fun repl/2,      Defaults = lists:foldl(Fun, Defaults0, Args), -    case Defaults#open_args.version of -        8 -> -            Defaults#open_args{max_no_slots = default}; -        _ -> -            is_comp_min_max(Defaults) -    end. +    is_comp_min_max(Defaults).  to_list(T) when is_atom(T) -> atom_to_list(T);  to_list(T) -> T. @@ -1131,7 +1122,6 @@ repl({file, File}, Defs) when is_atom(File) ->  repl({keypos, P}, Defs) when is_integer(P), P > 0 ->      Defs#open_args{keypos =P};  repl({max_no_slots, I}, Defs)  -> -    %% Version 9 only.      MaxSlots = is_max_no_slots(I),      Defs#open_args{max_no_slots = MaxSlots};  repl({min_no_slots, I}, Defs)  -> @@ -1147,8 +1137,9 @@ repl({type, T}, Defs) ->      mem(T, [set, bag, duplicate_bag]),      Defs#open_args{type =T};  repl({version, Version}, Defs) -> -    V = is_version(Version), -    Defs#open_args{version = V}; +    %% Backwards compatibility. +    is_version(Version), +    Defs;  repl({debug, Bool}, Defs) ->      %% Not documented.      mem(Bool, [true, false]), @@ -1164,16 +1155,15 @@ is_max_no_slots(default) -> default;  is_max_no_slots(I) when is_integer(I), I > 0, I < 1 bsl 31 -> I.  is_comp_min_max(Defs) -> -    #open_args{max_no_slots = Max, min_no_slots = Min, version = V} = Defs, -    case V of -	_ when Min =:= default -> Defs; -	_ when Max =:= default -> Defs; -	_ -> true = Min =< Max, Defs +    #open_args{max_no_slots = Max, min_no_slots = Min} = Defs, +    if +        Min =:= default -> Defs; +	Max =:= default -> Defs; +	true -> true = Min =< Max, Defs      end. -is_version(default) -> default; -is_version(8) -> 8; -is_version(9) -> 9. +is_version(default) -> true; +is_version(9) -> true.  mem(X, L) ->      case lists:member(X, L) of @@ -1288,17 +1278,23 @@ badarg_exit(Reply, _A) ->  init(Parent, Server) ->      process_flag(trap_exit, true), -    open_file_loop(#head{parent = Parent, server = Server}). - -open_file_loop(Head) ->      %% The Dets server pretends the file is open before      %% internal_open() has been called, which means that unless the      %% internal_open message is applied first, other processes can      %% find the pid by calling dets_server:get_pid() and do things      %% before Head has been initialized properly.      receive -        ?DETS_CALL(From, {internal_open, _Ref, _Args}=Op) -> -            do_apply_op(Op, From, Head, 0) +        ?DETS_CALL(From, {internal_open, Ref, Args}=Op) -> +            try do_internal_open(Parent, Server, From, Ref, Args) of +                Head -> +                    open_file_loop(Head, 0) +            catch +                exit:normal -> +                    exit(normal); +                _:Bad -> +                    bug_found(no_name, Op, Bad, From), +                    exit(Bad) % give up +            end      end.  open_file_loop(Head, N) when element(1, Head#head.update_mode) =:= error -> @@ -1379,28 +1375,7 @@ do_apply_op(Op, From, Head, N) ->          exit:normal ->               exit(normal);          _:Bad ->  -            Name = Head#head.name, -            case dets_utils:debug_mode() of -                true -> -                    %% If stream_op/5 found more requests, this is not -                    %% the last operation. -                    error_logger:format -                      ("** dets: Bug was found when accessing table ~w,~n" -                       "** dets: operation was ~p and reply was ~w.~n" -                       "** dets: Stacktrace: ~w~n", -                       [Name, Op, Bad, erlang:get_stacktrace()]); -                false -> -                    error_logger:format -                      ("** dets: Bug was found when accessing table ~w~n", -                       [Name]) -            end, -            if -                From =/= self() -> -                    From ! {self(), {error, {dets_bug, Name, Op, Bad}}}, -                    ok; -                true -> % auto_save | may_grow | {delayed_write, _} -                    ok -            end, +            bug_found(Head#head.name, Op, Bad, From),              open_file_loop(Head, N)      end. @@ -1408,10 +1383,7 @@ apply_op(Op, From, Head, N) ->      case Op of  	{add_user, Tab, OpenArgs}->              #open_args{file = Fname, type = Type, keypos = Keypos,  -                       ram_file = Ram, access = Access,  -		       version = Version} = OpenArgs, -            VersionOK = (Version =:= default) or  -                        (Head#head.version =:= Version), +                       ram_file = Ram, access = Access} = OpenArgs,  	    %% min_no_slots and max_no_slots are not tested  	    Res = if  		      Tab =:= Head#head.name, @@ -1419,7 +1391,6 @@ apply_op(Op, From, Head, N) ->  		      Head#head.type =:= Type,  		      Head#head.ram_file =:= Ram,  		      Head#head.access =:= Access, -		      VersionOK,  		      Fname =:= Head#head.filename ->  			  ok;  		      true -> @@ -1475,21 +1446,14 @@ apply_op(Op, From, Head, N) ->              From ! {self(), Res},              ok;  	{internal_open, Ref, Args} -> -	    ?PROFILE(ep:do()), -	    case do_open_file(Args, Head#head.parent, Head#head.server,Ref) of -		{ok, H2} ->  -		    From ! {self(), ok}, -		    H2; -		Error ->  -		    From ! {self(), Error}, -		    exit(normal) -	    end; +            do_internal_open(Head#head.parent, Head#head.server, From, +                             Ref, Args);  	may_grow when Head#head.update_mode =/= saved ->  	    if  		Head#head.update_mode =:= dirty ->  		    %% Won't grow more if the table is full.  		    {H2, _Res} =  -			(Head#head.mod):may_grow(Head, 0, many_times), +			dets_v9:may_grow(Head, 0, many_times),  		    {N + 1, H2};  		true ->   		    ok @@ -1519,21 +1483,10 @@ apply_op(Op, From, Head, N) ->  	    From ! {self(), Res},  	    erlang:garbage_collect(),  	    {0, H2}; -	{delete_key, Keys} when Head#head.update_mode =:= dirty -> -	    if -		Head#head.version =:= 8 -> -		    {H2, Res} = fdelete_key(Head, Keys), -		    From ! {self(), Res}, -		    {N + 1, H2}; -		true -> -		    stream_op(Op, From, [], Head, N) -	    end; +	{delete_key, _Keys} when Head#head.update_mode =:= dirty -> +            stream_op(Op, From, [], Head, N);  	{delete_object, Objs} when Head#head.update_mode =:= dirty ->  	    case check_objects(Objs, Head#head.keypos) of -		true when Head#head.version =:= 8 -> -		    {H2, Res} = fdelete_object(Head, Objs), -		    From ! {self(), Res}, -		    {N + 1, H2};  		true ->  		    stream_op(Op, From, [], Head, N);  		false -> @@ -1551,10 +1504,6 @@ apply_op(Op, From, Head, N) ->              H2;  	{insert, Objs} when Head#head.update_mode =:= dirty ->  	    case check_objects(Objs, Head#head.keypos) of -		true when Head#head.version =:= 8 -> -		    {H2, Res} = finsert(Head, Objs), -		    From ! {self(), Res}, -		    {N + 1, H2};  		true ->  		    stream_op(Op, From, [], Head, N);  		false -> @@ -1565,10 +1514,6 @@ apply_op(Op, From, Head, N) ->              {H2, Res} = finsert_new(Head, Objs),              From ! {self(), Res},              {N + 1, H2}; -	{lookup_keys, Keys}  when Head#head.version =:= 8 -> -	    {H2, Res} = flookup_keys(Head, Keys), -	    From ! {self(), Res}, -	    H2;  	{lookup_keys, _Keys} ->  	    stream_op(Op, From, [], Head, N);  	{match_init, State, Safe} -> @@ -1584,10 +1529,6 @@ apply_op(Op, From, Head, N) ->  	    {H2, Res} = fmatch(Head, MP, Spec, NObjs, Safe, From),  	    From ! {self(), Res},  	    H2; -	{member, Key} when Head#head.version =:= 8 -> -	    {H2, Res} = fmember(Head, Key), -	    From ! {self(), Res}, -	    H2;  	{member, _Key} = Op ->  	    stream_op(Op, From, [], Head, N);  	{next, Key} -> @@ -1628,7 +1569,7 @@ apply_op(Op, From, Head, N) ->  	    apply_op(WriteOp, From, H2, 0);  	WriteOp when Head#head.access =:= read_write,  		     Head#head.update_mode =:= saved -> -	    case catch (Head#head.mod):mark_dirty(Head) of +	    case catch dets_v9:mark_dirty(Head) of  		ok ->  		    start_auto_save_timer(Head),  		    H2 = Head#head{update_mode = dirty}, @@ -1643,6 +1584,40 @@ apply_op(Op, From, Head, N) ->  	    ok      end. +bug_found(Name, Op, Bad, From) -> +    case dets_utils:debug_mode() of +        true -> +            %% If stream_op/5 found more requests, this is not +            %% the last operation. +            error_logger:format +              ("** dets: Bug was found when accessing table ~w,~n" +               "** dets: operation was ~p and reply was ~w.~n" +               "** dets: Stacktrace: ~w~n", +               [Name, Op, Bad, erlang:get_stacktrace()]); +        false -> +            error_logger:format +              ("** dets: Bug was found when accessing table ~w~n", +               [Name]) +    end, +    if +        From =/= self() -> +            From ! {self(), {error, {dets_bug, Name, Op, Bad}}}, +            ok; +        true -> % auto_save | may_grow | {delayed_write, _} +            ok +    end. + +do_internal_open(Parent, Server, From, Ref, Args) -> +    ?PROFILE(ep:do()), +    case do_open_file(Args, Parent, Server, Ref) of +        {ok, Head} -> +            From ! {self(), ok}, +            Head; +        Error -> +            From ! {self(), Error}, +            exit(normal) +    end. +  start_auto_save_timer(Head) when Head#head.auto_save =:= infinity ->      ok;  start_auto_save_timer(Head) -> @@ -1650,7 +1625,7 @@ start_auto_save_timer(Head) ->      _Ref = erlang:send_after(Millis, self(), ?DETS_CALL(self(), auto_save)),      ok. -%% Version 9: Peek the message queue and try to evaluate several +%% Peek the message queue and try to evaluate several  %% lookup requests in parallel. Evalute delete_object, delete and  %% insert as well.  stream_op(Op, Pid, Pids, Head, N) -> @@ -1760,7 +1735,7 @@ lookup_reply(P, O) ->  %% Callback functions for system messages handling.  %%-----------------------------------------------------------------  system_continue(_Parent, _, Head) -> -    open_file_loop(Head). +    open_file_loop(Head, 0).  system_terminate(Reason, _Parent, _, Head) ->      _NewHead = do_stop(Head), @@ -1793,7 +1768,8 @@ read_file_header(FileName, Access, RamFile) ->          dets_utils:pread_close(Fd, FileName, ?FILE_FORMAT_VERSION_POS, 4),      if           Version =< 8 -> -            dets_v8:read_file_header(Fd, FileName); +            _ = file:close(Fd), +            throw({error, {format_8_no_longer_supported, FileName}});          Version =:= 9 ->              dets_v9:read_file_header(Fd, FileName);          true -> @@ -1820,7 +1796,7 @@ perform_save(Head, DoSync) when Head#head.update_mode =:= dirty;  				Head#head.update_mode =:= new_dirty ->      case catch begin                     {Head1, []} = write_cache(Head), -                   {Head2, ok} = (Head1#head.mod):do_perform_save(Head1), +                   {Head2, ok} = dets_v9:do_perform_save(Head1),                     ok = ensure_written(Head2, DoSync),                     {Head2#head{update_mode = saved}, ok}                 end of @@ -1853,7 +1829,7 @@ ensure_written(Head, false) when not Head#head.ram_file ->  do_bchunk_init(Head, Tab) ->      case catch write_cache(Head) of  	{H2, []} -> -	    case (H2#head.mod):table_parameters(H2) of +	    case dets_v9:table_parameters(H2) of  		undefined ->  		    {H2, {error, old_version}};  		Parms -> @@ -1862,9 +1838,9 @@ do_bchunk_init(Head, Tab) ->                                L =:= <<>> -> eof;                                true -> <<>>                            end, -                    C0 = #dets_cont{no_objs = default, bin = Bin, alloc = L},  		    BinParms = term_to_binary(Parms), -		    {H2, {C0#dets_cont{tab = Tab, proc = self(),what = bchunk}, +		    {H2, {#dets_cont{no_objs = default, bin = Bin, alloc = L, +                                     tab = Tab, proc = self(),what = bchunk},                            [BinParms]}}  	    end;  	{NewHead, _} = HeadError when is_record(NewHead, head) -> @@ -1904,16 +1880,8 @@ do_delete_all_objects(Head) ->  	  max_no_slots = MaxSlots, cache = Cache} = Head,       CacheSz = dets_utils:cache_size(Cache),      ok = dets_utils:truncate(Fd, Fname, bof), -    (Head#head.mod):initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots, -				  Ram, CacheSz, Auto, true). - -%% -> {NewHead, Reply}, Reply = ok | Error. -fdelete_key(Head, Keys) -> -    do_delete(Head, Keys, delete_key). - -%% -> {NewHead, Reply}, Reply = ok | badarg | Error. -fdelete_object(Head, Objects) -> -    do_delete(Head, Objects, delete_object). +    dets_v9:initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots, +                          Ram, CacheSz, Auto, true).  ffirst(H) ->      Ref = make_ref(), @@ -1930,7 +1898,7 @@ ffirst1(H) ->      ffirst(NH, 0).  ffirst(H, Slot) -> -    case (H#head.mod):slot_objs(H, Slot) of +    case dets_v9:slot_objs(H, Slot) of  	'$end_of_table' -> {H, '$end_of_table'};  	[] -> ffirst(H, Slot+1);  	[X|_] -> {H, element(H#head.keypos, X)} @@ -2067,7 +2035,7 @@ finfo(H, auto_save) -> {H, H#head.auto_save};  finfo(H, bchunk_format) ->       case catch write_cache(H) of          {H2, []} -> -            case (H2#head.mod):table_parameters(H2) of +            case dets_v9:table_parameters(H2) of                  undefined = Undef ->                      {H2, Undef};                  Parms -> @@ -2100,7 +2068,7 @@ finfo(H, no_keys) ->  	{H2, _} = HeadError when is_record(H2, head) ->  	    HeadError      end; -finfo(H, no_slots) -> {H, (H#head.mod):no_slots(H)}; +finfo(H, no_slots) -> {H, dets_v9:no_slots(H)};  finfo(H, pid) -> {H, self()};  finfo(H, ram_file) -> {H, H#head.ram_file};  finfo(H, safe_fixed) -> @@ -2127,7 +2095,7 @@ finfo(H, size) ->  	    HeadError      end;  finfo(H, type) -> {H, H#head.type}; -finfo(H, version) -> {H, H#head.version}; +finfo(H, version) -> {H, 9};  finfo(H, _) -> {H, undefined}.  file_size(Fd, FileName) ->  @@ -2136,8 +2104,6 @@ file_size(Fd, FileName) ->  test_bchunk_format(_Head, undefined) ->      false; -test_bchunk_format(Head, _Term) when Head#head.version =:= 8 -> -    false;  test_bchunk_format(Head, Term) ->      dets_v9:try_bchunk_header(Term, Head) =/= not_ok. @@ -2206,7 +2172,7 @@ do_finit(Head, Init, Format, NoSlots) ->      #head{fptr = Fd, type = Type, keypos = Kp, auto_save = Auto,            cache = Cache, filename = Fname, ram_file = Ram,  	  min_no_slots = MinSlots0, max_no_slots = MaxSlots, -          name = Tab, update_mode = UpdateMode, mod = HMod} = Head, +          name = Tab, update_mode = UpdateMode} = Head,      CacheSz = dets_utils:cache_size(Cache),      {How, Head1} =  	case Format of @@ -2219,9 +2185,10 @@ do_finit(Head, Init, Format, NoSlots) ->  			{general_init, Head};  		    true ->  			ok = dets_utils:truncate(Fd, Fname, bof), -			{ok, H} = HMod:initiate_file(Fd, Tab, Fname, Type, Kp, -						     MinSlots, MaxSlots, Ram, -						     CacheSz, Auto, false), +			{ok, H} = +                            dets_v9:initiate_file(Fd, Tab, Fname, Type, Kp, +                                                  MinSlots, MaxSlots, Ram, +                                                  CacheSz, Auto, false),  			{general_init, H}  		end;  	    bchunk -> @@ -2230,7 +2197,7 @@ do_finit(Head, Init, Format, NoSlots) ->  	end,      case How of  	bchunk_init ->  -	    case HMod:bchunk_init(Head1, Init) of +	    case dets_v9:bchunk_init(Head1, Init) of  		{ok, NewHead} ->  		    {ok, NewHead#head{update_mode = dirty}};  		Error -> @@ -2238,10 +2205,10 @@ do_finit(Head, Init, Format, NoSlots) ->  	    end;  	general_init ->  	    Cntrs = ets:new(dets_init, []), -	    Input = HMod:bulk_input(Head1, Init, Cntrs), +	    Input = dets_v9:bulk_input(Head1, Init, Cntrs),  	    SlotNumbers = {Head1#head.min_no_slots, bulk_init, MaxSlots},  	    {Reply, SizeData} =  -		do_sort(Head1, SlotNumbers, Input, Cntrs, Fname, not_used), +		do_sort(Head1, SlotNumbers, Input, Cntrs, Fname),  	    Bulk = true,  	    case Reply of   		{ok, NoDups, H1} -> @@ -2297,7 +2264,8 @@ fmatch(Head, MP, Spec, N, Safe, From) ->  	    {NewHead, Reply} = flookup_keys(Head, Keys),  	    case Reply of  		Objs when is_list(Objs) -> -		    MatchingObjs = ets:match_spec_run(Objs, MP), +                    {match_spec, MS} = MP, +		    MatchingObjs = ets:match_spec_run(Objs, MS),  		    {NewHead, {done, MatchingObjs}};  		Error ->  		    {NewHead, Error} @@ -2377,7 +2345,7 @@ fmatch_delete(Head, C) ->  	{[], _} ->  	    {Head, {done, 0}};  	{RTs, NC} -> -	    MP = C#dets_cont.match_program, +	    {match_spec, MP} = C#dets_cont.match_program,  	    case catch filter_binary_terms(RTs, MP, []) of  		{'EXIT', _} ->                      Bad = dets_utils:bad_object(fmatch_delete, RTs), @@ -2405,7 +2373,7 @@ do_fmatch_delete_var_keys(Head, MP, _Spec, From) ->      C0 = init_scan(NewHead, default),      {NewHead, {cont, C0#dets_cont{match_program = MP}, 0}}. -do_fmatch_constant_keys(Head, Keys, MP) -> +do_fmatch_constant_keys(Head, Keys, {match_spec, MP}) ->      case flookup_keys(Head, Keys) of  	{NewHead, ReadTerms} when is_list(ReadTerms) ->  	    Terms = filter_terms(ReadTerms, MP, []), @@ -2454,18 +2422,8 @@ do_delete(Head, Things, What) ->  	    HeadError      end. -fmember(Head, Key) -> -    case catch begin -                   {Head2, [{_NoPid,Objs}]} =  -                       update_cache(Head, [Key], {lookup, nopid}), -                   {Head2, Objs =/= []} -               end of -        {NewHead, _} = Reply when is_record(NewHead, head) -> -            Reply -    end. -  fnext(Head, Key) -> -    Slot = (Head#head.mod):db_hash(Key, Head), +    Slot = dets_v9:db_hash(Key, Head),      Ref = make_ref(),      case catch {Ref, fnext(Head, Key, Slot)} of  	{Ref, {H, R}} ->  @@ -2476,7 +2434,7 @@ fnext(Head, Key) ->  fnext(H, Key, Slot) ->      {NH, []} = write_cache(H), -    case (H#head.mod):slot_objs(NH, Slot) of +    case dets_v9:slot_objs(NH, Slot) of  	'$end_of_table' -> {NH, '$end_of_table'};  	L -> fnext_search(NH, Key, Slot, L)      end. @@ -2490,7 +2448,7 @@ fnext_search(H, K, Slot, L) ->  %% We've got to continue to search for the next key in the next slot  fnext_slot(H, K, Slot) -> -    case (H#head.mod):slot_objs(H, Slot) of +    case dets_v9:slot_objs(H, Slot) of  	'$end_of_table' -> {H, '$end_of_table'};  	[] -> fnext_slot(H, K, Slot+1);  	L -> {H, element(H#head.keypos, hd(L))} @@ -2518,11 +2476,10 @@ fopen2(Fname, Tab) ->  	    Acc = read_write,  	    Ram = false,   	    {ok, Fd, FH} = read_file_header(Fname, Acc, Ram), -            Mod = FH#fileheader.mod, -            Do = case Mod:check_file_header(FH, Fd) of -                     {ok, Head1, ExtraInfo} -> +            Do = case dets_v9:check_file_header(FH, Fd) of +                     {ok, Head1} ->                           Head2 = Head1#head{filename = Fname}, -                         try {ok, Mod:init_freelist(Head2, ExtraInfo)} +                         try {ok, dets_v9:init_freelist(Head2)}                           catch                               throw:_ ->                                   {repair, " has bad free lists, repairing ..."} @@ -2536,8 +2493,7 @@ fopen2(Fname, Tab) ->              case Do of  		{repair, Mess} ->                      io:format(user, "dets: file ~tp~s~n", [Fname, Mess]), -                    Version = default, -                    case fsck(Fd, Tab, Fname, FH, default, default, Version) of +                    case fsck(Fd, Tab, Fname, FH, default, default) of                          ok ->                              fopen2(Fname, Tab);                          Error -> @@ -2570,33 +2526,23 @@ fopen_existing_file(Tab, OpenArgs) ->      #open_args{file = Fname, type = Type, keypos = Kp, repair = Rep,                 min_no_slots = MinSlots, max_no_slots = MaxSlots,                 ram_file = Ram, delayed_write = CacheSz, auto_save = -               Auto, access = Acc, version = Version, debug = Debug} = +               Auto, access = Acc, debug = Debug} =          OpenArgs,      {ok, Fd, FH} = read_file_header(Fname, Acc, Ram), -    V9 = (Version =:= 9) or (Version =:= default),      MinF = (MinSlots =:= default) or (MinSlots =:= FH#fileheader.min_no_slots),      MaxF = (MaxSlots =:= default) or (MaxSlots =:= FH#fileheader.max_no_slots), -    Mod = (FH#fileheader.mod), -    Wh = case Mod:check_file_header(FH, Fd) of -	     {ok, Head, true} when Rep =:= force, Acc =:= read_write, -				   FH#fileheader.version =:= 9, -				   FH#fileheader.no_colls =/= undefined, -				   MinF, MaxF, V9 -> -		 {compact, Head, true}; -             {ok, _Head, _Extra} when Rep =:= force, Acc =:= read -> +    Wh = case dets_v9:check_file_header(FH, Fd) of +	     {ok, Head} when Rep =:= force, Acc =:= read_write, +                             FH#fileheader.no_colls =/= undefined, +                             MinF, MaxF -> +	         {compact, Head}; +             {ok, _Head} when Rep =:= force, Acc =:= read ->                   throw({error, {access_mode, Fname}}); -	     {ok, Head, need_compacting} when Acc =:= read -> -                 {final, Head, true}; % Version 8 only. -	     {ok, _Head, need_compacting} when Rep =:= true -> -		 %% The file needs to be compacted due to a very big -		 %% and fragmented free_list. Version 8 only. -		 M = " is now compacted ...", -		 {repair, M}; -	     {ok, _Head, _Extra} when Rep =:= force -> +	     {ok, _Head} when Rep =:= force ->  		 M = ", repair forced.",  		 {repair, M}; -	     {ok, Head, ExtraInfo} -> -		 {final, Head, ExtraInfo}; +	     {ok, Head} -> +		 {final, Head};  	     {error, not_closed} when Rep =:= force, Acc =:= read_write ->  		 M = ", repair forced.",  		 {repair, M}; @@ -2605,17 +2551,13 @@ fopen_existing_file(Tab, OpenArgs) ->  		 {repair, M};  	     {error, not_closed} when Rep =:= false ->  		 throw({error, {needs_repair, Fname}}); -	     {error, version_bump} when Rep =:= true, Acc =:= read_write -> -                 %% Version 8 only -		 M = " old version, upgrading ...", -		 {repair, M};  	     {error, Reason} ->  		 throw({error, {Reason, Fname}})  	 end,      Do = case Wh of -             {Tag, Hd, Extra} when Tag =:= final; Tag =:= compact -> +             {Tag, Hd} when Tag =:= final; Tag =:= compact ->                   Hd1 = Hd#head{filename = Fname}, -                 try {Tag, Mod:init_freelist(Hd1, Extra)} +                 try {Tag, dets_v9:init_freelist(Hd1)}                   catch                       throw:_ ->                           {repair, " has bad free lists, repairing ..."} @@ -2643,23 +2585,20 @@ fopen_existing_file(Tab, OpenArgs) ->  			      "now repairing ...~n", [Fname]),                      {ok, Fd2, _FH} = read_file_header(Fname, Acc, Ram),                      do_repair(Fd2, Tab, Fname, FH, MinSlots, MaxSlots,  -			      Version, OpenArgs) +			      OpenArgs)  	    end;  	{repair, Mess} ->  	    io:format(user, "dets: file ~tp~s~n", [Fname, Mess]),              do_repair(Fd, Tab, Fname, FH, MinSlots, MaxSlots,  -		      Version, OpenArgs); -	_ when FH#fileheader.version =/= Version, Version =/= default -> -	    throw({error, {version_mismatch, Fname}}); +		      OpenArgs);  	{final, H} ->  	    H1 = H#head{auto_save = Auto},  	    open_final(H1, Fname, Acc, Ram, CacheSz, Tab, Debug)      end. -do_repair(Fd, Tab, Fname, FH, MinSlots, MaxSlots, Version, OpenArgs) -> -    case fsck(Fd, Tab, Fname, FH, MinSlots, MaxSlots, Version) of +do_repair(Fd, Tab, Fname, FH, MinSlots, MaxSlots, OpenArgs) -> +    case fsck(Fd, Tab, Fname, FH, MinSlots, MaxSlots) of  	ok -> -	    %% No need to update 'version'.  	    erlang:garbage_collect(),  	    fopen3(Tab, OpenArgs#open_args{repair = false});  	Error -> @@ -2673,8 +2612,8 @@ open_final(Head, Fname, Acc, Ram, CacheSz, Tab, Debug) ->  		      filename = Fname,  		      name = Tab,  		      cache = dets_utils:new_cache(CacheSz)}, -    init_disk_map(Head1#head.version, Tab, Debug), -    (Head1#head.mod):cache_segps(Head1#head.fptr, Fname, Head1#head.next), +    init_disk_map(Tab, Debug), +    dets_v9:cache_segps(Head1#head.fptr, Fname, Head1#head.next),      check_growth(Head1),      {ok, Head1}. @@ -2683,7 +2622,7 @@ fopen_init_file(Tab, OpenArgs) ->      #open_args{file = Fname, type = Type, keypos = Kp,                  min_no_slots = MinSlotsArg, max_no_slots = MaxSlotsArg,   	       ram_file = Ram, delayed_write = CacheSz, auto_save = Auto,  -               version = UseVersion, debug = Debug} = OpenArgs, +               debug = Debug} = OpenArgs,      MinSlots = choose_no_slots(MinSlotsArg, ?DEFAULT_MIN_NO_SLOTS),      MaxSlots = choose_no_slots(MaxSlotsArg, ?DEFAULT_MAX_NO_SLOTS),      FileSpec = if @@ -2691,20 +2630,11 @@ fopen_init_file(Tab, OpenArgs) ->  		   true -> Fname  	       end,      {ok, Fd} = dets_utils:open(FileSpec, open_args(read_write, Ram)), -    Version = if -                  UseVersion =:= default -> -                      case os:getenv("DETS_USE_FILE_FORMAT") of -                          "8" -> 8; -                          _ -> 9 -                      end; -                  true -> -                      UseVersion -              end, -    Mod = version2module(Version),      %% No need to truncate an empty file. -    init_disk_map(Version, Tab, Debug), -    case catch Mod:initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots, -				 Ram, CacheSz, Auto, true) of +    init_disk_map(Tab, Debug), +    case catch dets_v9:initiate_file(Fd, Tab, Fname, Type, Kp, +                                     MinSlots, MaxSlots, +                                     Ram, CacheSz, Auto, true) of  	{error, Reason} when Ram ->  	    _ = file:close(Fd),  	    throw({error, Reason}); @@ -2719,15 +2649,13 @@ fopen_init_file(Tab, OpenArgs) ->      end.  %% Debug. -init_disk_map(9, Name, Debug) -> +init_disk_map(Name, Debug) ->      case Debug orelse dets_utils:debug_mode() of          true ->               dets_utils:init_disk_map(Name);          false ->              ok -    end; -init_disk_map(_Version, _Name, _Debug) -> -    ok. +    end.  open_args(Access, RamFile) ->      A1 = case Access of @@ -2740,15 +2668,7 @@ open_args(Access, RamFile) ->  	   end,      A1 ++ A2 ++ [binary, read]. -version2module(V) when V =< 8 -> dets_v8; -version2module(9) -> dets_v9. - -module2version(dets_v8) -> 8; -module2version(dets_v9) -> 9; -module2version(not_used) -> 9. -  %% -> ok | throw(Error)  -%% For version 9 tables only.  compact(SourceHead) ->      #head{name = Tab, filename = Fname, fptr = SFd, type = Type, keypos = Kp,  	  ram_file = Ram, auto_save = Auto} = SourceHead, @@ -2759,7 +2679,7 @@ compact(SourceHead) ->      %% It is normally not possible to have two open tables in the same      %% process since the process dictionary is used for caching      %% segment pointers, but here is works anyway--when reading a file -    %% serially the pointers to not need to be used. +    %% serially the pointers do not need to be used.      Head = case catch dets_v9:prep_table_copy(Fd, Tab, Tmp, Type, Kp, Ram,   					      CacheSz, Auto, TblParms) of  	       {ok, H} -> @@ -2794,7 +2714,7 @@ compact(SourceHead) ->  %% -> ok | Error  %% Closes Fd. -fsck(Fd, Tab, Fname, FH, MinSlotsArg, MaxSlotsArg, Version) -> +fsck(Fd, Tab, Fname, FH, MinSlotsArg, MaxSlotsArg) ->      %% MinSlots and MaxSlots are the option values.      #fileheader{min_no_slots = MinSlotsFile,                   max_no_slots = MaxSlotsFile} = FH, @@ -2807,10 +2727,10 @@ fsck(Fd, Tab, Fname, FH, MinSlotsArg, MaxSlotsArg, Version) ->      %% If the number of objects (keys) turns out to be significantly      %% different from NoSlots, we try again with the correct number of      %% objects (keys). -    case fsck_try(Fd, Tab, FH, Fname, SlotNumbers, Version) of +    case fsck_try(Fd, Tab, FH, Fname, SlotNumbers) of          {try_again, BetterNoSlots} ->  	    BetterSlotNumbers = {MinSlots, BetterNoSlots, MaxSlots}, -            case fsck_try(Fd, Tab, FH, Fname, BetterSlotNumbers, Version) of +            case fsck_try(Fd, Tab, FH, Fname, BetterSlotNumbers) of                  {try_again, _} ->                      _ = file:close(Fd),                      {error, {cannot_repair, Fname}}; @@ -2829,7 +2749,7 @@ choose_no_slots(NoSlots, _) -> NoSlots.  %% Initiating a table using a fun and repairing (or converting) a  %% file are completely different things, but nevertheless the same  %% method is used in both cases... -fsck_try(Fd, Tab, FH, Fname, SlotNumbers, Version) -> +fsck_try(Fd, Tab, FH, Fname, SlotNumbers) ->      Tmp = tempfile(Fname),      #fileheader{type = Type, keypos = KeyPos} = FH,      {_MinSlots, EstNoSlots, MaxSlots} = SlotNumbers, @@ -2838,7 +2758,7 @@ fsck_try(Fd, Tab, FH, Fname, SlotNumbers, Version) ->  			  max_no_slots = MaxSlots,                            ram_file = false, delayed_write = ?DEFAULT_CACHE,                            auto_save = infinity, access = read_write, -                          version = Version, debug = false}, +                          debug = false},      case catch fopen3(Tab, OpenArgs) of  	{ok, Head} ->              case fsck_try_est(Head, Fd, Fname, SlotNumbers, FH) of @@ -2888,10 +2808,9 @@ assure_no_file(File) ->  %% -> {ok, NewHead} | {try_again, integer()} | Error  fsck_try_est(Head, Fd, Fname, SlotNumbers, FH) ->      %% Mod is the module to use for reading input when repairing. -    Mod = FH#fileheader.mod,      Cntrs = ets:new(dets_repair, []), -    Input = Mod:fsck_input(Head, Fd, Cntrs, FH), -    {Reply, SizeData} = do_sort(Head, SlotNumbers, Input, Cntrs, Fname, Mod), +    Input = dets_v9:fsck_input(Head, Fd, Cntrs, FH), +    {Reply, SizeData} = do_sort(Head, SlotNumbers, Input, Cntrs, Fname),      Bulk = false,      case Reply of           {ok, NoDups, H1} -> @@ -2906,14 +2825,13 @@ fsck_try_est(Head, Fd, Fname, SlotNumbers, FH) ->  	    Else      end. -do_sort(Head, SlotNumbers, Input, Cntrs, Fname, Mod) -> -    OldV = module2version(Mod), +do_sort(Head, SlotNumbers, Input, Cntrs, Fname) ->      %% output_objs/4 replaces {LogSize,NoObjects} in Cntrs by      %% {LogSize,Position,Data,NoObjects | NoCollections}.      %% Data = {FileName,FileDescriptor} | [object()] -    %% For small tables Data may be a list of objects which is more +    %% For small tables Data can be a list of objects which is more      %% efficient since no temporary files are created. -    Output = (Head#head.mod):output_objs(OldV, Head, SlotNumbers, Cntrs), +    Output = dets_v9:output_objs(Head, SlotNumbers, Cntrs),      TmpDir = filename:dirname(Fname),      Reply = (catch file_sorter:sort(Input, Output,   				    [{format, binary},{tmpdir, TmpDir}])), @@ -2954,13 +2872,6 @@ fsck_copy1([SzData | L], Head, Bulk, NoDups) ->  	{ok, Copied} when Copied =:= ExpectedSize;  			  NoObjects =:= 0 -> % the segments  	    fsck_copy1(L, Head, Bulk, NoDups); -	{ok, Copied} when Bulk, Head#head.version =:= 8 -> -	    NoZeros = ExpectedSize - Copied, -	    Dups = NoZeros div Size, -	    Addr = Pos+Copied, -	    NewHead = free_n_objects(Head, Addr, Size-1, NoDups), -	    NewNoDups = NoDups - Dups, -	    fsck_copy1(L, NewHead, Bulk, NewNoDups);  	{ok, _Copied} -> % should never happen  	    close_files(Bulk, L, Head),  	    Reason = if Bulk -> initialization_failed;  @@ -2975,13 +2886,6 @@ fsck_copy1([], Head, _Bulk, NoDups) when NoDups =/= 0 ->  fsck_copy1([], Head, _Bulk, _NoDups) ->      {ok, Head#head{update_mode = dirty}}.  -free_n_objects(Head, _Addr, _Size, 0) -> -    Head; -free_n_objects(Head, Addr, Size, N) -> -    {NewHead, _} = dets_utils:free(Head, Addr, Size), -    NewAddr = Addr + Size + 1, -    free_n_objects(NewHead, NewAddr, Size, N-1). -  close_files(false, SizeData, Head) ->      _ = file:close(Head#head.fptr),      close_files(true, SizeData, Head); @@ -3000,7 +2904,7 @@ close_tmp(Fd) ->  fslot(H, Slot) ->      case catch begin                     {NH, []} = write_cache(H), -                   Objs = (NH#head.mod):slot_objs(NH, Slot), +                   Objs = dets_v9:slot_objs(NH, Slot),                     {NH, Objs}                 end of          {NewHead, _Objects} = Reply when is_record(NewHead, head) -> @@ -3050,7 +2954,7 @@ where_is_object(Head, Object) ->  	true ->  	    case catch write_cache(Head) of  		{NewHead, []} -> -		    {NewHead, (Head#head.mod):find_object(NewHead, Object)}; +		    {NewHead, dets_v9:find_object(NewHead, Object)};  		{NewHead, _} = HeadError when is_record(NewHead, head) ->  		    HeadError  	    end; @@ -3063,13 +2967,9 @@ check_objects([T | Ts], Kp) when tuple_size(T) >= Kp ->  check_objects(L, _Kp) ->      L =:= []. -no_things(Head) when Head#head.no_keys =:= undefined -> -    Head#head.no_objects;  no_things(Head) ->      Head#head.no_keys. -file_no_things(FH) when FH#fileheader.no_keys =:= undefined -> -    FH#fileheader.no_objects;  file_no_things(FH) ->      FH#fileheader.no_keys. @@ -3110,7 +3010,7 @@ update_cache(Head, ToAdd) ->      if   	Lookup; NewSize >= Cache#cache.tsize ->  	    %% The cache is considered full, or some lookup. -	    {NewHead, LU, PwriteList} = (Head#head.mod):write_cache(Head1), +	    {NewHead, LU, PwriteList} = dets_v9:write_cache(Head1),  	    {NewHead, Found ++ LU, PwriteList};  	NewC =:= [] ->  	    {Head1, Found, []}; @@ -3195,7 +3095,7 @@ delayed_write(Head, WrTime) ->  %% -> {NewHead, [LookedUpObject]} | throw({NewHead, Error})  write_cache(Head) -> -    {Head1, LU, PwriteList} = (Head#head.mod):write_cache(Head), +    {Head1, LU, PwriteList} = dets_v9:write_cache(Head),      {NewHead, ok} = dets_utils:pwrite(Head1, PwriteList),      {NewHead, LU}. @@ -3248,7 +3148,7 @@ scan(Head, C) -> % when is_record(C, dets_cont)      scan(Bin, Head, From, To, L, [], R, {C, Head#head.type}).  scan(Bin, H, From, To, L, Ts, R, {C0, Type} = C) -> -    case (H#head.mod):scan_objs(H, Bin, From, To, L, Ts, R, Type) of +    case dets_v9:scan_objs(H, Bin, From, To, L, Ts, R, Type) of          {more, NFrom, NTo, NL, NTs, NR, Sz} ->              scan_read(H, NFrom, NTo, Sz, NL, NTs, NR, C);          {stop, <<>>=B, NFrom, NTo, <<>>=NL, NTs} -> @@ -3305,7 +3205,7 @@ time_now() ->  make_timestamp(MonTime, TimeOffset) ->      ErlangSystemTime = erlang:convert_time_unit(MonTime+TimeOffset,  						native, -						micro_seconds), +						microsecond),      MegaSecs = ErlangSystemTime div 1000000000000,      Secs = ErlangSystemTime div 1000000 - MegaSecs*1000000,      MicroSecs = ErlangSystemTime rem 1000000, @@ -3317,7 +3217,7 @@ file_info(FileName) ->      case catch read_file_header(FileName, read, false) of  	{ok, Fd, FH} ->  	    _ = file:close(Fd), -            (FH#fileheader.mod):file_info(FH); +            dets_v9:file_info(FH);  	Other ->  	    Other      end. @@ -3332,15 +3232,13 @@ get_head_field(Fd, Field) ->  view(FileName) ->      case catch read_file_header(FileName, read, false) of          {ok, Fd, FH} -> -	    Mod = FH#fileheader.mod, -            try Mod:check_file_header(FH, Fd) of -                {ok, H0, ExtraInfo} -> -                    Mod = FH#fileheader.mod, -                    case Mod:check_file_header(FH, Fd) of -                        {ok, H0, ExtraInfo} -> -                            H = Mod:init_freelist(H0, ExtraInfo), +            try dets_v9:check_file_header(FH, Fd) of +                {ok, H0} -> +                    case dets_v9:check_file_header(FH, Fd) of +                        {ok, H0} -> +                            H = dets_v9:init_freelist(H0),                              v_free_list(H), -                            Mod:v_segments(H), +                            dets_v9:v_segments(H),                              ok;                          X ->                              X diff --git a/lib/stdlib/src/dets.hrl b/lib/stdlib/src/dets.hrl index 6ebeb96156..b5e732b08f 100644 --- a/lib/stdlib/src/dets.hrl +++ b/lib/stdlib/src/dets.hrl @@ -21,7 +21,7 @@  -define(DEFAULT_MIN_NO_SLOTS, 256).  -define(DEFAULT_MAX_NO_SLOTS, 32*1024*1024).  -define(DEFAULT_AUTOSAVE, 3). % minutes --define(DEFAULT_CACHE, {3000, 14000}). % {delay,size} in {milliseconds,bytes} +-define(DEFAULT_CACHE, {3000, 14000}). % cache_parms()  %% Type.  -define(SET, 1). @@ -46,83 +46,111 @@  -define(DETS_CALL(Pid, Req), {'$dets_call', Pid, Req}). +-type access()      :: 'read' | 'read_write'. +-type auto_save()   :: 'infinity' | non_neg_integer(). +-type hash_bif()    :: 'phash' | 'phash2'. +-type keypos()      :: pos_integer(). +-type no_colls()    :: [{LogSize :: non_neg_integer(), +                         NoCollections :: non_neg_integer()}]. +-type no_slots()    :: 'default' | non_neg_integer(). +-type tab_name()    :: term(). +-type type()        :: 'bag' | 'duplicate_bag' | 'set'. +-type update_mode() :: 'dirty' +                     | 'new_dirty' +                     | 'saved' +                     | {'error', Reason :: term()}. +  %% Record holding the file header and more.  -record(head,  { -	  m,               % size -	  m2,              % m * 2 -	  next,            % next position for growth (segm mgmt only) -	  fptr,            % the file descriptor -	  no_objects,      % number of objects in table, -	  no_keys,         % number of keys (version 9 only) -	  maxobjsize,      % 2-log of the size of the biggest object -                           % collection (version 9 only) +	  m :: non_neg_integer(),    % size +	  m2 :: non_neg_integer(),   % m * 2 +	  next :: non_neg_integer(), % next position for growth +                                     % (segm mgmt only) +	  fptr :: file:fd(),         % the file descriptor +	  no_objects :: non_neg_integer() , % number of objects in table, +	  no_keys :: non_neg_integer(),     % number of keys +	  maxobjsize :: 'undefined' | non_neg_integer(), % 2-log of +                           % the size of the biggest object collection  	  n,               % split indicator -	  type,            % set | bag | duplicate_bag -	  keypos,          % default is 1 as for ets -	  freelists,       % tuple of free lists of buddies -	                   % if fixed =/= false, then a pair of freelists -	  freelists_p,     % cached FreelistsPointer -	  no_collections,  % [{LogSize,NoCollections}] | undefined; number of -	                   % object collections per size (version 9(b)) -	  auto_save,       % Integer | infinity  -	  update_mode,     % saved | dirty | new_dirty | {error, Reason} -	  fixed = false,   % false | {now_time(), [{pid(),Counter}]} -                           % time of first fix, and number of fixes per process -	  hash_bif,        % hash bif used for this file (phash2, phash, hash) -          has_md5,         % whether the header has an MD5 sum (version 9(c)) -	  min_no_slots,    % minimum number of slots (default or integer) -	  max_no_slots,    % maximum number of slots (default or integer) -	  cache,           % cache(). Write cache. - -	  filename,             % name of the file being used -	  access = read_write,  % read | read_write -	  ram_file = false,     % true | false -	  name,                 % the name of the table - -	  parent,               % The supervisor of Dets processes. -	  server,               % The creator of Dets processes. - -          %% Depending on the file format: -          version, -          mod, -          bump, -          base +	  type :: type(), +	  keypos :: keypos(), % default is 1 as for ets +	  freelists :: 'undefined' +                     | tuple(), % tuple of free lists of buddies +	                        % if fixed =/= false, then a pair of freelists +	  freelists_p :: 'undefined' +                       | non_neg_integer(),  % cached FreelistsPointer +	  no_collections :: 'undefined' +                          | no_colls(), % number of object collections +                                        % per size (version 9(b)) +	  auto_save :: auto_save(), +	  update_mode :: update_mode(), +	  fixed = false :: 'false' +                         | {{integer(), integer()}, % time of first fix, +                            [{pid(),   % and number of fixes per process +                              non_neg_integer()}]}, +	  hash_bif :: hash_bif(),  % hash bif used for this file +          has_md5 :: boolean(),    % whether the header has +                                   % an MD5 sum (version 9(c)) +	  min_no_slots :: no_slots(),  % minimum number of slots +	  max_no_slots :: no_slots(),  % maximum number of slots +	  cache :: 'undefined' | cache(), % Write cache. + +	  filename :: file:name(), % name of the file being used +	  access = read_write :: access(), +	  ram_file = false :: boolean(), +	  name :: tab_name(),      % the name of the table + +	  parent :: 'undefined' | pid(), % The supervisor of Dets processes. +	  server :: 'undefined' | pid(), % The creator of Dets processes. + +          bump :: non_neg_integer(), +          base :: non_neg_integer()  	 }).  %% Info extracted from the file header.  -record(fileheader, { -	  freelist, -          fl_base, -	  cookie, -	  closed_properly, -	  type, -	  version, -	  m, -	  next, -	  keypos, -	  no_objects, -	  no_keys, -	  min_no_slots, -	  max_no_slots, -	  no_colls, -	  hash_method, -          read_md5, -          has_md5, -          md5, -	  trailer, -	  eof, -	  n, -	  mod +	  freelist :: non_neg_integer(), +          fl_base :: non_neg_integer(), +	  cookie :: non_neg_integer(), +	  closed_properly :: non_neg_integer(), +	  type :: 'badtype' | type(), +	  version :: non_neg_integer(), +	  m :: non_neg_integer(), +	  next :: non_neg_integer(), +	  keypos :: keypos(), +	  no_objects :: non_neg_integer(), +	  no_keys :: non_neg_integer(), +	  min_no_slots :: non_neg_integer(), +	  max_no_slots :: non_neg_integer(), +	  no_colls :: 'undefined' | no_colls(), +	  hash_method :: non_neg_integer(), +          read_md5 :: binary(), +          has_md5 :: boolean(), +          md5 :: binary(), +	  trailer :: non_neg_integer(), +	  eof :: non_neg_integer(), +	  n  	}). +-type delay() :: non_neg_integer(). +-type threshold() :: non_neg_integer(). +-type cache_parms() :: +        {Delay :: delay(), % max time items are kept in RAM only, +                           % in milliseconds +         Size :: threshold()}. % threshold size of cache, in bytes +  %% Write Cache.  -record(cache, { -	 cache,   % [{Key,{Seq,Item}}], write cache, last item first -         csize,   % current size of the cached items -         inserts, % upper limit on number of inserted keys -	 wrtime,  % last write or update time -	 tsize,   % threshold size of cache, in bytes -	 delay    % max time items are kept in RAM only, in milliseconds +	 cache :: % write cache, last item first +                 [{Key :: term(), +                   {Seq :: non_neg_integer(), Item :: term()}}], +         csize :: non_neg_integer(), % current size of the cached items +         inserts :: % upper limit on number of inserted keys +                    non_neg_integer(), +	 wrtime :: 'undefined' | integer(),  % last write or update time +	 tsize :: threshold(), % threshold size of cache +	 delay :: delay()      % max time items are kept in RAM only  	 }). +-type cache() :: #cache{}. diff --git a/lib/stdlib/src/dets_utils.erl b/lib/stdlib/src/dets_utils.erl index 34a8ddddaa..da6ebd18f2 100644 --- a/lib/stdlib/src/dets_utils.erl +++ b/lib/stdlib/src/dets_utils.erl @@ -20,13 +20,13 @@  -module(dets_utils).  %% Utility functions common to several dets file formats. -%% To be used from dets, dets_v8 and dets_v9 only. +%% To be used from modules dets and dets_v9 only.  -export([cmp/2, msort/1, mkeysort/2, mkeysearch/3, family/1]).  -export([rename/2, pread/2, pread/4, ipread/3, pwrite/2, write/2,           truncate/2, position/2, sync/1, open/2, truncate/3, fwrite/3, -         write_file/2, position/3, position_close/3, pwrite/4, +         write_file/2, position/3, position_close/3,           pwrite/3, pread_close/4, read_n/2, pread_n/3, read_4/2]).  -export([code_to_type/1, type_to_code/1]). @@ -44,8 +44,6 @@           all_allocated_as_list/1, find_allocated/4, find_next_allocated/3,           log2/1, make_zeros/1]). --export([init_slots_from_old_file/2]). -  -export([list_to_tree/1, tree_to_bin/5]).  -compile({inline, [{sz2pos,1}, {adjust_addr,3}]}). @@ -308,12 +306,6 @@ position_close(Fd, FileName, Pos) ->  	OK -> OK      end. -pwrite(Fd, FileName, Position, B) -> -    case file:pwrite(Fd, Position, B) of -	ok -> ok; -	Error -> file_error(FileName, {error, Error}) -    end. -  pwrite(Fd, FileName, Bins) ->      case file:pwrite(Fd, Bins) of  	ok -> @@ -478,20 +470,6 @@ new_cache({Delay, Size}) ->  %%%             Ullman. I think buddy systems were invented by Knuth, a long  %%%             time ago. -init_slots_from_old_file([{Slot,Addr} | T], Ftab) -> -    init_slot(Slot+1,[{Slot,Addr} | T], Ftab); -init_slots_from_old_file([], Ftab) -> -    Ftab. - -init_slot(_Slot,[], Ftab) -> -    Ftab; % should never happen -init_slot(_Slot,[{_Addr,0}|T], Ftab) -> -    init_slots_from_old_file(T, Ftab); -init_slot(Slot,[{_Slot1,Addr}|T], Ftab) -> -    Stree = element(Slot, Ftab), -    %%    io:format("init_slot ~p:~p~n",[Slot, Addr]), -    init_slot(Slot,T,setelement(Slot, Ftab, bplus_insert(Stree, Addr))). -  %%% The free lists are kept in RAM, and written to the end of the file  %%% from time to time. It is possible that a considerable amount of  %%% memory is used for a fragmented file. diff --git a/lib/stdlib/src/dets_v8.erl b/lib/stdlib/src/dets_v8.erl deleted file mode 100644 index 1bf53d91b1..0000000000 --- a/lib/stdlib/src/dets_v8.erl +++ /dev/null @@ -1,1594 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2016. All Rights Reserved. -%% -%% Licensed under the Apache License, Version 2.0 (the "License"); -%% you may not use this file except in compliance with the License. -%% You may obtain a copy of the License at -%% -%%     http://www.apache.org/licenses/LICENSE-2.0 -%% -%% Unless required by applicable law or agreed to in writing, software -%% distributed under the License is distributed on an "AS IS" BASIS, -%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -%% See the License for the specific language governing permissions and -%% limitations under the License. -%% -%% %CopyrightEnd% -%% --module(dets_v8). - -%% Dets files, implementation part. This module handles versions up to -%% and including 8(c). To be called from dets.erl only. - --export([mark_dirty/1, read_file_header/2, -         check_file_header/2, do_perform_save/1, initiate_file/11, -         init_freelist/2, fsck_input/4, -         bulk_input/3, output_objs/4, write_cache/1, may_grow/3, -         find_object/2, re_hash/2, slot_objs/2, scan_objs/8, -         db_hash/2, no_slots/1, table_parameters/1]). - --export([file_info/1, v_segments/1]). - --export([cache_segps/3]). - -%% For backward compatibility. --export([sz2pos/1]). - --dialyzer(no_improper_lists). - --compile({inline, [{sz2pos,1},{scan_skip,7}]}). --compile({inline, [{skip_bytes,5}, {get_segp,1}]}). --compile({inline, [{wl_lookup,5}]}). --compile({inline, [{actual_seg_size,0}]}). - --include("dets.hrl"). - -%%  The layout of the file is : -%% -%%   bytes   decsription -%%  ---------------------- File header -%%    4      FreelistsPointer -%%    4      Cookie -%%    4      ClosedProperly (pos=8) -%%    4      Type (pos=12) -%%    4      Version (pos=16) -%%    4      M -%%    4      Next -%%    4      KeyPos -%%    4      NoObjects -%%    4      N -%%  ------------------ end of file header -%%    4*8192 SegmentArray -%%  ------------------ -%%    4*256  First segment -%%  ----------------------------- This is BASE. -%%    ???    Objects (free and alive) -%%    4*256  Second segment (2 kB now, due to a bug) -%%    ???    Objects (free and alive) -%%    ... more objects and segments ... -%%  ----------------------------- -%%    ???    Free lists -%%  ----------------------------- -%%    4      File size, in bytes.  - -%%  The first slot (0) in the segment array always points to the -%%  pre-allocated first segment. -%%  Before we can find an object we must find the slot where the -%%  object resides. Each slot is a (possibly empty) list (or chain) of -%%  objects that hash to the same slot. If the value stored in the -%%  slot is zero, the slot chain is empty. If the slot value is -%%  non-zero, the value points to a position in the file where the -%%  chain starts. Each object in a chain has the following layout: -%% -%%   bytes  decsription -%%  -------------------- -%%    4     Pointer to the next object of the chain. -%%    4     Size of the object in bytes (Sz). -%%    4     Status  (FREE or ACTIVE) -%%    Sz    Binary representing the object -%% -%%  The status field is used while repairing a file (but not next or size). -%% -%%|---------------| -%%|      head     | -%%|       	  | -%%|               | -%%|_______________| -%%|		  |------| -%%|___seg ptr1____|      | -%%|		  |      | -%%|__ seg ptr 2___|      | -%%|               |      |    segment 1 -%%|	....	  |      V _____________ -%%			 |		| -%%			 |		| -%%			 |___slot 0 ____| -%%                       |              | -%%                       |___slot 1 ____|-----| -%%			 |		|     | -%%			 |   .....	|     |  1:st obj in slot 1 -%%					      V  segment 1 -%%						|-----------| -%%						|  next     | -%%						|___________| -%%						|  size     | -%%						|___________| -%%						|  status   | -%%						|___________| -%%						|	    | -%%						|           | -%%						|   obj     | -%%						|           | - -%%% -%%% File header -%%% - --define(HEADSZ, 40).          % The size of the file header, in bytes. --define(SEGSZ, 256).          % Size of a segment, in words. --define(SEGSZ_LOG2, 8). --define(SEGARRSZ, 8192).      % Maximal number of segments. --define(SEGADDR(SegN), (?HEADSZ + (4 * (SegN)))). --define(BASE, ?SEGADDR((?SEGSZ + ?SEGARRSZ))). --define(MAXOBJS, (?SEGSZ * ?SEGARRSZ)). % 2 M objects - --define(SLOT2SEG(S), ((S) bsr ?SEGSZ_LOG2)). - -%% BIG is used for hashing. BIG must be greater than the maximum -%% number of slots, currently MAXOBJS. --define(BIG, 16#ffffff). - -%% Hard coded positions into the file header: --define(FREELIST_POS, 0). --define(CLOSED_PROPERLY_POS, 8). --define(D_POS, 20). --define(NO_OBJECTS_POS, (?D_POS + 12)). - -%% The version of a dets file is indicated by the ClosedProperly -%% field. Version 6 was used in the R1A release, and version 7 in the -%% R1B release up to and including the R3B01 release. Both version 6 -%% and version 7 indicate properly closed files by the value -%% CLOSED_PROPERLY. -%% -%% The current version, 8, has three sub-versions: -%% -%% - 8(a), indicated by the value CLOSED_PROPERLY (same as in versions 6  -%%         and 7), introduced in R3B02; -%% - 8(b), indicated by the value CLOSED_PROPERLY2(_NEED_COMPACTING), -%%         introduced in R5A and used up to and including R6A; -%% - 8(c), indicated by the value CLOSED_PROPERLY_NEW_HASH(_NEED_COMPACTING), -%%         in use since R6B. -%% -%% The difference between the 8(a) and the 8(b) versions is the format -%% used for free lists saved on dets files. -%% The 8(c) version uses a different hashing algorithm, erlang:phash -%% (former versions use erlang:hash). -%% Version 8(b) files are only converted to version 8(c) if repair is -%% done, so we need compatibility with 8(b) for a _long_ time. -%% -%% There are known bugs due to the fact that keys and objects are -%% sometimes compared (==) and sometimes matched (=:=). The version -%% used by default (9, see dets_v9.erl) does not have this problem. - --define(NOT_PROPERLY_CLOSED,0). --define(CLOSED_PROPERLY,1). --define(CLOSED_PROPERLY2,2). --define(CLOSED_PROPERLY2_NEED_COMPACTING,3). --define(CLOSED_PROPERLY_NEW_HASH,4). --define(CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING,5). - --define(FILE_FORMAT_VERSION, 8). --define(CAN_BUMP_BY_REPAIR, [6, 7]). --define(CAN_CONVERT_FREELIST, [8]). - -%%% -%%% Object header (next, size, status). -%%% - --define(OHDSZ, 12).         % The size of the object header, in bytes. --define(STATUS_POS, 8).     % Position of the status field. - -%% The size of each object is a multiple of 16. -%% BUMP is used when repairing files. --define(BUMP, 16). - --define(ReadAhead, 512). - -%%-define(DEBUGF(X,Y), io:format(X, Y)). --define(DEBUGF(X,Y), void). - -%% -> ok | throw({NewHead,Error}) -mark_dirty(Head) -> -    Dirty = [{?CLOSED_PROPERLY_POS, <<?NOT_PROPERLY_CLOSED:32>>}], -    {_NewHead, ok} = dets_utils:pwrite(Head, Dirty), -    ok = dets_utils:sync(Head), -    {ok, _Pos} = dets_utils:position(Head, Head#head.freelists_p), -    ok = dets_utils:truncate(Head, cur). - -%% -> {ok, head()} | throw(Error) -initiate_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots,  -		Ram, CacheSz, Auto, _DoInitSegments) -> -    Freelist = 0, -    Cookie = ?MAGIC, -    ClosedProperly = ?NOT_PROPERLY_CLOSED, % immediately overwritten -    Version = ?FILE_FORMAT_VERSION, -    Factor = est_no_segments(MinSlots), -    N = 0, -    M = Next = ?SEGSZ * Factor, -    NoObjects = 0, -    dets_utils:pwrite(Fd, Fname, 0,  -                      <<Freelist:32, -                      Cookie:32, -                      ClosedProperly:32, -                      (dets_utils:type_to_code(Type)):32, -                      Version:32, -                      M:32, -                      Next:32, -                      Kp:32, -                      NoObjects:32, -                      N:32, -		      0:(?SEGARRSZ*4)/unit:8, % Initialize SegmentArray -		      0:(?SEGSZ*4)/unit:8>>), % Initialize first segment -    %% We must set the first slot of the segment pointer array to -    %% point to the first segment -    Pos = ?SEGADDR(0), -    SegP = (?HEADSZ + (4 * ?SEGARRSZ)), -    dets_utils:pwrite(Fd, Fname, Pos, <<SegP:32>>), -    segp_cache(Pos, SegP), - -    Ftab = dets_utils:init_alloc(?BASE), -    H0 = #head{freelists=Ftab, fptr = Fd, base = ?BASE}, -    {H1, Ws} = init_more_segments(H0, 1, Factor, undefined, []), - -    %% This is not optimal but simple: always initiate the segments. -    dets_utils:pwrite(Fd, Fname, Ws), - -    %% Return a new nice head structure -    Head = #head{ -      m  = M, -      m2 = M * 2, -      next = Next, -      fptr = Fd, -      no_objects = NoObjects, -      n = N, -      type = Type, -      update_mode = dirty, -      freelists = H1#head.freelists, -      auto_save = Auto, -      hash_bif = phash, -      keypos = Kp, -      min_no_slots = Factor * ?SEGSZ, -      max_no_slots = no_segs(MaxSlots) * ?SEGSZ, -       -      ram_file = Ram,  -      filename = Fname,  -      name = Tab, -      cache = dets_utils:new_cache(CacheSz), -      version = Version, -      bump = ?BUMP, -      base = ?BASE, -      mod = ?MODULE -     }, -    {ok, Head}. - -est_no_segments(MinSlots) when 1 + ?SLOT2SEG(MinSlots) > ?SEGARRSZ -> -    ?SEGARRSZ; -est_no_segments(MinSlots) -> -    1 + ?SLOT2SEG(MinSlots). - -init_more_segments(Head, SegNo, Factor, undefined, Ws) when SegNo < Factor -> -    init_more_segments(Head, SegNo, Factor, seg_zero(), Ws); -init_more_segments(Head, SegNo, Factor, SegZero, Ws) when SegNo < Factor -> -    {NewHead, W} = allocate_segment(Head, SegZero, SegNo), -    init_more_segments(NewHead, SegNo+1, Factor, SegZero, W++Ws); -init_more_segments(Head, _SegNo, _Factor, _SegZero, Ws) -> -    {Head, Ws}. - -allocate_segment(Head, SegZero, SegNo) -> -    %% may throw error: -    {NewHead, Segment, _} = dets_utils:alloc(Head, 4 * ?SEGSZ), -    InitSegment = {Segment, SegZero}, -    Pos = ?SEGADDR(SegNo), -    segp_cache(Pos, Segment), -    SegPointer = {Pos, <<Segment:32>>}, -    {NewHead, [InitSegment, SegPointer]}. - -%% Read free lists (using a Buddy System) from file.  -init_freelist(Head, {convert_freelist,_Version}) -> -    %% This function converts the saved freelist of the form -    %% [{Slot1,Addr1},{Addr1,Addr2},...,{AddrN,0},{Slot2,Addr},...] -    %% i.e each slot is a linked list which ends with a 0. -    %% This is stored in a bplus_tree per Slot. -    %% Each Slot is a position in a tuple. - -    Ftab = dets_utils:empty_free_lists(), -    Pos = Head#head.freelists_p, -    case catch prterm(Head, Pos, ?OHDSZ) of -	{0, _Sz, Term}  -> -	    FreeList1 = lists:reverse(Term), -            FreeList = dets_utils:init_slots_from_old_file(FreeList1, Ftab), -            Head#head{freelists = FreeList, base = ?BASE}; -	_ -> -	    throw({error, {bad_freelists, Head#head.filename}}) -    end; -init_freelist(Head, _) -> -    %% bplus_tree stored as is -    Pos = Head#head.freelists_p, -    case catch prterm(Head, Pos, ?OHDSZ) of -	{0, _Sz, Term}  -> -            Head#head{freelists = Term, base = ?BASE}; -	_ -> -	    throw({error, {bad_freelists, Head#head.filename}}) -    end. - -%% -> {ok, Fd, fileheader()} | throw(Error) -read_file_header(Fd, FileName) -> -    {ok, Bin} = dets_utils:pread_close(Fd, FileName, 0, ?HEADSZ), -    [Freelist, Cookie, CP, Type2, Version, M, Next, Kp, NoObjects, N] =  -	bin2ints(Bin), -    {ok, EOF} = dets_utils:position_close(Fd, FileName, eof), -    {ok, <<FileSize:32>>} = dets_utils:pread_close(Fd, FileName, EOF-4, 4), -    FH = #fileheader{freelist = Freelist, -                     fl_base = ?BASE, -		     cookie = Cookie, -		     closed_properly = CP, -		     type = dets_utils:code_to_type(Type2), -		     version = Version, -		     m = M, -		     next = Next, -		     keypos = Kp, -		     no_objects = NoObjects, -		     min_no_slots = ?DEFAULT_MIN_NO_SLOTS, -		     max_no_slots = ?DEFAULT_MAX_NO_SLOTS, -		     trailer = FileSize, -		     eof = EOF, -		     n = N, -		     mod = ?MODULE}, -    {ok, Fd, FH}. - -%% -> {ok, head(), ExtraInfo} | {error, Reason} (Reason lacking file name) -%% ExtraInfo = {convert_freelist, Version} | true | need_compacting  -check_file_header(FH, Fd) -> -    Test =  -	if -	    FH#fileheader.cookie =/= ?MAGIC -> -		{error, not_a_dets_file}; -	    FH#fileheader.type =:= badtype -> -		{error, invalid_type_code}; -	    FH#fileheader.version =/= ?FILE_FORMAT_VERSION ->  -		case lists:member(FH#fileheader.version, -                                  ?CAN_BUMP_BY_REPAIR) of -		    true -> -			{error, version_bump}; -		    false -> -			{error, bad_version} -		end; -	    FH#fileheader.trailer =/= FH#fileheader.eof -> -		{error, not_closed}; -	    FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY -> -		case lists:member(FH#fileheader.version, -				  ?CAN_CONVERT_FREELIST) of -		    true -> -			{ok, {convert_freelist, FH#fileheader.version}, hash}; -		    false -> -			{error, not_closed} % should not happen -		end; -	    FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY2 -> -		{ok, true, hash}; -	    FH#fileheader.closed_properly =:=  -	          ?CLOSED_PROPERLY2_NEED_COMPACTING  -> -		{ok, need_compacting, hash}; -	    FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY_NEW_HASH -> -		{ok, true, phash}; -	    FH#fileheader.closed_properly =:=  -	         ?CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING  -> -		{ok, need_compacting, phash}; -	    FH#fileheader.closed_properly =:= ?NOT_PROPERLY_CLOSED -> -		{error, not_closed}; -	    FH#fileheader.closed_properly >  -	         ?CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING -> -		{error, not_closed}; -	    true -> -		{error, not_a_dets_file} -	end, -    case Test of -	{ok, ExtraInfo, HashAlg} -> -	    H = #head{ -	      m = FH#fileheader.m, -	      m2 = FH#fileheader.m * 2, -	      next = FH#fileheader.next, -	      fptr = Fd, -	      no_objects= FH#fileheader.no_objects, -	      n = FH#fileheader.n, -	      type = FH#fileheader.type, -	      update_mode = saved, -	      auto_save = infinity,             % not saved on file -	      fixed = false,			% not saved on file -	      freelists_p = FH#fileheader.freelist, -	      hash_bif = HashAlg, -	      keypos = FH#fileheader.keypos, -	      min_no_slots = FH#fileheader.min_no_slots, -	      max_no_slots = FH#fileheader.max_no_slots, -	      version = ?FILE_FORMAT_VERSION, -	      mod = ?MODULE, -	      bump = ?BUMP, -	      base = FH#fileheader.fl_base}, -	    {ok, H, ExtraInfo}; -	Error -> -	    Error -    end. - -cache_segps(Fd, FileName, M) -> -    NSegs = no_segs(M), -    {ok, Bin} = dets_utils:pread_close(Fd, FileName, ?HEADSZ, 4 * NSegs), -    Fun = fun(S, P) -> segp_cache(P, S), P+4 end, -    lists:foldl(Fun, ?HEADSZ, bin2ints(Bin)). - -no_segs(NoSlots) -> -    ?SLOT2SEG(NoSlots - 1) + 1. - -bin2ints(<<Int:32, B/binary>>) -> -    [Int | bin2ints(B)]; -bin2ints(<<>>) -> -    []. - -%%% -%%% Repair, conversion and initialization of a dets file. -%%% - -bulk_input(Head, InitFun, Cntrs) -> -    bulk_input(Head, InitFun, Cntrs, make_ref()). - -bulk_input(Head, InitFun, Cntrs, Ref) -> -    fun(close) -> -	    ok; -       (read) -> -	    case catch {Ref, InitFun(read)} of -		{Ref, end_of_input} -> -		    end_of_input; -		{Ref, {L0, NewInitFun}} when is_list(L0),  -                                             is_function(NewInitFun) -> -		    Kp = Head#head.keypos, -		    case catch bulk_objects(L0, Head, Cntrs, Kp, []) of -			{'EXIT', _Error} -> -			    _ = (catch NewInitFun(close)), -			    {error, invalid_objects_list}; -			L -> -			    {L, bulk_input(Head, NewInitFun, Cntrs, Ref)} -		    end; -		{Ref, Value} -> -		    {error, {init_fun, Value}}; -		Error -> -		    throw({thrown, Error}) -	    end -    end. - -bulk_objects([T | Ts], Head, Cntrs, Kp, L) -> -    BT = term_to_binary(T), -    Sz = byte_size(BT), -    LogSz = sz2pos(Sz+?OHDSZ), -    count_object(Cntrs, LogSz), -    Key = element(Kp, T), -    bulk_objects(Ts, Head, Cntrs, Kp, [make_object(Head, Key, LogSz, BT) | L]); -bulk_objects([], _Head, _Cntrs, _Kp, L) -> -    L. - --define(FSCK_SEGMENT, 10000). - --define(DCT(D, CT), [D | CT]). - --define(VNEW(N, E), erlang:make_tuple(N, E)). --define(VSET(I, V, E), setelement(I, V, E)). --define(VGET(I, V), element(I, V)). - -%% OldVersion not used, assuming later versions have been converted already. -output_objs(OldVersion, Head, SlotNumbers, Cntrs) -> -    fun(close) -> -	    {ok, 0, Head}; -       ([]) -> -	    output_objs(OldVersion, Head, SlotNumbers, Cntrs); -       (L) -> -	    %% Descending sizes. -	    Count = lists:sort(ets:tab2list(Cntrs)), -	    RCount = lists:reverse(Count), -	    NoObjects = lists:foldl(fun({_Sz,No}, A) -> A + No end, 0, Count), -	    {_, MinSlots, _} = SlotNumbers, -	    if -		%% Using number of objects for bags and duplicate bags -		%% is not ideal; number of (unique) keys should be -		%% used instead. The effect is that there will be more -		%% segments than "necessary". -		MinSlots =/= bulk_init, -		abs(?SLOT2SEG(NoObjects) - ?SLOT2SEG(MinSlots)) > 5, -		(NoObjects < ?MAXOBJS) -> -		    {try_again, NoObjects}; -		true -> -                    Head1 = Head#head{no_objects = NoObjects}, -		    SegSz = actual_seg_size(), -		    {_, End, _} = dets_utils:alloc(Head, SegSz-1), -		    %% Now {LogSize,NoObjects} in Cntrs is replaced by -		    %% {LogSize,Position,{FileName,FileDescriptor},NoObjects}. -		    {Head2, CT} = allocate_all_objects(Head1, RCount, Cntrs), -		    [E | Es] = bin2term(L, []), -		    {NE, Acc, DCT1} =  -			output_slots(E, Es, [E], Head2, ?DCT(0, CT)), -		    NDCT = write_all_sizes(DCT1, Cntrs), -		    Max = ets:info(Cntrs, size), -                    output_objs2(NE, Acc, Head2, Cntrs, NDCT, End, Max,Max) -	    end -    end. - -output_objs2(E, Acc, Head, Cntrs, DCT, End, 0, MaxNoChunks) -> -    NDCT = write_all_sizes(DCT, Cntrs), -    output_objs2(E, Acc, Head, Cntrs, NDCT, End, MaxNoChunks, MaxNoChunks); -output_objs2(E, Acc, Head, Cntrs, DCT, End, ChunkI, MaxNoChunks) -> -    fun(close) -> -	    DCT1 = output_slot(Acc, Head, DCT), -	    NDCT = write_all_sizes(DCT1, Cntrs), -	    ?DCT(NoDups, CT) = NDCT, -	    [SegAddr | []] = ?VGET(tuple_size(CT), CT), -            FinalZ = End - SegAddr, -            [{?FSCK_SEGMENT, _, {FileName, Fd}, _}] =  -		ets:lookup(Cntrs, ?FSCK_SEGMENT), -	    ok = dets_utils:fwrite(Fd, FileName,  -				   dets_utils:make_zeros(FinalZ)), -            NewHead = Head#head{no_objects = Head#head.no_objects - NoDups}, -	    {ok, NoDups, NewHead}; -       (L) -> -	    Es = bin2term(L, []), -	    {NE, NAcc, NDCT} = output_slots(E, Es, Acc, Head, DCT), -	    output_objs2(NE, NAcc, Head, Cntrs, NDCT, End,  -			 ChunkI-1, MaxNoChunks) -    end. - -%% By allocating bigger objects before smaller ones, holes in the -%% buddy system memory map are avoided. Unfortunately, the segments -%% are always allocated first, so if there are objects bigger than a -%% segment, there is a hole to handle. (Haven't considered placing the -%% segments among other objects of the same size.) -allocate_all_objects(Head, Count, Cntrs) -> -    SegSize = actual_seg_size(), -    {Head1, HSz, HN, HA} = alloc_hole(Count, Head, SegSize), -    {Max, _} = hd(Count), -    CT = ?VNEW(Max+1, not_used), -    {Head2, NCT} = allocate_all(Head1, Count, Cntrs, CT), -    Head3 = free_hole(Head2, HSz, HN, HA), -    {Head3, NCT}. - -alloc_hole([{LSize,_} | _], Head, SegSz) when ?POW(LSize-1) > SegSz -> -    {_, SegAddr, _} = dets_utils:alloc(Head, SegSz-1), -    Size = ?POW(LSize-1)-1, -    {_, Addr, _} = dets_utils:alloc(Head, Size), -    N = (Addr - SegAddr) div SegSz, -    Head1 = dets_utils:alloc_many(Head, SegSz, N, SegAddr), -    {Head1, SegSz-1, N, SegAddr}; -alloc_hole(_Count, Head, _SegSz) -> -    {Head, 0, 0, 0}. - -free_hole(Head, _Size, 0, _Addr) -> -    Head; -free_hole(Head, Size, N, Addr) -> -    {Head1, _} = dets_utils:free(Head, Addr, Size), -    free_hole(Head1, Size, N-1, Addr+Size+1). - -%% One (temporary) file for each buddy size, write all objects of that -%% size to the file. -allocate_all(Head, [{LSize,NoObjects} | Count], Cntrs, CT) -> -    Size = ?POW(LSize-1)-1, -    {_Head, Addr, _} = dets_utils:alloc(Head, Size), -    NewHead = dets_utils:alloc_many(Head, Size+1, NoObjects, Addr), -    {FileName, Fd} = temp_file(Head, LSize), -    true = ets:insert(Cntrs, {LSize, Addr, {FileName, Fd}, NoObjects}), -    NCT = ?VSET(LSize, CT, [Addr | []]), -    allocate_all(NewHead, Count, Cntrs, NCT); -allocate_all(Head, [], Cntrs, CT) -> -    %% Note that space for the segments has been allocated already. -    %% And one file for the segments... -    {FileName, Fd} = temp_file(Head, ?FSCK_SEGMENT), -    Addr = ?SEGADDR(?SEGARRSZ), -    true = ets:insert(Cntrs, {?FSCK_SEGMENT, Addr, {FileName, Fd}, 0}), -    NCT = ?VSET(tuple_size(CT), CT, [Addr | []]), -    {Head, NCT}. - -temp_file(Head, N) -> -    TmpName = lists:concat([Head#head.filename, '.', N]), -    {ok, Fd} = dets_utils:open(TmpName, [raw, binary, write]), -    {TmpName, Fd}. - -bin2term([<<Slot:32, LogSize:8, BinTerm/binary>> | BTs], L) -> -    bin2term(BTs, [{Slot, LogSize, BinTerm} | L]); -bin2term([], L) -> -    lists:reverse(L). - -write_all_sizes(?DCT(D, CT), Cntrs) -> -    ?DCT(D, write_sizes(1, tuple_size(CT), CT, Cntrs)). - -write_sizes(Sz, Sz, CT, Cntrs) -> -    write_size(Sz, ?FSCK_SEGMENT, CT, Cntrs); -write_sizes(Sz, MaxSz, CT, Cntrs) -> -    NCT = write_size(Sz, Sz, CT, Cntrs), -    write_sizes(Sz+1, MaxSz, NCT, Cntrs). - -write_size(Sz, I, CT, Cntrs) -> -    case ?VGET(Sz, CT) of -	not_used -> -	    CT; -	[Addr | L] -> -	    {FileName, Fd} = ets:lookup_element(Cntrs, I, 3), -	    case file:write(Fd, lists:reverse(L)) of -		ok -> -		    ?VSET(Sz, CT, [Addr | []]); -		Error -> -		    dets_utils:file_error(FileName, Error) -	    end -    end. - -output_slots(E, [E1 | Es], Acc, Head, DCT)  -                       when element(1, E) =:= element(1, E1) -> -    output_slots(E1, Es, [E1 | Acc], Head, DCT); -output_slots(_E, [E | L], Acc, Head, DCT) -> -    NDCT = output_slot(Acc, Head, DCT), -    output_slots(E, L, [E], Head, NDCT); -output_slots(E, [], Acc, _Head, DCT) -> -    {E, Acc, DCT}. - -output_slot([E], _Head, ?DCT(D, CT)) -> -    ?DCT(D, output_slot([{foo, E}], 0, foo, CT)); -output_slot(Es0, Head, ?DCT(D, CT)) -> -    Kp = Head#head.keypos, -    Fun = fun({_Slot, _LSize, BinTerm} = E) ->  -		  Key = element(Kp, binary_to_term(BinTerm)), -		  {Key, E} -	  end, -    Es = lists:map(Fun, Es0), -    NEs = case Head#head.type of -	      set -> -		  [{Key0,_} = E | L0] = lists:sort(Es), -		  choose_one(lists:sort(L0), Key0, [E]); -	      bag ->  -		  lists:usort(Es); -	      duplicate_bag ->  -		  lists:sort(Es) -	  end, -    Dups = D + length(Es) - length(NEs), -    ?DCT(Dups, output_slot(NEs, 0, foo, CT)). - -choose_one([{Key,_} | Es], Key, L) -> -    choose_one(Es, Key, L); -choose_one([{Key,_} = E | Es], _Key, L) -> -    choose_one(Es, Key, [E | L]); -choose_one([], _Key, L) -> -    L. - -output_slot([E | Es], Next, _Slot, CT) -> -    {_Key, {Slot, LSize, BinTerm}} = E, -    Size = byte_size(BinTerm), -    Size2 = ?POW(LSize-1), -    Pad = <<0:(Size2-Size-?OHDSZ)/unit:8>>, -    BinObject = [<<Next:32, Size:32, ?ACTIVE:32>>, BinTerm | Pad], -    [Addr | L] = ?VGET(LSize, CT), -    NCT = ?VSET(LSize, CT, [Addr+Size2 | [BinObject | L]]), -    output_slot(Es, Addr, Slot, NCT); -output_slot([], Next, Slot, CT) -> -    I = tuple_size(CT), -    [Addr | L] = ?VGET(I, CT), -    {Pos, _} = slot_position(Slot), -    NoZeros = Pos - Addr, -    BinObject = if  -		    NoZeros > 100 -> -			[dets_utils:make_zeros(NoZeros) | <<Next:32>>]; -		    true -> -			<<0:NoZeros/unit:8,Next:32>> -   	        end, -    Size = NoZeros+4, -    ?VSET(I, CT, [Addr+Size | [BinObject | L]]). - -%% Does not close Fd. -fsck_input(Head, Fd, Cntrs, _FileHeader) -> -    %% The file is not compressed, so the object size cannot exceed -    %% the filesize, for all objects. -    MaxSz = case file:position(Fd, eof) of -                {ok, Pos} -> -                    Pos; -                _ -> -                    (1 bsl 32) - 1 -            end, -    State0 = fsck_read(?BASE, Fd, []), -    fsck_input1(Head, State0, Fd, MaxSz, Cntrs). - -fsck_input1(Head, State, Fd, MaxSz, Cntrs) -> -    fun(close) -> -	    ok; -       (read) -> -	    case State of -		done -> -		    end_of_input; -		{done, L} -> -		    R = count_input(Cntrs, L, []), -		    {R, fsck_input1(Head, done, Fd, MaxSz, Cntrs)}; -		{cont, L, Bin, Pos} -> -		    R = count_input(Cntrs, L, []), -                    FR = fsck_objs(Bin, Head#head.keypos, Head, []), -		    NewState = fsck_read(FR, Pos, Fd, MaxSz, Head), -		    {R, fsck_input1(Head, NewState, Fd, MaxSz, Cntrs)} -	    end -    end. - -%% The ets table Cntrs is used for counting objects per size. -count_input(Cntrs, [[LogSz | B] | Ts], L) -> -    count_object(Cntrs, LogSz), -    count_input(Cntrs, Ts, [B | L]); -count_input(_Cntrs, [], L) -> -    L. - -count_object(Cntrs, LogSz) -> -    case catch ets:update_counter(Cntrs, LogSz, 1) of -	N when is_integer(N) -> ok; -	_Badarg -> true = ets:insert(Cntrs, {LogSz, 1}) -    end. - -fsck_read(Pos, F, L) -> -    case file:position(F, Pos) of -	{ok, _} -> -	    read_more_bytes(<<>>, 0, Pos, F, L); -	_Error -> -	    {done, L} -    end. - -fsck_read({more, Bin, Sz, L}, Pos, F, MaxSz, Head) when Sz > MaxSz -> -    FR = skip_bytes(Bin, ?BUMP, Head#head.keypos, Head, L), -    fsck_read(FR, Pos, F, MaxSz, Head); -fsck_read({more, Bin, Sz, L}, Pos, F, _MaxSz, _Head) -> -    read_more_bytes(Bin, Sz, Pos, F, L); -fsck_read({new, Skip, L}, Pos, F, _MaxSz, _Head) -> -    NewPos = Pos + Skip, -    fsck_read(NewPos, F, L). - -read_more_bytes(B, Min, Pos, F, L) -> -    Max = if  -	      Min < ?CHUNK_SIZE -> ?CHUNK_SIZE;  -	      true -> Min  -	  end, -    case dets_utils:read_n(F, Max) of -	eof -> -	    {done, L}; -	Bin -> -	    NewPos = Pos + byte_size(Bin), -	    {cont, L, list_to_binary([B, Bin]), NewPos} -    end. - -fsck_objs(Bin = <<_N:32, Sz:32, Status:32, Tail/binary>>, Kp, Head, L) -> -    if  -	Status =:= ?ACTIVE -> -	    case Tail of -		<<BinTerm:Sz/binary, Tail2/binary>> -> -		    case catch element(Kp, binary_to_term(BinTerm)) of -			{'EXIT', _} -> -			    skip_bytes(Bin, ?BUMP, Kp, Head, L); -			Key -> -			    LogSz = sz2pos(Sz+?OHDSZ), -			    Obj = make_object(Head, Key, LogSz, BinTerm), -			    NL = [[LogSz | Obj] | L], -			    Skip = ?POW(LogSz-1) - Sz - ?OHDSZ, -			    skip_bytes(Tail2, Skip, Kp, Head, NL) -		    end; -		_ -> -                    {more, Bin, Sz, L} -	    end; -	true ->  -	    skip_bytes(Bin, ?BUMP, Kp, Head, L) -    end; -fsck_objs(Bin, _Kp, _Head, L) -> -    {more, Bin, 0, L}. -     -%% Version 8 has to know about version 9. -make_object(Head, Key, _LogSz, BT) when Head#head.version =:= 9 -> -    Slot = dets_v9:db_hash(Key, Head), -    <<Slot:32, BT/binary>>; -make_object(Head, Key, LogSz, BT) -> -    Slot = db_hash(Key, Head), -    <<Slot:32, LogSz:8, BT/binary>>. - -%% Inlined. -skip_bytes(Bin, Skip, Kp, Head, L) -> -    case Bin of -	<<_:Skip/binary, Tail/binary>> -> -	    fsck_objs(Tail, Kp, Head, L); -	_ -> -            {new, Skip - byte_size(Bin), L} -    end. - -%% -> {NewHead, ok} | throw({Head, Error}) -do_perform_save(H) -> -    FL = dets_utils:get_freelists(H), -    B = term_to_binary(FL), -    Size = byte_size(B), -    ?DEBUGF("size of freelist = ~p~n", [Size]), -    ?DEBUGF("head.m = ~p~n", [H#head.m]), -    ?DEBUGF("head.no_objects = ~p~n", [H#head.no_objects]), - -    {ok, Pos} = dets_utils:position(H, eof), -    H1 = H#head{freelists_p = Pos}, -    W1 = {?FREELIST_POS, <<Pos:32>>}, -    W2 = {Pos, [<<0:32, Size:32, ?FREE:32>>, B]}, -     -    W3 = {?D_POS, <<(H1#head.m):32,  -	            (H1#head.next):32,  -	            (H1#head.keypos):32, -	            (H1#head.no_objects):32, -		    (H1#head.n):32>>}, -    {ClosedProperly, ClosedProperlyNeedCompacitng} =  -	case H1#head.hash_bif of -	    hash -> -		{?CLOSED_PROPERLY2, ?CLOSED_PROPERLY2_NEED_COMPACTING}; -	    phash -> -		{?CLOSED_PROPERLY_NEW_HASH,  -		 ?CLOSED_PROPERLY_NEW_HASH_NEED_COMPACTING} -	end, -    W4 =  -	if  -	    Size > 1000, Size > H1#head.no_objects -> -		{?CLOSED_PROPERLY_POS,  -		 <<ClosedProperlyNeedCompacitng:32>>}; -	    true -> -		{?CLOSED_PROPERLY_POS, <<ClosedProperly:32>>} -	end, -    W5 = {?FILE_FORMAT_VERSION_POS, <<?FILE_FORMAT_VERSION:32>>}, -    {H2, ok} = dets_utils:pwrite(H1, [W1,W2,W3,W4,W5]), -    {ok, Pos2} = dets_utils:position(H2, eof), -    ?DEBUGF("Writing file size ~p, eof at ~p~n", [Pos2+4, Pos2]), -    dets_utils:pwrite(H2, [{Pos2, <<(Pos2 + 4):32>>}]). - -%% -> [term()] | throw({Head, Error}) -slot_objs(H, Slot) when Slot >= H#head.next -> -    '$end_of_table'; -slot_objs(H, Slot) -> -    {_Pos, Chain} = chain(H, Slot), -    collect_chain(H, Chain). - -collect_chain(_H, 0) -> []; -collect_chain(H, Pos) -> -    {Next, _Sz, Term} = prterm(H, Pos, ?ReadAhead), -    [Term | collect_chain(H, Next)]. - -db_hash(Key, Head) -> -    H = h(Key, Head#head.hash_bif), -    Hash = H rem Head#head.m, -    if -	Hash < Head#head.n -> -	    H rem (Head#head.m2); % H rem (2 * m) -	true -> -	    Hash -    end. - -h(I, phash) -> erlang:phash(I, ?BIG) - 1; -h(I, HF) -> erlang:HF(I, ?BIG) - 1. %% stupid BIF has 1 counts. - -no_slots(_Head) -> -    undefined. - -table_parameters(_Head) -> -    undefined. - -%% Re-hashing a segment, starting with SlotStart. -%% -%% On the average, half of the objects of the chain are put into a new -%% chain. If the slot of the old chain is i, then the slot of the new -%% chain is i+m. -%% Note that the insertion of objects into the new chain is simplified -%% by the fact that the chains are not sorted on key, which means that -%% each moved object can be inserted first in the new chain. -%% (It is also a fact that the objects with the same key are not sorted.) -%% -%% -> {ok, Writes} | throw({Head, Error}) -re_hash(Head, SlotStart) -> -    {SlotPos, _4} = slot_position(SlotStart), -    {ok, Bin} = dets_utils:pread(Head, SlotPos, 4*?SEGSZ, 0), -    {Read, Cs} = split_bin(SlotPos, Bin, [], []), -    re_hash_read(Head, [], Read, Cs). - -split_bin(Pos, <<P:32, B/binary>>, R, Cs) -> -    if -	P =:= 0 -> -	    split_bin(Pos+4, B, R, Cs); -	true -> -	    split_bin(Pos+4, B, [{P,?ReadAhead} | R], [[Pos] | Cs]) -    end; -split_bin(_Pos, <<>>, R, Cs) -> -    {R, Cs}. - -re_hash_read(Head, Cs, R, RCs) -> -    {ok, Bins} = dets_utils:pread(R, Head), -    re_hash_read(Head, R, RCs, Bins, Cs, [], []). - -re_hash_read(Head, [{Pos, Size} | Ps], [C | Cs],  -	     [<<Next:32, Sz:32, _Status:32, Bin0/binary>> | Bins],  -	     DoneCs, R, RCs) -> -    case byte_size(Bin0) of -	BinSz when BinSz >= Sz -> -	    case catch binary_to_term(Bin0) of -		{'EXIT', _Error} -> -		    throw(dets_utils:corrupt_reason(Head, bad_object)); -		Term -> -		    Key = element(Head#head.keypos, Term), -		    New = h(Key, Head#head.hash_bif) rem Head#head.m2, -		    NC = case New >= Head#head.m of -			     true -> [{Pos,New} | C]; -			     false -> [Pos | C] -			 end, -		    if -			Next =:= 0 -> -			    NDoneCs = [NC | DoneCs],  -			    re_hash_read(Head, Ps, Cs, Bins, NDoneCs, R, RCs); -			true -> -			    NR = [{Next,?ReadAhead} | R], -			    NRCs = [NC | RCs], -			    re_hash_read(Head, Ps, Cs, Bins, DoneCs, NR, NRCs) -		    end -	    end; -	BinSz when Size =:= BinSz+?OHDSZ -> -	    NR = [{Pos, Sz+?OHDSZ} | R], -	    re_hash_read(Head, Ps, Cs, Bins, DoneCs, NR, [C | RCs]); -	_BinSz -> -	    throw({Head, {error, {premature_eof, Head#head.filename}}}) -    end; -re_hash_read(Head, [], [], [], Cs, [], []) -> -    re_hash_traverse_chains(Cs, Head, [], [], []); -re_hash_read(Head, [], [], [], Cs, R, RCs) -> -    re_hash_read(Head, Cs, R, RCs). - -re_hash_traverse_chains([C | Cs], Head, Rs, Ns, Ws) -> -    case re_hash_find_new(C, Rs, start, start) of -	false -> -	    re_hash_traverse_chains(Cs, Head, Rs, Ns, Ws); -	{NRs, FirstNew, LastNew} ->  -	    LastInNew = case C of -			    [{_,_} | _] -> true; -			    _ -> false -			end, -	    N = {FirstNew, LastNew, LastInNew}, -	    NWs = re_hash_link(C, start, start, start, Ws), -	    re_hash_traverse_chains(Cs, Head, NRs, [N | Ns], NWs) -    end; -re_hash_traverse_chains([], Head, Rs, Ns, Ws) -> -    {ok, Bins} = dets_utils:pread(Rs, Head), -    {ok, insert_new(Rs, Bins, Ns, Ws)}. - -re_hash_find_new([{Pos,NewSlot} | C], R, start, start) -> -    {SPos, _4} = slot_position(NewSlot), -    re_hash_find_new(C, [{SPos,4} | R], Pos, Pos); -re_hash_find_new([{Pos,_SPos} | C], R, _FirstNew, LastNew) -> -    re_hash_find_new(C, R, Pos, LastNew); -re_hash_find_new([_Pos | C], R, FirstNew, LastNew) -> -    re_hash_find_new(C, R, FirstNew, LastNew); -re_hash_find_new([], _R, start, start) -> -    false; -re_hash_find_new([], R, FirstNew, LastNew) -> -    {R, FirstNew, LastNew}. - -re_hash_link([{Pos,_SPos} | C], LastOld, start, _LastInNew, Ws) -> -    re_hash_link(C, LastOld, Pos, true, Ws); -re_hash_link([{Pos,_SPos} | C], LastOld, LastNew, false, Ws) -> -    re_hash_link(C, LastOld, Pos, true, [{Pos,<<LastNew:32>>} | Ws]); -re_hash_link([{Pos,_SPos} | C], LastOld, _LastNew, LastInNew, Ws) -> -    re_hash_link(C, LastOld, Pos, LastInNew, Ws); -re_hash_link([Pos | C], start, LastNew, true, Ws) -> -    re_hash_link(C, Pos, LastNew, false, [{Pos,<<0:32>>} | Ws]); -re_hash_link([Pos | C], LastOld, LastNew, true, Ws) -> -    re_hash_link(C, Pos, LastNew, false, [{Pos,<<LastOld:32>>} | Ws]); -re_hash_link([Pos | C], _LastOld, LastNew, LastInNew, Ws) -> -    re_hash_link(C, Pos, LastNew, LastInNew, Ws); -re_hash_link([], _LastOld, _LastNew, _LastInNew, Ws) -> -    Ws. - -insert_new([{NewSlotPos,_4} | Rs], [<<P:32>> = PB | Bins], [N | Ns], Ws) -> -    {FirstNew, LastNew, LastInNew} = N, -    Ws1 = case P of -	      0 when LastInNew -> -		  Ws; -	      0 -> -		  [{LastNew, <<0:32>>} | Ws]; -	      _ -> -		  [{LastNew, PB} | Ws] -	  end, -    NWs = [{NewSlotPos, <<FirstNew:32>>} | Ws1], -    insert_new(Rs, Bins, Ns, NWs); -insert_new([], [], [], Ws) -> -    Ws. - -%% When writing the cache, a 'work list' is first created: -%%   WorkList = [{Key, {Delete,Lookup,[Inserted]}}] -%%   Delete = keep | delete -%%   Lookup = skip | lookup -%%   Inserted = {object(), No} -%%   No = integer() -%% If No =< 0 then there will be -No instances of object() on the file -%% when the cache has been written. If No > 0 then No instances of -%% object() will be added to the file. -%% If Delete has the value 'delete', then all objects with the key Key -%% have been deleted. (This could be viewed as a shorthand for {Object,0} -%% for each object Object on the file not mentioned in some Inserted.) -%% If Lookup has the value 'lookup', all objects with the key Key will -%% be returned. -%% - -%% -> {NewHead, [LookedUpObject], pwrite_list()} | throw({NewHead, Error}) -write_cache(Head) -> -    #head{cache = C, type = Type} = Head, -    case dets_utils:is_empty_cache(C) of -	true -> {Head, [], []}; -	false -> -	    {NewC, _MaxInserts, PerKey} = dets_utils:reset_cache(C), -	    %% NoInsertedKeys is an upper limit on the number of new keys. -	    {WL, NoInsertedKeys} = make_wl(PerKey, Type), -	    Head1 = Head#head{cache = NewC}, -	    case may_grow(Head1, NoInsertedKeys, once) of -		{Head2, ok} -> -		    eval_work_list(Head2, WL); -		HeadError -> -		    throw(HeadError) -	    end -    end. - -make_wl(PerKey, Type) -> -    make_wl(PerKey, Type, [], 0). - -make_wl([{Key,L} | PerKey], Type, WL, Ins) -> -    [Cs | I] = wl(L, Type), -    make_wl(PerKey, Type, [{Key,Cs} | WL], Ins+I); -make_wl([], _Type, WL, Ins) -> -    {WL, Ins}. - -wl(L, Type) -> -    wl(L, Type, keep, skip, 0, []). - -wl([{_Seq, delete_key} | Cs], Type, _Del, Lookup, _I, _Objs) -> -    wl(Cs, Type, delete, Lookup, 0, []); -wl([{_Seq, {delete_object, Object}} | Cs], Type, Del, Lookup, I, Objs) -> -    NObjs = lists:keydelete(Object, 1, Objs), -    wl(Cs, Type, Del, Lookup, I, [{Object,0} | NObjs]); -wl([{_Seq, {insert, Object}} | Cs], Type, _Del, Lookup, _I, _Objs)  -                    when Type =:= set -> -    wl(Cs, Type, delete, Lookup, 1, [{Object,-1}]); -wl([{_Seq, {insert, Object}} | Cs], Type, Del, Lookup, _I, Objs) -> -    NObjs =  -	case lists:keyfind(Object, 1, Objs) of -	    {_, 0} -> -		lists:keyreplace(Object, 1, Objs, {Object,-1}); -	    {_, _C} when Type =:= bag -> % C =:= 1; C =:= -1 -		Objs; -	    {_, C} when C < 0 -> % when Type =:= duplicate_bag -		lists:keyreplace(Object, 1, Objs, {Object,C-1}); -	    {_, C} -> % when C > 0, Type =:= duplicate_bag -		lists:keyreplace(Object, 1, Objs, {Object,C+1}); -	    false when Del =:= delete -> -		[{Object, -1} | Objs]; -	    false -> -		[{Object, 1} | Objs] -	end, -    wl(Cs, Type, Del, Lookup, 1, NObjs); -wl([{_Seq, {lookup,_Pid}=Lookup} | Cs], Type, Del, _Lookup, I, Objs) -> -    wl(Cs, Type, Del, Lookup, I, Objs); -wl([], _Type, Del, Lookup, I, Objs) -> -    [{Del, Lookup, Objs} | I]. - -%% -> {NewHead, ok} | {NewHead, Error} -may_grow(Head, 0, once) -> -    {Head, ok}; -may_grow(Head, _N, _How) when Head#head.fixed =/= false -> -    {Head, ok}; -may_grow(#head{access = read}=Head, _N, _How) -> -    {Head, ok}; -may_grow(Head, _N, _How) when Head#head.next >= ?MAXOBJS -> -    {Head, ok}; -may_grow(Head, N, How) -> -    Extra = erlang:min(2*?SEGSZ, Head#head.no_objects + N - Head#head.next), -    case catch may_grow1(Head, Extra, How) of -	{error, Reason} -> % alloc may throw error -	    {Head, {error, Reason}}; -	Reply -> -	    Reply -    end. - -may_grow1(Head, Extra, many_times) when Extra > ?SEGSZ -> -    Reply = grow(Head, 1, undefined), -    self() ! ?DETS_CALL(self(), may_grow), -    Reply; -may_grow1(Head, Extra, _How) ->     -    grow(Head, Extra, undefined). - -%% -> {Head, ok} | throw({Head, Error}) -grow(Head, Extra, _SegZero) when Extra =< 0 -> -    {Head, ok}; -grow(Head, Extra, undefined) -> -    grow(Head, Extra, seg_zero()); -grow(Head, Extra, SegZero) -> -    #head{n = N, next = Next, m = M} = Head, -    SegNum = ?SLOT2SEG(Next), -    {Head0, Ws1} = allocate_segment(Head, SegZero, SegNum), -    {Head1, ok} = dets_utils:pwrite(Head0, Ws1), -    %% If re_hash fails, segp_cache has been called, but it does not matter. -    {ok, Ws2} = re_hash(Head1, N), -    {Head2, ok} = dets_utils:pwrite(Head1, Ws2), -    NewHead = -	if  -	    N + ?SEGSZ =:= M -> -		Head2#head{n = 0, next = Next + ?SEGSZ, m = 2 * M, m2 = 4 * M}; -	    true -> -		Head2#head{n = N + ?SEGSZ, next = Next + ?SEGSZ} -	end, -    grow(NewHead, Extra - ?SEGSZ, SegZero). - -seg_zero() -> -    <<0:(4*?SEGSZ)/unit:8>>. - -find_object(Head, Object) -> -    Key = element(Head#head.keypos, Object), -    Slot = db_hash(Key, Head), -    find_object(Head, Object, Slot).     - -find_object(H, _Obj, Slot) when Slot >= H#head.next -> -    false; -find_object(H, Obj, Slot) -> -    {_Pos, Chain} = chain(H, Slot), -    case catch find_obj(H, Obj, Chain) of -	{ok, Pos} -> -	    {ok, Pos}; -	_Else -> -	    false -    end. - -find_obj(H, Obj, Pos) when Pos > 0 -> -    {Next, _Sz, Term} = prterm(H, Pos, ?ReadAhead), -    if  -	Term == Obj -> -	    {ok, Pos}; -	true -> -	    find_obj(H, Obj, Next) -    end. - -%% Given, a slot, return the {Pos, Chain} in the file where the -%% objects hashed to this slot reside. Pos is the position in the -%% file where the chain pointer is written and Chain is the position -%% in the file where the first object resides. -chain(Head, Slot) -> -    Pos = ?SEGADDR(?SLOT2SEG(Slot)), -    Segment = get_segp(Pos), -    FinalPos = Segment + (4 * ?REM2(Slot, ?SEGSZ)), -    {ok, <<Chain:32>>} = dets_utils:pread(Head, FinalPos, 4, 0), -    {FinalPos, Chain}. - -%%% -%%% Cache routines depending on the dets file format. -%%% - -%% -> {Head, [LookedUpObject], pwrite_list()} | throw({Head, Error}) -eval_work_list(Head, WorkLists) -> -    SWLs = tag_with_slot(WorkLists, Head, []), -    P1 = dets_utils:family(SWLs),  -    {PerSlot, SlotPositions} = remove_slot_tag(P1, [], []), -    {ok, Bins} = dets_utils:pread(SlotPositions, Head), -    first_object(PerSlot, SlotPositions, Bins, Head, [], [], [], []). - -tag_with_slot([{K,_} = WL | WLs], Head, L) -> -    tag_with_slot(WLs, Head, [{db_hash(K, Head), WL} | L]); -tag_with_slot([], _Head, L) -> -    L. - -remove_slot_tag([{S,SWLs} | SSWLs], Ls, SPs) -> -    remove_slot_tag(SSWLs, [SWLs | Ls], [slot_position(S) | SPs]); -remove_slot_tag([], Ls, SPs) -> -    {Ls, SPs}. - -%% The initial chain pointers and the first object in each chain are -%% read "in parallel", that is, with one call to file:pread/2 (two -%% calls altogether). The following chain objects are read one by -%% one. This is a compromise: if the chains are long and threads are -%% active, it would be faster to keep a state for each chain and read -%% the objects of the chains in parallel, but the overhead would be -%% quite substantial. - -first_object([WorkLists | SPs], [{P1,_4} | Ss], [<<P2:32>> | Bs], Head, -	      ObjsToRead, ToRead, Ls, LU) when P2 =:= 0 -> -    L0 = [{old,P1}], -    {L, NLU} = eval_slot(Head, ?ReadAhead, P2, WorkLists, L0, LU), -    first_object(SPs, Ss, Bs, Head, ObjsToRead, ToRead, [L | Ls], NLU); -first_object([WorkLists | SPs], [{P1,_4} | Ss], [<<P2:32>> | Bs], Head,  -	      ObjsToRead, ToRead, Ls, LU) -> -    E = {P1,P2,WorkLists}, -    first_object(SPs, Ss, Bs, Head,  -		 [E | ObjsToRead], [{P2, ?ReadAhead} | ToRead], Ls, LU); -first_object([], [], [], Head, ObjsToRead, ToRead, Ls, LU) -> -    {ok, Bins} = dets_utils:pread(ToRead, Head), -    case catch eval_first(Bins, ObjsToRead, Head, Ls, LU) of -	{ok, NLs, NLU} ->  -	    case create_writes(NLs, Head, [], 0) of -		{Head1, [], 0} -> -		    {Head1, NLU, []}; -		{Head1, Ws, No} -> -		    {NewHead, Ws2} = update_no_objects(Head1, Ws, No), -		    {NewHead, NLU, Ws2} -	    end; -	_Error ->  -	    throw(dets_utils:corrupt_reason(Head, bad_object)) -    end. - -%% Update no_objects on the file too, if the number of segments that -%% dets:fsck/6 use for estimate has changed. -update_no_objects(Head, Ws, 0) -> {Head, Ws}; -update_no_objects(Head, Ws, Delta) -> -    No = Head#head.no_objects, -    NewNo = No + Delta, -    NWs =  -	if  -	    NewNo > ?MAXOBJS -> -		Ws; -	    ?SLOT2SEG(No) =:= ?SLOT2SEG(NewNo) -> -		Ws; -	    true -> -		[{?NO_OBJECTS_POS, <<NewNo:32>>} | Ws] -	end, -    {Head#head{no_objects = NewNo}, NWs}. - -eval_first([<<Next:32, Sz:32, _Status:32, Bin/binary>> | Bins],  -	   [SP | SPs], Head, Ls, LU) -> -    {P1, P2, WLs} = SP, -    L0 = [{old,P1}], -    case byte_size(Bin) of -	BinSz when BinSz >= Sz -> -	    Term = binary_to_term(Bin), -	    Key = element(Head#head.keypos, Term), -	    {L, NLU} = find_key(Head, P2, Next, Sz, Term, Key, WLs, L0, LU), -	    eval_first(Bins, SPs, Head, [L | Ls], NLU); -	_BinSz -> -	    {L, NLU} = eval_slot(Head, Sz+?OHDSZ, P2, WLs, L0, LU), -	    eval_first(Bins, SPs, Head, [L | Ls], NLU) -    end; -eval_first([], [], _Head, Ls, LU) -> -    {ok, Ls, LU}. - -eval_slot(_Head, _TrySize, _Pos=0, [], L, LU) -> -    {L, LU}; -eval_slot(Head, _TrySize, Pos=0, [WL | WLs], L, LU) -> -    {_Key, {_Delete, LookUp, Objects}} = WL, -    {NL, NLU} = end_of_key(Objects, LookUp, L, []), -    eval_slot(Head, ?ReadAhead, Pos, WLs, NL, NLU++LU); -eval_slot(Head, TrySize, Pos, WLs, L, LU) -> -    {NextPos, Size, Term} = prterm(Head, Pos, TrySize), -    Key = element(Head#head.keypos, Term), -    find_key(Head, Pos, NextPos, Size, Term, Key, WLs, L, LU). - -find_key(Head, Pos, NextPos, Size, Term, Key, WLs, L, LU) -> -    case lists:keyfind(Key, 1, WLs) of -	{_, {Delete, LookUp, Objects}} = WL -> -	    NWLs = lists:delete(WL, WLs), -	    {NewObjects, NL, LUK} = eval_object(Size, Term, Delete, LookUp,  -						Objects, Head, Pos, L, []), -	    eval_key(Key, Delete, LookUp, NewObjects, Head, NextPos,  -		     NWLs, NL, LU, LUK); -	false -> -	    L0 = [{old,Pos} | L], -	    eval_slot(Head, ?ReadAhead, NextPos, WLs, L0, LU) -    end. - -eval_key(_Key, _Delete, Lookup, _Objects, Head, Pos, WLs, L, LU, LUK)  -                            when Head#head.type =:= set -> -    NLU = case Lookup of -	      {lookup, Pid} -> [{Pid,LUK} | LU]; -	      skip -> LU -	  end, -    eval_slot(Head, ?ReadAhead, Pos, WLs, L, NLU); -eval_key(_Key, _Delete, LookUp, Objects, Head, Pos, WLs, L, LU, LUK)  -                                                          when Pos =:= 0 -> -    {NL, NLU} = end_of_key(Objects, LookUp, L, LUK), -    eval_slot(Head, ?ReadAhead, Pos, WLs, NL, NLU++LU); -eval_key(Key, Delete, LookUp, Objects, Head, Pos, WLs, L, LU, LUK) -> -    {NextPos, Size, Term} = prterm(Head, Pos, ?ReadAhead), -    case element(Head#head.keypos, Term) of -	Key -> -	    {NewObjects, NL, LUK1} =  -		eval_object(Size, Term, Delete, LookUp,Objects,Head,Pos,L,LUK), -	    eval_key(Key, Delete, LookUp, NewObjects, Head, NextPos, WLs,  -		     NL, LU, LUK1); -	Key2 -> -	    {L1, NLU} = end_of_key(Objects, LookUp, L, LUK), -	    find_key(Head, Pos, NextPos, Size, Term, Key2, WLs, L1, NLU++LU) -    end. - -%% All objects in Objects have the key Key. -eval_object(Size, Term, Delete, LookUp, Objects, Head, Pos, L, LU) -> -    Type = Head#head.type, -    case lists:keyfind(Term, 1, Objects) of -	{_Object, N} when N =:= 0 -> -	    L1 = [{delete,Pos,Size} | L], -	    {Objects, L1, LU}; -	{_Object, N} when N < 0, Type =:= set -> -	    L1 = [{old,Pos} | L], -	    wl_lookup(LookUp, Objects, Term, L1, LU); -	{Object, _N} when Type =:= bag -> % when N =:= 1; N =:= -1 -	    L1 = [{old,Pos} | L], -	    Objects1 = lists:keydelete(Object, 1, Objects), -	    wl_lookup(LookUp, Objects1, Term, L1, LU); -	{Object, N} when N < 0, Type =:= duplicate_bag -> -	    L1 = [{old,Pos} | L], -	    Objects1 = lists:keyreplace(Object, 1, Objects, {Object,N+1}), -	    wl_lookup(LookUp, Objects1, Term, L1, LU); -	{_Object, N} when N > 0, Type =:= duplicate_bag -> -	    L1 = [{old,Pos} | L], -	    wl_lookup(LookUp, Objects, Term, L1, LU); -	false when Type =:= set, Delete =:= delete -> -	    case lists:keyfind(-1, 2, Objects) of -		false -> % no inserted object, perhaps deleted objects -		    L1 = [{delete,Pos,Size} | L], -		    {[], L1, LU}; -		{Term2, -1} -> -		    Bin2 = term_to_binary(Term2), -		    NSize = byte_size(Bin2), -		    Overwrite =  -			if -			    NSize =:= Size -> -				true; -			    true -> -				SizePos = sz2pos(Size+?OHDSZ), -				NSizePos = sz2pos(NSize+?OHDSZ), -				SizePos =:= NSizePos -			end, -		    E = if  -			    Overwrite -> -				{overwrite,Bin2,Pos}; -			    true -> -				{replace,Bin2,Pos,Size} -			end, -		    wl_lookup(LookUp, [], Term2, [E | L], LU) -	    end; -	false when Delete =:= delete -> -	    L1 = [{delete,Pos,Size} | L], -	    {Objects, L1, LU}; -	false -> -	    L1 = [{old,Pos} | L], -	    wl_lookup(LookUp, Objects, Term, L1, LU) -    end. - -%% Inlined. -wl_lookup({lookup,_}, Objects, Term, L, LU) -> -    {Objects, L, [Term | LU]}; -wl_lookup(skip, Objects, _Term, L, LU) -> -    {Objects, L, LU}. - -end_of_key([{Object,N0} | Objs], LookUp, L, LU) when N0 =/= 0 -> -    N = abs(N0), -    NL = [{insert,N,term_to_binary(Object)} | L], -    NLU = case LookUp of  -	      {lookup, _} -> -		  lists:duplicate(N, Object) ++ LU; -	      skip -> -		  LU -	  end, -    end_of_key(Objs, LookUp, NL, NLU); -end_of_key([_ | Objects], LookUp, L, LU) -> -    end_of_key(Objects, LookUp, L, LU); -end_of_key([], {lookup,Pid}, L, LU) -> -    {L, [{Pid,LU}]}; -end_of_key([], skip, L, LU) -> -    {L, LU}. - -create_writes([L | Ls], H, Ws, No) -> -    {NH, NWs, NNo} = create_writes(L, H, Ws, No, 0, true), -    create_writes(Ls, NH, NWs, NNo); -create_writes([], H, Ws, No) -> -    {H, lists:reverse(Ws), No}. - -create_writes([{old,Pos} | L], H, Ws, No, _Next, true) -> -    create_writes(L, H, Ws, No, Pos, true); -create_writes([{old,Pos} | L], H, Ws, No, Next, false) -> -    W = {Pos, <<Next:32>>}, -    create_writes(L, H, [W | Ws], No, Pos, true); -create_writes([{insert,N,Bin} | L], H, Ws, No, Next, _NextIsOld) -> -    {NH, NWs, Pos} = create_inserts(N, H, Ws, Next, byte_size(Bin), Bin), -    create_writes(L, NH, NWs, No+N, Pos, false); -create_writes([{overwrite,Bin,Pos} | L], H, Ws, No, Next, _) -> -    Size = byte_size(Bin), -    W = {Pos, [<<Next:32, Size:32, ?ACTIVE:32>>, Bin]}, -    create_writes(L, H, [W | Ws], No, Pos, true); -create_writes([{replace,Bin,Pos,OSize} | L], H, Ws, No, Next, _) -> -    Size = byte_size(Bin), -    {H1, _} = dets_utils:free(H, Pos, OSize+?OHDSZ), -    {NH, NewPos, _} = dets_utils:alloc(H1, ?OHDSZ + Size), -    W1 = {NewPos, [<<Next:32, Size:32, ?ACTIVE:32>>, Bin]}, -    NWs = if  -	      Pos =:= NewPos ->  -		  [W1 | Ws]; -	      true ->  -		  W2 = {Pos+?STATUS_POS, <<?FREE:32>>}, -		  [W1,W2 | Ws] -	  end, -    create_writes(L, NH, NWs, No, NewPos, false); -create_writes([{delete,Pos,Size} | L], H, Ws, No, Next, _) -> -    {NH, _} = dets_utils:free(H, Pos, Size+?OHDSZ), -    NWs = [{Pos+?STATUS_POS,<<?FREE:32>>} | Ws], -    create_writes(L, NH, NWs, No-1, Next, false); -create_writes([], H, Ws, No, _Next, _NextIsOld) -> -    {H, Ws, No}. - -create_inserts(0, H, Ws, Next, _Size, _Bin) -> -    {H, Ws, Next}; -create_inserts(N, H, Ws, Next, Size, Bin) -> -    {NH, Pos, _} = dets_utils:alloc(H, ?OHDSZ + Size), -    W = {Pos, [<<Next:32, Size:32, ?ACTIVE:32>>, Bin]}, -    create_inserts(N-1, NH, [W | Ws], Pos, Size, Bin). - -slot_position(S) -> -    Pos = ?SEGADDR(?SLOT2SEG(S)), -    Segment = get_segp(Pos), -    FinalPos = Segment + (4 * ?REM2(S, ?SEGSZ)), -    {FinalPos, 4}. - -%% Twice the size of a segment due to the bug in sz2pos/1. Inlined. -actual_seg_size() -> -    ?POW(sz2pos(?SEGSZ*4)-1). - -segp_cache(Pos, Segment) -> -    put(Pos, Segment). - -%% Inlined. -get_segp(Pos) -> -    get(Pos). - -%% Bug: If Sz0 is equal to 2**k for some k, then 2**(k+1) bytes are -%% allocated (wasting 2**k bytes). -sz2pos(N) -> -    1 + dets_utils:log2(N+1). - -scan_objs(_Head, Bin, From, To, L, Ts, R, _Type) -> -    scan_objs(Bin, From, To, L, Ts, R). - -scan_objs(Bin, From, To, L, Ts, -1) -> -    {stop, Bin, From, To, L, Ts}; -scan_objs(B = <<_N:32, Sz:32, St:32, T/binary>>, From, To, L, Ts, R) -> -    if  -	St =:= ?ACTIVE; -	St =:= ?FREE -> % deleted after scanning started -	    case T of -		<<BinTerm:Sz/binary, T2/binary>> -> -		    NTs = [BinTerm | Ts], -		    OSz = Sz + ?OHDSZ, -		    Skip = ?POW(sz2pos(OSz)-1) - OSz, -		    F2 = From + OSz, -		    NR = if  -			     R < 0 -> -				 R + 1; -			     true -> -				 R + OSz + Skip -			 end, -		    scan_skip(T2, F2, To, Skip, L, NTs, NR); -		_ -> -                    {more, From, To, L, Ts, R, Sz+?OHDSZ} -	    end; -	true -> % a segment -	    scan_skip(B, From, To, actual_seg_size(), L, Ts, R) -    end; -scan_objs(_B, From, To, L, Ts, R) -> -    {more, From, To, L, Ts, R, 0}. - -scan_skip(Bin, From, To, Skip, L, Ts, R) when From + Skip < To -> -    SkipPos = From + Skip, -    case Bin of -	<<_:Skip/binary, Tail/binary>> -> -	    scan_objs(Tail, SkipPos, To, L, Ts, R); -	_ -> -            {more, SkipPos, To, L, Ts, R, 0} -    end; -scan_skip(Bin, From, To, Skip, L, Ts, R) when From + Skip =:= To -> -    scan_next_allocated(Bin, From, To, L, Ts, R); -scan_skip(_Bin, From, _To, Skip, L, Ts, R) -> % when From + Skip > _To  -    From1 = From + Skip, -    {more, From1, From1, L, Ts, R, 0}. - -scan_next_allocated(_Bin, _From, To, <<>>=L, Ts, R) -> -    {more, To, To, L, Ts, R, 0}; -scan_next_allocated(Bin, From0, _To, <<From:32, To:32, L/binary>>, Ts, R) -> -    Skip = From - From0, -    scan_skip(Bin, From0, To, Skip, L, Ts, R). - -%% Read term from file at position Pos -prterm(Head, Pos, ReadAhead) -> -    Res = dets_utils:pread(Head, Pos, ?OHDSZ, ReadAhead), -    ?DEBUGF("file:pread(~tp, ~p, ?) -> ~p~n", [Head#head.filename, Pos, Res]), -    {ok, <<Next:32, Sz:32, _Status:32, Bin0/binary>>} = Res, -    ?DEBUGF("{Next, Sz} = ~p~n", [{Next, Sz}]), -    Bin = case byte_size(Bin0) of -	      Actual when Actual >= Sz -> -		  Bin0; -	      _ -> -		  {ok, Bin1} = dets_utils:pread(Head, Pos +  ?OHDSZ, Sz, 0), -		  Bin1 -	  end, -    Term = binary_to_term(Bin), -    {Next, Sz, Term}. - -%%%%%%%%%%%%%%%%%  DEBUG functions %%%%%%%%%%%%%%%% - -file_info(FH) -> -    #fileheader{closed_properly = CP, keypos = Kp, -                m = M, next = Next, n = N, version = Version, -                type = Type, no_objects = NoObjects}  -        = FH, -    if -        CP =:= 0 -> -            {error, not_closed}; -        FH#fileheader.cookie =/= ?MAGIC -> -            {error, not_a_dets_file}; -        FH#fileheader.version =/= ?FILE_FORMAT_VERSION -> -            {error, bad_version}; -        true -> -            {ok, [{closed_properly,CP},{keypos,Kp},{m, M}, -                  {n,N},{next,Next},{no_objects,NoObjects}, -                  {type,Type},{version,Version}]} -    end. - -v_segments(H) -> -    v_segments(H, 0). - -v_segments(_H, ?SEGARRSZ) -> -    done; -v_segments(H, SegNo) -> -    Seg = dets_utils:read_4(H#head.fptr, ?SEGADDR(SegNo)), -    if -	Seg =:= 0 -> -	    done; -	true -> -	    io:format("SEGMENT ~w ", [SegNo]), -	    io:format("At position ~w~n", [Seg]), -	    v_segment(H, SegNo, Seg, 0), -	    v_segments(H, SegNo+1) -    end. - -v_segment(_H, _, _SegPos, ?SEGSZ) -> -    done; -v_segment(H, SegNo, SegPos, SegSlot) -> -    Slot = SegSlot + (SegNo * ?SEGSZ), -    Chain = dets_utils:read_4(H#head.fptr, SegPos + (4 * SegSlot)), -    if  -	Chain =:= 0 ->  %% don't print empty chains -	    true; -	true -> -	    io:format("   <~p>~p: [",[SegPos + (4 * SegSlot), Slot]), -	    print_chain(H, Chain) -    end, -    v_segment(H, SegNo, SegPos, SegSlot+1). - -print_chain(_H, 0) -> -    io:format("] \n", []); -print_chain(H, Pos) -> -    {ok, _} = file:position(H#head.fptr, Pos), -    case rterm(H#head.fptr) of -	{ok, 0, _Sz, Term} -> -	    io:format("<~p>~p] \n",[Pos, Term]); -	{ok, Next, _Sz, Term} -> -	    io:format("<~p>~p, ", [Pos, Term]), -	    print_chain(H, Next); -	Other -> -	    io:format("~nERROR ~p~n", [Other]) -    end. - -%% Can't be used at the bucket level!!!! -%% Only when we go down a chain -rterm(F) -> -    case catch rterm2(F) of -	{'EXIT', Reason} -> %% truncated DAT file  -	    dets_utils:vformat("** dets: Corrupt or truncated dets file~n",  -                               []),  -	    {error, Reason}; -	Other ->  -	    Other -    end. - -rterm2(F) -> -    {ok, <<Next:32, Sz:32, _:32>>} = file:read(F, ?OHDSZ), -    {ok, Bin} = file:read(F, Sz), -    Term = binary_to_term(Bin), -    {ok, Next, Sz, Term}. - - diff --git a/lib/stdlib/src/dets_v9.erl b/lib/stdlib/src/dets_v9.erl index 6c406fc03a..3ab8f87ebf 100644 --- a/lib/stdlib/src/dets_v9.erl +++ b/lib/stdlib/src/dets_v9.erl @@ -24,8 +24,8 @@  -export([mark_dirty/1, read_file_header/2,           check_file_header/2, do_perform_save/1, initiate_file/11, -         prep_table_copy/9, init_freelist/2, fsck_input/4, -         bulk_input/3, output_objs/4, bchunk_init/2, +         prep_table_copy/9, init_freelist/1, fsck_input/4, +         bulk_input/3, output_objs/3, bchunk_init/2,           try_bchunk_header/2, compact_init/3, read_bchunks/2,           write_cache/1, may_grow/3, find_object/2, slot_objs/2,           scan_objs/8, db_hash/2, no_slots/1, table_parameters/1]). @@ -228,8 +228,8 @@  -define(CLOSED_PROPERLY_POS, 8).  -define(D_POS, 20). -%%% Dets file versions up to 8 are handled in dets_v8. This module -%%% handles version 9, introduced in R8. +%%% This module handles Dets file format version 9, introduced in +%%% Erlang/OTP R8.  %%%   %%% Version 9(a) tables have 256 reserved bytes in the file header,  %%% all initialized to zero. @@ -249,32 +249,32 @@  -define(OHDSZ, 8).          % The size of the object header, in bytes.  -define(STATUS_POS, 4).     % Position of the status field. --define(OHDSZ_v8, 12).      % The size of the version 8 object header. -  %% The size of each object is a multiple of 16.  %% BUMP is used when repairing files.  -define(BUMP, 16). -%%% '$hash' is the value of HASH_PARMS in R8, '$hash2' is the value in R9. +%%% '$hash' is the value of HASH_PARMS in Erlang/OTP R8, '$hash2' is +%%% the value in Erlang/OTP R9.  %%%  %%% The fields of the ?HASH_PARMS records are the same, but having -%%% different tags makes bchunk_init on R8 nodes reject data from R9 -%%% nodes, and vice versa. This is overkill, and due to an oversight. -%%% What should have been done in R8 was to check the hash method, not -%%% only the type of the table and the key position. R8 nodes cannot -%%% handle the phash2 method. +%%% different tags makes bchunk_init on Erlang/OTP R8 nodes reject +%%% data from Erlang/OTP R9 nodes, and vice versa. This is overkill, +%%% and due to an oversight. What should have been done in Erlang/OTP +%%% R8 was to check the hash method, not only the type of the table +%%% and the key position. Erlang/OTP R8 nodes cannot handle the phash2 +%%% method.  -define(HASH_PARMS, '$hash2').  -define(BCHUNK_FORMAT_VERSION, 1).  -record(?HASH_PARMS, { -	   file_format_version,  +	   file_format_version,  	   bchunk_format_version,   	   file, type, keypos, hash_method,  	   n,m,next,  	   min,max,  	   no_objects,no_keys, -	   no_colls  % [{LogSz,NoColls}], NoColls >= 0 +	   no_colls :: no_colls()  	  }).  -define(ACTUAL_SEG_SIZE, (?SEGSZ*4)). @@ -364,10 +364,8 @@ init_file(Fd, Tab, Fname, Type, Kp, MinSlots, MaxSlots, Ram, CacheSz,        filename = Fname,         name = Tab,        cache = dets_utils:new_cache(CacheSz), -      version = ?FILE_FORMAT_VERSION,        bump = ?BUMP, -      base = ?BASE, % to be overwritten -      mod = ?MODULE +      base = ?BASE % to be overwritten       },      FreeListsPointer = 0, @@ -457,7 +455,7 @@ alloc_seg(Head, SegZero, SegNo, Part) ->      {NewHead, InitSegment, [SegPointer]}.  %% Read free lists (using a Buddy System) from file.  -init_freelist(Head, true) -> +init_freelist(Head) ->      Pos = Head#head.freelists_p,      free_lists_from_file(Head, Pos). @@ -510,12 +508,10 @@ read_file_header(Fd, FileName) ->                       md5 = erlang:md5(MD5DigestedPart),  		     trailer = FileSize + FlBase,  		     eof = EOF, -		     n = N, -		     mod = ?MODULE}, +		     n = N},      {ok, Fd, FH}. -%% -> {ok, head(), ExtraInfo} | {error, Reason} (Reason lacking file name) -%% ExtraInfo = true +%% -> {ok, head()} | {error, Reason} (Reason lacking file name)  check_file_header(FH, Fd) ->      HashBif = code_to_hash_method(FH#fileheader.hash_method),      Test =  @@ -534,14 +530,14 @@ check_file_header(FH, Fd) ->  	    HashBif =:= undefined ->  		{error, bad_hash_bif};  	    FH#fileheader.closed_properly =:= ?CLOSED_PROPERLY -> -		{ok, true}; +		ok;  	    FH#fileheader.closed_properly =:= ?NOT_PROPERLY_CLOSED ->  		{error, not_closed};  	    true ->  		{error, not_a_dets_file}  	end,      case Test of -	{ok, ExtraInfo} -> +	ok ->              MaxObjSize = max_objsize(FH#fileheader.no_colls),  	    H = #head{  	      m = FH#fileheader.m, @@ -563,11 +559,9 @@ check_file_header(FH, Fd) ->  	      min_no_slots = FH#fileheader.min_no_slots,  	      max_no_slots = FH#fileheader.max_no_slots,  	      no_collections = FH#fileheader.no_colls, -	      version = ?FILE_FORMAT_VERSION, -	      mod = ?MODULE,  	      bump = ?BUMP,  	      base = FH#fileheader.fl_base}, -	    {ok, H, ExtraInfo}; +	    {ok, H};  	Error ->  	    Error      end. @@ -621,7 +615,7 @@ no_segs(NoSlots) ->  %%%  %%% bulk_input/3. Initialization, the general case (any stream of objects). -%%% output_objs/4. Initialization (general case) and repair. +%%% output_objs/3. Initialization (general case) and repair.  %%% bchunk_init/2. Initialization using bchunk.  bulk_input(Head, InitFun, _Cntrs) -> @@ -678,7 +672,7 @@ bulk_objects([], _Head, Kp, Seq, L) when is_integer(Kp), is_integer(Seq) ->  -define(OBJ_COUNTER, 2).  -define(KEY_COUNTER, 3). -output_objs(OldV, Head, SlotNums, Cntrs) when OldV =< 9 -> +output_objs(Head, SlotNums, Cntrs) ->      fun(close) ->              %% Make sure that the segments are initialized in case              %% init_table has been called. @@ -686,31 +680,31 @@ output_objs(OldV, Head, SlotNums, Cntrs) when OldV =< 9 ->              Acc = [], % This is the only way Acc can be empty.              true = ets:insert(Cntrs, {?FSCK_SEGMENT,0,[],0}),  	    true = ets:insert(Cntrs, {?COUNTERS, 0, 0}), -            Fun = output_objs2(foo, Acc, OldV, Head, Cache, Cntrs, +            Fun = output_objs2(foo, Acc, Head, Cache, Cntrs,  			       SlotNums, bar),              Fun(close);         ([]) -> -	    output_objs(OldV, Head, SlotNums, Cntrs); +	    output_objs(Head, SlotNums, Cntrs);         (L) ->  	    %% Information about number of objects per size is not  	    %% relevant for version 9. It is the number of collections  	    %% that matters.              true = ets:delete_all_objects(Cntrs),  	    true = ets:insert(Cntrs, {?COUNTERS, 0, 0}), -	    Es = bin2term(L, OldV, Head#head.keypos), +	    Es = bin2term(L, Head#head.keypos),  	    %% The cache is a tuple indexed by the (log) size. An element  	    %% is [BinaryObject].  	    Cache = ?VEMPTY,  	    {NE, NAcc, NCache} = output_slots(Es, Head, Cache, Cntrs, 0, 0), -	    output_objs2(NE, NAcc, OldV, Head, NCache, Cntrs, SlotNums, 1) +	    output_objs2(NE, NAcc, Head, NCache, Cntrs, SlotNums, 1)      end. -output_objs2(E, Acc, OldV, Head, Cache, SizeT, SlotNums, 0) -> +output_objs2(E, Acc, Head, Cache, SizeT, SlotNums, 0) ->      NCache = write_all_sizes(Cache, SizeT, Head, more),      %% Number of handled file_sorter chunks before writing:      Max = erlang:max(1, erlang:min(tuple_size(NCache), 10)), -    output_objs2(E, Acc, OldV, Head, NCache, SizeT, SlotNums, Max); -output_objs2(E, Acc, OldV, Head, Cache, SizeT, SlotNums, ChunkI) -> +    output_objs2(E, Acc, Head, NCache, SizeT, SlotNums, Max); +output_objs2(E, Acc, Head, Cache, SizeT, SlotNums, ChunkI) ->      fun(close) ->              {_, [], Cache1} =                    if @@ -747,11 +741,10 @@ output_objs2(E, Acc, OldV, Head, Cache, SizeT, SlotNums, ChunkI) ->  		    end  	    end;         (L) -> -	    Es = bin2term(L, OldV, Head#head.keypos), +	    Es = bin2term(L, Head#head.keypos),  	    {NE, NAcc, NCache} =   		output_slots(E, Es, Acc, Head, Cache, SizeT, 0, 0), -	    output_objs2(NE, NAcc, OldV, Head, NCache, SizeT, SlotNums, -			 ChunkI-1) +	    output_objs2(NE, NAcc, Head, NCache, SizeT, SlotNums, ChunkI-1)      end.  %%% Compaction.  @@ -1245,10 +1238,8 @@ allocate_all(Head, [{LSize,_,Data,NoCollections} | DTL], L) ->      E = {LSize,Addr,Data,NoCollections},      allocate_all(NewHead, DTL, [E | L]). -bin2term(Bin, 9, Kp) -> -    bin2term1(Bin, Kp, []); -bin2term(Bin, 8, Kp) -> -    bin2term_v8(Bin, Kp, []). +bin2term(Bin, Kp) -> +    bin2term1(Bin, Kp, []).  bin2term1([<<Slot:32, Seq:32, BinTerm/binary>> | BTs], Kp, L) ->      Term = binary_to_term(BinTerm), @@ -1257,13 +1248,6 @@ bin2term1([<<Slot:32, Seq:32, BinTerm/binary>> | BTs], Kp, L) ->  bin2term1([], _Kp, L) ->      lists:reverse(L). -bin2term_v8([<<Slot:32, BinTerm/binary>> | BTs], Kp, L) -> -    Term = binary_to_term(BinTerm), -    Key = element(Kp, Term), -    bin2term_v8(BTs, Kp, [{Slot, Key, foo, Term, BinTerm} | L]); -bin2term_v8([], _Kp, L) -> -    lists:reverse(L). -  write_all_sizes({}=Cache, _SizeT, _Head, _More) ->      Cache;  write_all_sizes(Cache, SizeT, Head, More) -> @@ -1461,7 +1445,7 @@ temp_file(Head, SizeT, N) ->  %% Does not close Fd.  fsck_input(Head, Fd, Cntrs, FileHeader) ->      MaxSz0 = case FileHeader#fileheader.has_md5 of -                 true when is_integer(FileHeader#fileheader.no_colls) ->  +                 true when is_list(FileHeader#fileheader.no_colls) ->                       ?POW(max_objsize(FileHeader#fileheader.no_colls));                   _ ->                       %% The file is not compressed, so the bucket size @@ -1485,10 +1469,10 @@ fsck_input(Head, State, Fd, MaxSz, Cntrs) ->  		done ->  		    end_of_input;  		{done, L, _Seq} -> -		    R = count_input(Head, Cntrs, L), +		    R = count_input(L),  		    {R, fsck_input(Head, done, Fd, MaxSz, Cntrs)};  		{cont, L, Bin, Pos, Seq} -> -		    R = count_input(Head, Cntrs, L), +		    R = count_input(L),                      FR = fsck_objs(Bin, Head#head.keypos, Head, [], Seq),                      NewState = fsck_read(FR, Pos, Fd, MaxSz, Head),  		    {R, fsck_input(Head, NewState, Fd, MaxSz, Cntrs)} @@ -1496,20 +1480,9 @@ fsck_input(Head, State, Fd, MaxSz, Cntrs) ->      end.  %% The ets table Cntrs is used for counting objects per size. -count_input(Head, Cntrs, L) when Head#head.version =:= 8 -> -    count_input1(Cntrs, L, []); -count_input(_Head, _Cntrs, L) -> +count_input(L) ->      lists:reverse(L). -count_input1(Cntrs, [[LogSz | B] | Ts], L) -> -    case catch ets:update_counter(Cntrs, LogSz, 1) of -	N when is_integer(N) -> ok; -	_Badarg -> true = ets:insert(Cntrs, {LogSz, 1}) -    end, -    count_input1(Cntrs, Ts, [B | L]); -count_input1(_Cntrs, [], L) -> -    L. -  fsck_read(Pos, F, L, Seq) ->      case file:position(F, Pos) of  	{ok, _} -> @@ -1564,11 +1537,6 @@ fsck_objs(Bin = <<Sz:32, Status:32, Tail/binary>>, Kp, Head, L, Seq) ->  fsck_objs(Bin, _Kp, _Head, L, Seq) ->      {more, Bin, 0, L, Seq}. -make_objects([{K,BT}|Os], Seq, Kp, Head, L) when Head#head.version =:= 8 -> -    LogSz = dets_v8:sz2pos(byte_size(BT)+?OHDSZ_v8), -    Slot = dets_v8:db_hash(K, Head), -    Obj = [LogSz | <<Slot:32, LogSz:8, BT/binary>>], -    make_objects(Os, Seq, Kp, Head, [Obj | L]);  make_objects([{K,BT} | Os], Seq, Kp, Head, L) ->      Obj = make_object(Head, K, Seq, BT),      make_objects(Os, Seq+1, Kp, Head, [Obj | L]); @@ -1607,7 +1575,7 @@ do_perform_save(H) ->      FileHeader = file_header(H1, FreeListsPointer, ?CLOSED_PROPERLY),      case dets_utils:debug_mode() of          true ->  -            TmpHead0 = init_freelist(H1#head{fixed = false}, true), +            TmpHead0 = init_freelist(H1#head{fixed = false}),              TmpHead = TmpHead0#head{base = H1#head.base},              case                   catch dets_utils:all_allocated_as_list(TmpHead) @@ -1794,7 +1762,7 @@ table_parameters(Head) ->  				      (E, A) -> [E | A]  				   end, [], CL),  	    NoColls = lists:reverse(NoColls0), -	    #?HASH_PARMS{file_format_version = Head#head.version,  +	    #?HASH_PARMS{file_format_version = ?FILE_FORMAT_VERSION,  			 bchunk_format_version = ?BCHUNK_FORMAT_VERSION,  			 file = filename:basename(Head#head.filename),  			 type = Head#head.type, diff --git a/lib/stdlib/src/dict.erl b/lib/stdlib/src/dict.erl index f921e28ef6..9449ba3dc2 100644 --- a/lib/stdlib/src/dict.erl +++ b/lib/stdlib/src/dict.erl @@ -38,7 +38,7 @@  %% Standard interface.  -export([new/0,is_key/2,to_list/1,from_list/1,size/1,is_empty/1]). --export([fetch/2,find/2,fetch_keys/1,erase/2]). +-export([fetch/2,find/2,fetch_keys/1,erase/2,take/2]).  -export([store/3,append/3,append_list/3,update/3,update/4,update_counter/3]).  -export([fold/3,map/2,filter/2,merge/3]). @@ -172,6 +172,27 @@ erase_key(Key, [E|Bkt0]) ->      {[E|Bkt1],Dc};  erase_key(_, []) -> {[],0}. +-spec take(Key, Dict) -> {Value, Dict1} | error when +      Dict :: dict(Key, Value), +      Dict1 :: dict(Key, Value), +      Key :: term(), +      Value :: term(). + +take(Key, D0) -> +    Slot = get_slot(D0, Key), +    case on_bucket(fun (B0) -> take_key(Key, B0) end, D0, Slot) of +	{D1,{Value,Dc}} -> +            {Value, maybe_contract(D1, Dc)}; +	{_,error} -> error +    end. + +take_key(Key, [?kv(Key,Val)|Bkt]) -> +    {Bkt,{Val,1}}; +take_key(Key, [E|Bkt0]) -> +    {Bkt1,Res} = take_key(Key, Bkt0), +    {[E|Bkt1],Res}; +take_key(_, []) -> {[],error}. +  -spec store(Key, Value, Dict1) -> Dict2 when        Dict1 :: dict(Key, Value),        Dict2 :: dict(Key, Value). diff --git a/lib/stdlib/src/erl_expand_records.erl b/lib/stdlib/src/erl_expand_records.erl index ebcbc54ab1..2280464bff 100644 --- a/lib/stdlib/src/erl_expand_records.erl +++ b/lib/stdlib/src/erl_expand_records.erl @@ -17,7 +17,8 @@  %%  %% %CopyrightEnd%  %% -%% Purpose : Expand records into tuples. +%% Purpose: Expand records into tuples. Also add explicit module +%% names to calls to imported functions and BIFs.  %% N.B. Although structs (tagged tuples) are not yet allowed in the  %% language there is code included in pattern/2 and expr/3 (commented out) @@ -31,7 +32,7 @@  -record(exprec, {compile=[],          % Compile flags                   vcount=0,            % Variable counter -                 imports=[],          % Imports +		 calltype=#{},	      % Call types                   records=dict:new(),  % Record definitions                   strict_ra=[],        % strict record accesses                   checked_ra=[]        % successfully accessed records @@ -46,22 +47,34 @@  %% erl_lint without errors.  module(Fs0, Opts0) ->      Opts = compiler_options(Fs0) ++ Opts0, -    St0 = #exprec{compile = Opts}, +    Calltype = init_calltype(Fs0), +    St0 = #exprec{compile = Opts, calltype = Calltype},      {Fs,_St} = forms(Fs0, St0),      Fs.  compiler_options(Forms) ->      lists:flatten([C || {attribute,_,compile,C} <- Forms]). +init_calltype(Forms) -> +    Locals = [{{Name,Arity},local} || {function,_,Name,Arity,_} <- Forms], +    Ctype = maps:from_list(Locals), +    init_calltype_imports(Forms, Ctype). + +init_calltype_imports([{attribute,_,import,{Mod,Fs}}|T], Ctype0) -> +    true = is_atom(Mod), +    Ctype = foldl(fun(FA, Acc) -> +			  Acc#{FA=>{imported,Mod}} +		  end, Ctype0, Fs), +    init_calltype_imports(T, Ctype); +init_calltype_imports([_|T], Ctype) -> +    init_calltype_imports(T, Ctype); +init_calltype_imports([], Ctype) -> Ctype. +  forms([{attribute,_,record,{Name,Defs}}=Attr | Fs], St0) ->      NDefs = normalise_fields(Defs),      St = St0#exprec{records=dict:store(Name, NDefs, St0#exprec.records)},      {Fs1, St1} = forms(Fs, St),      {[Attr | Fs1], St1}; -forms([{attribute,L,import,Is} | Fs0], St0) -> -    St1 = import(Is, St0), -    {Fs,St2} = forms(Fs0, St1), -    {[{attribute,L,import,Is} | Fs], St2};  forms([{function,L,N,A,Cs0} | Fs0], St0) ->      {Cs,St1} = clauses(Cs0, St0),      {Fs,St2} = forms(Fs0, St1), @@ -334,8 +347,16 @@ expr({'receive',Line,Cs0,To0,ToEs0}, St0) ->      {ToEs,St2} = exprs(ToEs0, St1),      {Cs,St3} = clauses(Cs0, St2),      {{'receive',Line,Cs,To,ToEs},St3}; -expr({'fun',_,{function,_F,_A}}=Fun, St) -> -    {Fun,St}; +expr({'fun',Lf,{function,F,A}}=Fun0, St0) -> +    case erl_internal:bif(F, A) of +        true -> +	    {As,St1} = new_vars(A, Lf, St0), +	    Cs = [{clause,Lf,As,[],[{call,Lf,{atom,Lf,F},As}]}], +	    Fun = {'fun',Lf,{clauses,Cs}}, +	    expr(Fun,  St1); +	false -> +	    {Fun0,St0} +    end;  expr({'fun',_,{function,_M,_F,_A}}=Fun, St) ->      {Fun,St};  expr({'fun',Line,{clauses,Cs0}}, St0) -> @@ -352,14 +373,30 @@ expr({call,Line,{remote,_,{atom,_,erlang},{atom,_,is_record}},  expr({call,Line,{tuple,_,[{atom,_,erlang},{atom,_,is_record}]},        [A,{atom,_,Name}]}, St) ->      record_test(Line, A, Name, St); +expr({call,Line,{atom,_La,record_info},[_,_]=As0}, St0) -> +    {As,St1} = expr_list(As0, St0), +    record_info_call(Line, As, St1);  expr({call,Line,{atom,_La,N}=Atom,As0}, St0) ->      {As,St1} = expr_list(As0, St0),      Ar = length(As), -    case {N,Ar} =:= {record_info,2} andalso not imported(N, Ar, St1) of -	true -> -	    record_info_call(Line, As, St1); -	false -> -	    {{call,Line,Atom,As},St1} +    NA = {N,Ar}, +    case St0#exprec.calltype of +	#{NA := local} -> +	    {{call,Line,Atom,As},St1}; +	#{NA := {imported,Module}} -> +	    ModAtom = {atom,Line,Module}, +	    {{call,Line,{remote,Line,ModAtom,Atom},As},St1}; +	_ -> +	    case erl_internal:bif(N, Ar) of +		true -> +		    ModAtom = {atom,Line,erlang}, +		    {{call,Line,{remote,Line,ModAtom,Atom},As},St1}; +		false -> +		    %% Call to a module_info/0,1 or one of the +		    %% pseudo-functions in the shell. Leave it as +		    %% a local call. +		    {{call,Line,Atom,As},St1} +	    end      end;  expr({call,Line,{remote,Lr,M,F},As0}, St0) ->      {[M1,F1 | As1],St1} = expr_list([M,F | As0], St0), @@ -470,9 +507,16 @@ lc_tq(Line, [{b_generate,Lg,P0,G0} | Qs0], St0) ->      {P1,St2} = pattern(P0, St1),      {Qs1,St3} = lc_tq(Line, Qs0, St2),      {[{b_generate,Lg,P1,G1} | Qs1],St3}; -lc_tq(Line, [F0 | Qs0], St0) -> +lc_tq(Line, [F0 | Qs0], #exprec{calltype=Calltype}=St0) ->      %% Allow record/2 and expand out as guard test. -    case erl_lint:is_guard_test(F0) of +    IsOverriden = fun(FA) -> +			  case Calltype of +			      #{FA := local} -> true; +			      #{FA := {imported,_}} -> true; +			      _ -> false +			  end +		  end, +    case erl_lint:is_guard_test(F0, [], IsOverriden) of          true ->              {F1,St1} = guard_test(F0, St0),              {Qs1,St2} = lc_tq(Line, Qs0, St1), @@ -769,6 +813,13 @@ bin_element({bin_element,Line,Expr,Size,Type}, {Es,St0}) ->                    end,      {[{bin_element,Line,Expr1,Size1,Type} | Es],St2}. +new_vars(N, L, St) -> new_vars(N, L, St, []). + +new_vars(N, L, St0, Vs) when N > 0 -> +    {V,St1} = new_var(L, St0), +    new_vars(N-1, L, St1, [V|Vs]); +new_vars(0, _L, St, Vs) -> {Vs,St}. +  new_var(L, St0) ->      {New,St1} = new_var_name(St0),      {{var,L,New},St1}. @@ -783,18 +834,6 @@ make_list(Ts, Line) ->  call_error(L, R) ->      {call,L,{remote,L,{atom,L,erlang},{atom,L,error}},[R]}. -import({Mod,Fs}, St) -> -    St#exprec{imports=add_imports(Mod, Fs, St#exprec.imports)}; -import(_Mod0, St) -> -    St. - -add_imports(Mod, [F | Fs], Is) -> -    add_imports(Mod, Fs, orddict:store(F, Mod, Is)); -add_imports(_, [], Is) -> Is. - -imported(F, A, St) -> -    orddict:is_key({F,A}, St#exprec.imports). -  %%%  %%% Replace is_record/3 in guards with matching if possible.  %%% diff --git a/lib/stdlib/src/erl_internal.erl b/lib/stdlib/src/erl_internal.erl index c08328b4b7..006e7946af 100644 --- a/lib/stdlib/src/erl_internal.erl +++ b/lib/stdlib/src/erl_internal.erl @@ -54,6 +54,8 @@  -export([is_type/2]). +-export([add_predefined_functions/1]). +  %%---------------------------------------------------------------------------  %%  Erlang builtin functions allowed in guards. @@ -61,42 +63,28 @@        Name :: atom(),        Arity :: arity(). +%% Please keep the alphabetical order.  guard_bif(abs, 1) -> true; -guard_bif(float, 1) -> true; -guard_bif(trunc, 1) -> true; -guard_bif(round, 1) -> true; -guard_bif(length, 1) -> true; -guard_bif(hd, 1) -> true; -guard_bif(tl, 1) -> true; -guard_bif(size, 1) -> true; +guard_bif(binary_part, 2) -> true; +guard_bif(binary_part, 3) -> true;  guard_bif(bit_size, 1) -> true;  guard_bif(byte_size, 1) -> true; +guard_bif(ceil, 1) -> true;  guard_bif(element, 2) -> true; -guard_bif(self, 0) -> true; +guard_bif(float, 1) -> true; +guard_bif(floor, 1) -> true; +guard_bif(hd, 1) -> true; +guard_bif(length, 1) -> true;  guard_bif(map_size, 1) -> true;  guard_bif(node, 0) -> true;  guard_bif(node, 1) -> true; +guard_bif(round, 1) -> true; +guard_bif(self, 0) -> true; +guard_bif(size, 1) -> true; +guard_bif(tl, 1) -> true; +guard_bif(trunc, 1) -> true;  guard_bif(tuple_size, 1) -> true; -guard_bif(is_atom, 1) -> true; -guard_bif(is_binary, 1) -> true; -guard_bif(is_bitstring, 1) -> true; -guard_bif(is_boolean, 1) -> true; -guard_bif(is_float, 1) -> true; -guard_bif(is_function, 1) -> true; -guard_bif(is_function, 2) -> true; -guard_bif(is_integer, 1) -> true; -guard_bif(is_list, 1) -> true; -guard_bif(is_map, 1) -> true; -guard_bif(is_number, 1) -> true; -guard_bif(is_pid, 1) -> true; -guard_bif(is_port, 1) -> true; -guard_bif(is_reference, 1) -> true; -guard_bif(is_tuple, 1) -> true; -guard_bif(is_record, 2) -> true; -guard_bif(is_record, 3) -> true; -guard_bif(binary_part, 2) -> true; -guard_bif(binary_part, 3) -> true; -guard_bif(Name, A) when is_atom(Name), is_integer(A) -> false. +guard_bif(Name, A) -> new_type_test(Name, A).  %%  Erlang type tests.  -spec type_test(Name, Arity) -> boolean() when @@ -109,10 +97,11 @@ type_test(Name, Arity) ->  %%  Erlang new-style type tests.  -spec new_type_test(Name::atom(), Arity::arity()) -> boolean(). +%% Please keep the alphabetical order.  new_type_test(is_atom, 1) -> true; -new_type_test(is_boolean, 1) -> true;  new_type_test(is_binary, 1) -> true;  new_type_test(is_bitstring, 1) -> true; +new_type_test(is_boolean, 1) -> true;  new_type_test(is_float, 1) -> true;  new_type_test(is_function, 1) -> true;  new_type_test(is_function, 2) -> true; @@ -122,10 +111,10 @@ new_type_test(is_map, 1) -> true;  new_type_test(is_number, 1) -> true;  new_type_test(is_pid, 1) -> true;  new_type_test(is_port, 1) -> true; -new_type_test(is_reference, 1) -> true; -new_type_test(is_tuple, 1) -> true;  new_type_test(is_record, 2) -> true;  new_type_test(is_record, 3) -> true; +new_type_test(is_reference, 1) -> true; +new_type_test(is_tuple, 1) -> true;  new_type_test(Name, A) when is_atom(Name), is_integer(A) -> false.  %%  Erlang old-style type tests. @@ -271,6 +260,7 @@ bif(bitsize, 1) -> true;  bif(bit_size, 1) -> true;  bif(bitstring_to_list, 1) -> true;  bif(byte_size, 1) -> true; +bif(ceil, 1) -> true;  bif(check_old_code, 1) -> true;  bif(check_process_code, 2) -> true;  bif(check_process_code, 3) -> true; @@ -291,6 +281,7 @@ bif(float_to_list, 1) -> true;  bif(float_to_list, 2) -> true;  bif(float_to_binary, 1) -> true;  bif(float_to_binary, 2) -> true; +bif(floor, 1) -> true;  bif(garbage_collect, 0) -> true;  bif(garbage_collect, 1) -> true;  bif(garbage_collect, 2) -> true; @@ -584,3 +575,68 @@ is_type(term, 0) -> true;  is_type(timeout, 0) -> true;  is_type(tuple, 0) -> true;  is_type(_, _) -> false. + +%%% +%%% Add and export the pre-defined functions: +%%% +%%%   module_info/0 +%%%   module_info/1 +%%%   behaviour_info/1 (optional) +%%% + +-spec add_predefined_functions(Forms) -> UpdatedForms when +      Forms :: [erl_parse:abstract_form() | erl_parse:form_info()], +      UpdatedForms :: [erl_parse:abstract_form() | erl_parse:form_info()]. + +add_predefined_functions(Forms) -> +    Forms ++ predefined_functions(Forms). + +predefined_functions(Forms) -> +    Attrs = [{Name,Val} || {attribute,_,Name,Val} <- Forms], +    {module,Mod} = lists:keyfind(module, 1, Attrs), +    Callbacks = [Callback || {callback,Callback} <- Attrs], +    OptionalCallbacks = get_optional_callbacks(Attrs), +    Mpf1 = module_predef_func_beh_info(Callbacks, OptionalCallbacks), +    Mpf2 = module_predef_funcs_mod_info(Mod), +    Mpf = [erl_parse:new_anno(F) || F <- Mpf1++Mpf2], +    Exp = [{F,A} || {function,_,F,A,_} <- Mpf], +    [{attribute,0,export,Exp}|Mpf]. + +get_optional_callbacks(Attrs) -> +    L = [O || {optional_callbacks,O} <- Attrs, is_fa_list(O)], +    lists:append(L). + +is_fa_list([{FuncName, Arity}|L]) +  when is_atom(FuncName), is_integer(Arity), Arity >= 0 -> +    is_fa_list(L); +is_fa_list([]) -> true; +is_fa_list(_) -> false. + +module_predef_func_beh_info([], _) -> +    []; +module_predef_func_beh_info(Callbacks0, OptionalCallbacks) -> +    Callbacks = [FA || {{_,_}=FA,_} <- Callbacks0], +    List = make_list(Callbacks), +    OptionalList = make_list(OptionalCallbacks), +    [{function,0,behaviour_info,1, +      [{clause,0,[{atom,0,callbacks}],[],[List]}, +       {clause,0,[{atom,0,optional_callbacks}],[],[OptionalList]}]}]. + +make_list([]) -> {nil,0}; +make_list([{Name,Arity}|Rest]) -> +    {cons,0, +     {tuple,0, +      [{atom,0,Name}, +       {integer,0,Arity}]}, +     make_list(Rest)}. + +module_predef_funcs_mod_info(Mod) -> +    ModAtom = {atom,0,Mod}, +    [{function,0,module_info,0, +      [{clause,0,[],[], +        [{call,0,{remote,0,{atom,0,erlang},{atom,0,get_module_info}}, +          [ModAtom]}]}]}, +     {function,0,module_info,1, +      [{clause,0,[{var,0,'X'}],[], +        [{call,0,{remote,0,{atom,0,erlang},{atom,0,get_module_info}}, +          [ModAtom,{var,0,'X'}]}]}]}]. diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl index e9332ce069..1b84234fac 100644 --- a/lib/stdlib/src/erl_lint.erl +++ b/lib/stdlib/src/erl_lint.erl @@ -27,7 +27,7 @@  -export([module/1,module/2,module/3,format_error/1]).  -export([exprs/2,exprs_opt/3,used_vars/2]). % Used from erl_eval.erl. --export([is_pattern_expr/1,is_guard_test/1,is_guard_test/2]). +-export([is_pattern_expr/1,is_guard_test/1,is_guard_test/2,is_guard_test/3]).  -export([is_guard_expr/1]).  -export([bool_option/4,value_option/3,value_option/7]). @@ -238,7 +238,11 @@ format_error({removed_type, MNA, ReplacementMNA, Rel}) ->      io_lib:format("the type ~s was removed in ~s; use ~s instead",                    [format_mna(MNA), Rel, format_mna(ReplacementMNA)]);  format_error({obsolete_guard, {F, A}}) -> -    io_lib:format("~p/~p obsolete", [F, A]); +    io_lib:format("~p/~p obsolete (use is_~p/~p)", [F, A, F, A]); +format_error({obsolete_guard_overridden,Test}) -> +    io_lib:format("obsolete ~s/1 (meaning is_~s/1) is illegal when " +		  "there is a local/imported function named is_~p/1 ", +		  [Test,Test,Test]);  format_error({too_many_arguments,Arity}) ->      io_lib:format("too many arguments (~w) - "  		  "maximum allowed is ~w", [Arity,?MAX_ARGUMENTS]); @@ -522,7 +526,7 @@ start(File, Opts) ->  		      true, Opts)},  	 {export_all,  	  bool_option(warn_export_all, nowarn_export_all, -		      false, Opts)}, +		      true, Opts)},  	 {export_vars,  	  bool_option(warn_export_vars, nowarn_export_vars,  		      false, Opts)}, @@ -1765,7 +1769,8 @@ bit_size({atom,_Line,all}, _Vt, St, _Check) -> {all,[],St};  bit_size(Size, Vt, St, Check) ->      %% Try to safely evaluate Size if constant to get size,      %% otherwise just treat it as an expression. -    case is_gexpr(Size, St#lint.records) of +    Info = is_guard_test2_info(St), +    case is_gexpr(Size, Info) of          true ->              case erl_eval:partial_eval(Size) of                  {integer,_ILn,I} -> {I,[],St}; @@ -2000,77 +2005,104 @@ gexpr_list(Es, Vt, St) ->  %% is_guard_test(Expression) -> boolean().  %%  Test if a general expression is a guard test. +%% +%%  Note: Only use this function in contexts where there can be +%%  no definition of a local function that may override a guard BIF +%%  (for example, in the shell).  -spec is_guard_test(Expr) -> boolean() when        Expr :: erl_parse:abstract_expr().  is_guard_test(E) -> -    is_guard_test2(E, dict:new()). +    is_guard_test2(E, {dict:new(),fun(_) -> false end}).  %% is_guard_test(Expression, Forms) -> boolean().  is_guard_test(Expression, Forms) -> +    is_guard_test(Expression, Forms, fun(_) -> false end). + + +%% is_guard_test(Expression, Forms, IsOverridden) -> boolean(). +%%  Test if a general expression is a guard test. +%% +%%  IsOverridden({Name,Arity}) should return 'true' if Name/Arity is +%%  a local or imported function in the module. If the abstract code has +%%  passed through erl_expand_records, any call without an explicit +%%  module is to a local function, so IsOverridden can be defined as: +%% +%%    fun(_) -> true end +%% +-spec is_guard_test(Expr, Forms, IsOverridden) -> boolean() when +      Expr :: erl_parse:abstract_expr(), +      Forms :: [erl_parse:abstract_form() | erl_parse:form_info()], +      IsOverridden :: fun((fa()) -> boolean()). + +is_guard_test(Expression, Forms, IsOverridden) ->      RecordAttributes = [A || A = {attribute, _, record, _D} <- Forms],      St0 = foldl(fun(Attr0, St1) ->                          Attr = set_file(Attr0, "none"),                          attribute_state(Attr, St1)                  end, start(), RecordAttributes), -    is_guard_test2(set_file(Expression, "nofile"), St0#lint.records). +    is_guard_test2(set_file(Expression, "nofile"), +		   {St0#lint.records,IsOverridden}).  %% is_guard_test2(Expression, RecordDefs :: dict:dict()) -> boolean(). -is_guard_test2({call,Line,{atom,Lr,record},[E,A]}, RDs) -> -    is_gexpr({call,Line,{atom,Lr,is_record},[E,A]}, RDs); -is_guard_test2({call,_Line,{atom,_La,Test},As}=Call, RDs) -> -    case erl_internal:type_test(Test, length(As)) of -        true -> is_gexpr_list(As, RDs); -        false -> is_gexpr(Call, RDs) -    end; -is_guard_test2(G, RDs) -> +is_guard_test2({call,Line,{atom,Lr,record},[E,A]}, Info) -> +    is_gexpr({call,Line,{atom,Lr,is_record},[E,A]}, Info); +is_guard_test2({call,_Line,{atom,_La,Test},As}=Call, {_,IsOverridden}=Info) -> +    A = length(As), +    not IsOverridden({Test,A}) andalso +	case erl_internal:type_test(Test, A) of +	    true -> is_gexpr_list(As, Info); +	    false -> is_gexpr(Call, Info) +	end; +is_guard_test2(G, Info) ->      %%Everything else is a guard expression. -    is_gexpr(G, RDs). +    is_gexpr(G, Info).  %% is_guard_expr(Expression) -> boolean().  %%  Test if an expression is a guard expression.  is_guard_expr(E) -> is_gexpr(E, []). -is_gexpr({var,_L,_V}, _RDs) -> true; -is_gexpr({char,_L,_C}, _RDs) -> true; -is_gexpr({integer,_L,_I}, _RDs) -> true; -is_gexpr({float,_L,_F}, _RDs) -> true; -is_gexpr({atom,_L,_A}, _RDs) -> true; -is_gexpr({string,_L,_S}, _RDs) -> true; -is_gexpr({nil,_L}, _RDs) -> true; -is_gexpr({cons,_L,H,T}, RDs) -> is_gexpr_list([H,T], RDs); -is_gexpr({tuple,_L,Es}, RDs) -> is_gexpr_list(Es, RDs); -%%is_gexpr({struct,_L,_Tag,Es}, RDs) -> -%%    is_gexpr_list(Es, RDs); -is_gexpr({record_index,_L,_Name,Field}, RDs) -> -    is_gexpr(Field, RDs); -is_gexpr({record_field,_L,Rec,_Name,Field}, RDs) -> -    is_gexpr_list([Rec,Field], RDs); -is_gexpr({record,L,Name,Inits}, RDs) -> -    is_gexpr_fields(Inits, L, Name, RDs); -is_gexpr({bin,_L,Fs}, RDs) -> +is_gexpr({var,_L,_V}, _Info) -> true; +is_gexpr({char,_L,_C}, _Info) -> true; +is_gexpr({integer,_L,_I}, _Info) -> true; +is_gexpr({float,_L,_F}, _Info) -> true; +is_gexpr({atom,_L,_A}, _Info) -> true; +is_gexpr({string,_L,_S}, _Info) -> true; +is_gexpr({nil,_L}, _Info) -> true; +is_gexpr({cons,_L,H,T}, Info) -> is_gexpr_list([H,T], Info); +is_gexpr({tuple,_L,Es}, Info) -> is_gexpr_list(Es, Info); +%%is_gexpr({struct,_L,_Tag,Es}, Info) -> +%%    is_gexpr_list(Es, Info); +is_gexpr({record_index,_L,_Name,Field}, Info) -> +    is_gexpr(Field, Info); +is_gexpr({record_field,_L,Rec,_Name,Field}, Info) -> +    is_gexpr_list([Rec,Field], Info); +is_gexpr({record,L,Name,Inits}, Info) -> +    is_gexpr_fields(Inits, L, Name, Info); +is_gexpr({bin,_L,Fs}, Info) ->      all(fun ({bin_element,_Line,E,Sz,_Ts}) -> -                is_gexpr(E, RDs) and (Sz =:= default orelse is_gexpr(Sz, RDs)) +                is_gexpr(E, Info) and (Sz =:= default orelse is_gexpr(Sz, Info))          end, Fs); -is_gexpr({call,_L,{atom,_Lf,F},As}, RDs) -> +is_gexpr({call,_L,{atom,_Lf,F},As}, {_,IsOverridden}=Info) ->      A = length(As), -    erl_internal:guard_bif(F, A) andalso is_gexpr_list(As, RDs); -is_gexpr({call,_L,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, RDs) -> +    not IsOverridden({F,A}) andalso erl_internal:guard_bif(F, A) +	andalso is_gexpr_list(As, Info); +is_gexpr({call,_L,{remote,_Lr,{atom,_Lm,erlang},{atom,_Lf,F}},As}, Info) ->      A = length(As),      (erl_internal:guard_bif(F, A) orelse is_gexpr_op(F, A)) -        andalso is_gexpr_list(As, RDs); -is_gexpr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,F}]},As}, RDs) -> -    is_gexpr({call,L,{remote,Lt,{atom,Lm,erlang},{atom,Lf,F}},As}, RDs); -is_gexpr({op,_L,Op,A}, RDs) -> -    is_gexpr_op(Op, 1) andalso is_gexpr(A, RDs); -is_gexpr({op,_L,'andalso',A1,A2}, RDs) -> -    is_gexpr_list([A1,A2], RDs); -is_gexpr({op,_L,'orelse',A1,A2}, RDs) -> -    is_gexpr_list([A1,A2], RDs); -is_gexpr({op,_L,Op,A1,A2}, RDs) -> -    is_gexpr_op(Op, 2) andalso is_gexpr_list([A1,A2], RDs); -is_gexpr(_Other, _RDs) -> false. +        andalso is_gexpr_list(As, Info); +is_gexpr({call,L,{tuple,Lt,[{atom,Lm,erlang},{atom,Lf,F}]},As}, Info) -> +    is_gexpr({call,L,{remote,Lt,{atom,Lm,erlang},{atom,Lf,F}},As}, Info); +is_gexpr({op,_L,Op,A}, Info) -> +    is_gexpr_op(Op, 1) andalso is_gexpr(A, Info); +is_gexpr({op,_L,'andalso',A1,A2}, Info) -> +    is_gexpr_list([A1,A2], Info); +is_gexpr({op,_L,'orelse',A1,A2}, Info) -> +    is_gexpr_list([A1,A2], Info); +is_gexpr({op,_L,Op,A1,A2}, Info) -> +    is_gexpr_op(Op, 2) andalso is_gexpr_list([A1,A2], Info); +is_gexpr(_Other, _Info) -> false.  is_gexpr_op(Op, A) ->      try erl_internal:op_type(Op, A) of @@ -2082,14 +2114,14 @@ is_gexpr_op(Op, A) ->      catch _:_ -> false      end. -is_gexpr_list(Es, RDs) -> all(fun (E) -> is_gexpr(E, RDs) end, Es). +is_gexpr_list(Es, Info) -> all(fun (E) -> is_gexpr(E, Info) end, Es). -is_gexpr_fields(Fs, L, Name, RDs) -> +is_gexpr_fields(Fs, L, Name, {RDs,_}=Info) ->      IFs = case dict:find(Name, RDs) of                {ok,{_Line,Fields}} -> Fs ++ init_fields(Fs, L, Fields);                error  -> Fs            end, -    all(fun ({record_field,_Lf,_Name,V}) -> is_gexpr(V, RDs); +    all(fun ({record_field,_Lf,_Name,V}) -> is_gexpr(V, Info);              (_Other) -> false end, IFs).  %% exprs(Sequence, VarTable, State) -> @@ -3193,7 +3225,8 @@ lc_quals([{b_generate,_Line,P,E} | Qs], Vt0, Uvt0, St0) ->      {Vt,Uvt,St} = handle_generator(P,E,Vt0,Uvt0,St1),      lc_quals(Qs, Vt, Uvt, St);  lc_quals([F|Qs], Vt, Uvt, St0) -> -    {Fvt,St1} = case is_guard_test2(F, St0#lint.records) of +    Info = is_guard_test2_info(St0), +    {Fvt,St1} = case is_guard_test2(F, Info) of  		    true -> guard_test(F, Vt, St0);  		    false -> expr(F, Vt, St0)  		end, @@ -3201,6 +3234,12 @@ lc_quals([F|Qs], Vt, Uvt, St0) ->  lc_quals([], Vt, Uvt, St) ->      {Vt, Uvt, St}. +is_guard_test2_info(#lint{records=RDs,locals=Locals,imports=Imports}) -> +    {RDs,fun(FA) -> +		 is_local_function(Locals, FA) orelse +		     is_imported_function(Imports, FA) +	 end}. +  handle_generator(P,E,Vt,Uvt,St0) ->      {Evt,St1} = expr(E, Vt, St0),      %% Forget variables local to E immediately. @@ -3618,16 +3657,26 @@ obsolete_guard({call,Line,{atom,Lr,F},As}, St0) ->  	false ->  	    deprecated_function(Line, erlang, F, As, St0);  	true -> -	    case is_warn_enabled(obsolete_guard, St0) of -		true -> -		    add_warning(Lr,{obsolete_guard, {F, Arity}}, St0); -		false -> -		    St0 -	    end +	    St = case is_warn_enabled(obsolete_guard, St0) of +		     true -> +			 add_warning(Lr, {obsolete_guard, {F, Arity}}, St0); +		     false -> +			 St0 +		 end, +	    test_overriden_by_local(Lr, F, Arity, St)      end;  obsolete_guard(_G, St) ->      St. +test_overriden_by_local(Line, OldTest, Arity, St) -> +    ModernTest = list_to_atom("is_"++atom_to_list(OldTest)), +    case is_local_function(St#lint.locals, {ModernTest, Arity}) of +	true -> +	    add_error(Line, {obsolete_guard_overridden,OldTest}, St); +	false -> +	    St +    end. +  %% keyword_warning(Line, Atom, State) -> State.  %%  Add warning for atoms that will be reserved keywords in the future.  %%  (Currently, no such keywords to warn for.) diff --git a/lib/stdlib/src/erl_parse.yrl b/lib/stdlib/src/erl_parse.yrl index d2dd2848b5..922455a6f2 100644 --- a/lib/stdlib/src/erl_parse.yrl +++ b/lib/stdlib/src/erl_parse.yrl @@ -33,7 +33,6 @@ list tail  list_comprehension lc_expr lc_exprs  binary_comprehension  tuple -%struct  record_expr record_tuple record_field record_fields  map_expr map_tuple map_field map_field_assoc map_field_exact map_fields map_key  if_expr if_clause if_clauses case_expr cr_clause cr_clauses receive_expr @@ -108,9 +107,8 @@ type_sig -> fun_type 'when' type_guards   : {type, ?anno('$1'), bounded_fun,  type_guards -> type_guard                 : ['$1'].  type_guards -> type_guard ',' type_guards : ['$1'|'$3']. -type_guard -> atom '(' top_types ')'      : {type, ?anno('$1'), constraint, -                                             ['$1', '$3']}. -type_guard -> var '::' top_type           : build_def('$1', '$3'). +type_guard -> atom '(' top_types ')'   : build_compat_constraint('$1', '$3'). +type_guard -> var '::' top_type        : build_constraint('$1', '$3').  top_types -> top_type                     : ['$1'].  top_types -> top_type ',' top_types       : ['$1'|'$3']. @@ -269,7 +267,6 @@ expr_max -> binary : '$1'.  expr_max -> list_comprehension : '$1'.  expr_max -> binary_comprehension : '$1'.  expr_max -> tuple : '$1'. -%%expr_max -> struct : '$1'.  expr_max -> '(' expr ')' : '$2'.  expr_max -> 'begin' exprs 'end' : {block,?anno('$1'),'$2'}.  expr_max -> if_expr : '$1'. @@ -328,10 +325,6 @@ lc_expr -> binary '<=' expr : {b_generate,?anno('$2'),'$1','$3'}.  tuple -> '{' '}' : {tuple,?anno('$1'),[]}.  tuple -> '{' exprs '}' : {tuple,?anno('$1'),'$2'}. - -%%struct -> atom tuple : -%%	{struct,?anno('$1'),element(3, '$1'),element(3, '$2')}. -  map_expr -> '#' map_tuple :  	{map, ?anno('$1'),'$2'}.  map_expr -> expr_max '#' map_tuple : @@ -517,6 +510,22 @@ comp_op -> '>' : '$1'.  comp_op -> '=:=' : '$1'.  comp_op -> '=/=' : '$1'. +Header +"%% This file was automatically generated from the file \"erl_parse.yrl\"." +"%%" +"%% Copyright Ericsson AB 1996-2015. All Rights Reserved." +"%%" +"%% Licensed under the Apache License, Version 2.0 (the \"License\"); you may" +"%% not use this file except in compliance with the License. You may obtain" +"%% a copy of the License at <http://www.apache.org/licenses/LICENSE-2.0>" +"%%" +"%% Unless required by applicable law or agreed to in writing, software" +"%% distributed under the License is distributed on an \"AS IS\" BASIS," +"%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied." +"%% See the License for the specific language governing permissions and" +"%% limitations under the License." +"". +  Erlang code.  -export([parse_form/1,parse_exprs/1,parse_term/1]). @@ -1041,13 +1050,13 @@ build_typed_attribute({atom,Aa,Attr},_) ->      end.  build_type_spec({Kind,Aa}, {SpecFun, TypeSpecs}) -  when (Kind =:= spec) or (Kind =:= callback) -> +  when Kind =:= spec ; Kind =:= callback ->      NewSpecFun =  	case SpecFun of  	    {atom, _, Fun} ->  		{Fun, find_arity_from_specs(TypeSpecs)}; -	    {{atom,_, Mod}, {atom,_, Fun}} -> -		{Mod,Fun,find_arity_from_specs(TypeSpecs)} +	    {{atom, _, Mod}, {atom, _, Fun}} -> +		{Mod, Fun, find_arity_from_specs(TypeSpecs)}          end,      {attribute,Aa,Kind,{NewSpecFun, TypeSpecs}}. @@ -1061,11 +1070,24 @@ find_arity_from_specs([Spec|_]) ->      {type, _, 'fun', [{type, _, product, Args},_]} = Fun,      length(Args). -build_def({var, A, '_'}, _Types) -> +%% The 'is_subtype(V, T)' syntax is not supported as of Erlang/OTP +%% 19.0, but is kept for backward compatibility. +build_compat_constraint({atom, _, is_subtype}, [{var, _, _}=LHS, Type]) -> +    build_constraint(LHS, Type); +build_compat_constraint({atom, _, is_subtype}, [LHS, _Type]) -> +    ret_err(?anno(LHS), "bad type variable"); +build_compat_constraint({atom, A, Atom}, _Types) -> +    ret_err(A, io_lib:format("unsupported constraint ~w", [Atom])). + +build_constraint({atom, _, is_subtype}, [{var, _, _}=LHS, Type]) -> +    build_constraint(LHS, Type); +build_constraint({atom, A, Atom}, _Foo) -> +    ret_err(A, io_lib:format("unsupported constraint ~w", [Atom])); +build_constraint({var, A, '_'}, _Types) ->      ret_err(A, "bad type variable"); -build_def(LHS, Types) -> +build_constraint(LHS, Type) ->      IsSubType = {atom, ?anno(LHS), is_subtype}, -    {type, ?anno(LHS), constraint, [IsSubType, [LHS, Types]]}. +    {type, ?anno(LHS), constraint, [IsSubType, [LHS, Type]]}.  lift_unions(T1, {type, _Aa, union, List}) ->      {type, ?anno(T1), union, [T1|List]}; @@ -1571,19 +1593,6 @@ anno_from_term(Term) ->      NewTerm.  %% Forms. -%% Recognize what sys_pre_expand does: -modify_anno1({'fun',A,F,{_,_,_}=Id}, Ac, Mf) -> -    {A1,Ac1} = Mf(A, Ac), -    {F1,Ac2} = modify_anno1(F, Ac1, Mf), -    {{'fun',A1,F1,Id},Ac2}; -modify_anno1({named_fun,A,N,F,{_,_,_}=Id}, Ac, Mf) -> -    {A1,Ac1} = Mf(A, Ac), -    {F1,Ac2} = modify_anno1(F, Ac1, Mf), -    {{named_fun,A1,N,F1,Id},Ac2}; -modify_anno1({attribute,A,N,[V]}, Ac, Mf) -> -    {{attribute,A1,N1,V1},Ac1} = modify_anno1({attribute,A,N,V}, Ac, Mf), -    {{attribute,A1,N1,[V1]},Ac1}; -%% End of sys_pre_expand special forms.  modify_anno1({function,F,A}, Ac, _Mf) ->      {{function,F,A},Ac};  modify_anno1({function,M,F,A}, Ac, Mf) -> diff --git a/lib/stdlib/src/error_logger_file_h.erl b/lib/stdlib/src/error_logger_file_h.erl index 665685d3ee..0b262de3ab 100644 --- a/lib/stdlib/src/error_logger_file_h.erl +++ b/lib/stdlib/src/error_logger_file_h.erl @@ -116,8 +116,8 @@ write_event(#st{fd=Fd}=State, Event) ->  	ignore ->  	    ok;  	{Head,Pid,FormatList} -> -	    Time = maybe_utc(erlang:universaltime()), -	    Header = write_time(Time, Head), +	    Time = erlang:universaltime(), +	    Header = header(Time, Head),  	    Body = format_body(State, FormatList),  	    AtNode = if  			 node(Pid) =/= node() -> @@ -125,7 +125,7 @@ write_event(#st{fd=Fd}=State, Event) ->  			 true ->  			     []  		     end, -	    io:put_chars(Fd, [Header,Body,AtNode]) +	    io:put_chars(Fd, [Header,AtNode,Body])      end.  format_body(State, [{Format,Args}|T]) -> @@ -172,21 +172,6 @@ parse_event({warning_report, _GL, {Pid, std_warning, Args}}) ->      {"WARNING REPORT",Pid,format_term(Args)};  parse_event(_) -> ignore. -maybe_utc(Time) -> -    UTC = case application:get_env(sasl, utc_log) of -              {ok, Val} -> Val; -              undefined -> -                  %% Backwards compatible: -                  case application:get_env(stdlib, utc_log) of -                      {ok, Val} -> Val; -                      undefined -> false -                  end -          end, -    maybe_utc(Time, UTC). - -maybe_utc(Time, true) -> {utc, Time}; -maybe_utc(Time, _) -> {local, calendar:universal_time_to_local_time(Time)}. -  format_term(Term) when is_list(Term) ->      case string_p(Term) of  	true -> @@ -227,17 +212,33 @@ string_p1([H|T]) when is_list(H) ->  string_p1([]) -> true;  string_p1(_) ->  false. -write_time({utc,{{Y,Mo,D},{H,Mi,S}}}, Type) -> -    io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n", -		  [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]); -write_time({local, {{Y,Mo,D},{H,Mi,S}}}, Type) -> -    io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n", -		  [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]). +get_utc_config() -> +    %% SASL utc_log configuration overrides stdlib config +    %% in order to have uniform timestamps in log messages +    case application:get_env(sasl, utc_log) of +        {ok, Val} -> Val; +        undefined -> +            case application:get_env(stdlib, utc_log) of +                {ok, Val} -> Val; +                undefined -> false +            end +    end. + +header(Time, Title) -> +    case get_utc_config() of +        true -> +            header(Time, Title, "UTC "); +        _ -> +            header(calendar:universal_time_to_local_time(Time), Title, "") +    end. + +header({{Y,Mo,D},{H,Mi,S}}, Title, UTC) -> +    io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ~s===~n", +                  [Title,D,month(Mo),Y,t(H),t(Mi),t(S),UTC]).  t(X) when is_integer(X) -> -    t1(integer_to_list(X)); -t(_) -> -    "". +    t1(integer_to_list(X)). +  t1([X]) -> [$0,X];  t1(X)   -> X. @@ -253,5 +254,3 @@ month(9) -> "Sep";  month(10) -> "Oct";  month(11) -> "Nov";  month(12) -> "Dec". - - diff --git a/lib/stdlib/src/error_logger_tty_h.erl b/lib/stdlib/src/error_logger_tty_h.erl index cb22a8c0b6..2f2fd65252 100644 --- a/lib/stdlib/src/error_logger_tty_h.erl +++ b/lib/stdlib/src/error_logger_tty_h.erl @@ -128,13 +128,12 @@ write_events(State, [Ev|Es]) ->  write_events(_State, []) ->      ok. -do_write_event(State, {Time0, Event}) -> +do_write_event(State, {Time, Event}) ->      case parse_event(Event) of  	ignore ->  	    ok; -	{Head,Pid,FormatList} -> -	    Time = maybe_utc(Time0), -	    Header = write_time(Time, Head), +	{Title,Pid,FormatList} -> +	    Header = header(Time, Title),  	    Body = format_body(State, FormatList),  	    AtNode = if  			 node(Pid) =/= node() -> @@ -142,7 +141,7 @@ do_write_event(State, {Time0, Event}) ->  			 true ->  			     []  		     end, -	    Str = [Header,Body,AtNode], +	    Str = [Header,AtNode,Body],  	    case State#st.io_mod of  		io_lib ->  		    Str; @@ -197,21 +196,6 @@ parse_event({warning_report, _GL, {Pid, std_warning, Args}}) ->      {"WARNING REPORT",Pid,format_term(Args)};  parse_event(_) -> ignore. -maybe_utc(Time) -> -    UTC = case application:get_env(sasl, utc_log) of -              {ok, Val} -> Val; -              undefined -> -                  %% Backwards compatible: -                  case application:get_env(stdlib, utc_log) of -                      {ok, Val} -> Val; -                      undefined -> false -                  end -          end, -    maybe_utc(Time, UTC). - -maybe_utc(Time, true) -> {utc, Time}; -maybe_utc(Time, _) -> {local, calendar:universal_time_to_local_time(Time)}. -  format_term(Term) when is_list(Term) ->      case string_p(Term) of  	true -> @@ -255,12 +239,29 @@ string_p1([H|T]) when is_list(H) ->  string_p1([]) -> true;  string_p1(_) ->  false. -write_time({utc,{{Y,Mo,D},{H,Mi,S}}},Type) -> -    io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s UTC ===~n", -		  [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]); -write_time({local, {{Y,Mo,D},{H,Mi,S}}},Type) -> -    io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ===~n", -		  [Type,D,month(Mo),Y,t(H),t(Mi),t(S)]). +get_utc_config() -> +    %% SASL utc_log configuration overrides stdlib config +    %% in order to have uniform timestamps in log messages +    case application:get_env(sasl, utc_log) of +        {ok, Val} -> Val; +        undefined -> +            case application:get_env(stdlib, utc_log) of +                {ok, Val} -> Val; +                undefined -> false +            end +    end. + +header(Time, Title) -> +    case get_utc_config() of +        true -> +            header(Time, Title, "UTC "); +        _ -> +            header(calendar:universal_time_to_local_time(Time), Title, "") +    end. + +header({{Y,Mo,D},{H,Mi,S}}, Title, UTC) -> +    io_lib:format("~n=~s==== ~p-~s-~p::~s:~s:~s ~s===~n", +                 [Title,D,month(Mo),Y,t(H),t(Mi),t(S),UTC]).  t(X) when is_integer(X) ->      t1(integer_to_list(X)); @@ -281,8 +282,3 @@ month(9) -> "Sep";  month(10) -> "Oct";  month(11) -> "Nov";  month(12) -> "Dec". - - - - - diff --git a/lib/stdlib/src/eval_bits.erl b/lib/stdlib/src/eval_bits.erl index 80667023fb..631faa3be5 100644 --- a/lib/stdlib/src/eval_bits.erl +++ b/lib/stdlib/src/eval_bits.erl @@ -67,16 +67,20 @@ expr_grp([Field | FS], Bs0, Lf, Acc) ->  expr_grp([], Bs0, _Lf, Acc) ->      {value,Acc,Bs0}. +eval_field({bin_element, _, {string, _, S}, {integer,_,8}, [integer,{unit,1},unsigned,big]}, Bs0, _Fun) -> +    Latin1 = [C band 16#FF || C <- S], +    {list_to_binary(Latin1),Bs0};  eval_field({bin_element, _, {string, _, S}, default, default}, Bs0, _Fun) ->      Latin1 = [C band 16#FF || C <- S],      {list_to_binary(Latin1),Bs0}; -eval_field({bin_element, Line, {string, _, S}, Size0, Options0}, Bs, _Fun) -> -    {_Size,[Type,_Unit,_Sign,Endian]} =  +eval_field({bin_element, Line, {string, _, S}, Size0, Options0}, Bs0, Fun) -> +    {Size1,[Type,{unit,Unit},Sign,Endian]} =          make_bit_type(Line, Size0, Options0), -    Res = << <<(eval_exp_field1(C, no_size, no_unit, -				Type, Endian, no_sign))/binary>> || +    {value,Size,Bs1} = Fun(Size1, Bs0), +    Res = << <<(eval_exp_field1(C, Size, Unit, +				Type, Endian, Sign))/binary>> ||  	      C <- S >>, -    {Res,Bs}; +    {Res,Bs1};  eval_field({bin_element,Line,E,Size0,Options0}, Bs0, Fun) ->      {value,V,Bs1} = Fun(E, Bs0),      {Size1,[Type,{unit,Unit},Sign,Endian]} =  diff --git a/lib/stdlib/src/gb_sets.erl b/lib/stdlib/src/gb_sets.erl index 47a8fa6db0..6d6f7d40ac 100644 --- a/lib/stdlib/src/gb_sets.erl +++ b/lib/stdlib/src/gb_sets.erl @@ -1,8 +1,3 @@ -%% -%% %CopyrightBegin% -%%  -%% Copyright Ericsson AB 2001-2015. All Rights Reserved. -%%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License.  %% You may obtain a copy of the License at @@ -14,8 +9,6 @@  %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  %% See the License for the specific language governing permissions and  %% limitations under the License. -%%  -%% %CopyrightEnd%  %%  %% =====================================================================  %% Ordered Sets implemented as General Balanced Trees diff --git a/lib/stdlib/src/gb_trees.erl b/lib/stdlib/src/gb_trees.erl index c4a20d92a7..c0cdde012e 100644 --- a/lib/stdlib/src/gb_trees.erl +++ b/lib/stdlib/src/gb_trees.erl @@ -1,8 +1,3 @@ -%% -%% %CopyrightBegin% -%%  -%% Copyright Ericsson AB 2001-2015. All Rights Reserved. -%%   %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License.  %% You may obtain a copy of the License at @@ -14,8 +9,6 @@  %% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  %% See the License for the specific language governing permissions and  %% limitations under the License. -%%  -%% %CopyrightEnd%  %%  %% =====================================================================  %% General Balanced Trees - highly efficient dictionaries. @@ -59,6 +52,13 @@  %% - delete_any(X, T): removes key X from tree T if the key is present  %%   in the tree, otherwise does nothing; returns new tree.  %% +%% - take(X, T): removes element with key X from tree T; returns new tree +%%   without removed element. Assumes that the key is present in the tree. +%% +%% - take_any(X, T): removes element with key X from tree T and returns +%%   a new tree if the key is present; otherwise does nothing and returns +%%   'error'. +%%  %% - balance(T): rebalances tree T. Note that this is rarely necessary,  %%   but may be motivated when a large number of entries have been  %%   deleted from the tree without further insertions. Rebalancing could @@ -121,7 +121,8 @@  -export([empty/0, is_empty/1, size/1, lookup/2, get/2, insert/3,  	 update/3, enter/3, delete/2, delete_any/2, balance/1,  	 is_defined/2, keys/1, values/1, to_list/1, from_orddict/1, -	 smallest/1, largest/1, take_smallest/1, take_largest/1, +	 smallest/1, largest/1, take/2, take_any/2, +         take_smallest/1, take_largest/1,  	 iterator/1, iterator_from/2, next/1, map/2]). @@ -423,6 +424,41 @@ merge(Smaller, Larger) ->  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +-spec take_any(Key, Tree1) -> {Value, Tree2} | 'error' when +      Tree1 :: tree(Key, _), +      Tree2 :: tree(Key, _), +      Key   :: term(), +      Value :: term(). + +take_any(Key, Tree) -> +    case is_defined(Key, Tree) of +        true -> take(Key, Tree); +        false -> error +    end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +-spec take(Key, Tree1) -> {Value, Tree2} when +      Tree1 :: tree(Key, _), +      Tree2 :: tree(Key, _), +      Key   :: term(), +      Value :: term(). + +take(Key, {S, T}) when is_integer(S), S >= 0 -> +    {Value, Res} = take_1(Key, T), +    {Value, {S - 1, Res}}. + +take_1(Key, {Key1, Value, Smaller, Larger}) when Key < Key1 -> +    {Value2, Smaller1} = take_1(Key, Smaller), +    {Value2, {Key1, Value, Smaller1, Larger}}; +take_1(Key, {Key1, Value, Smaller, Bigger}) when Key > Key1 -> +    {Value2, Bigger1} = take_1(Key, Bigger), +    {Value2, {Key1, Value, Smaller, Bigger1}}; +take_1(_, {_Key, Value, Smaller, Larger}) -> +    {Value, merge(Smaller, Larger)}. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +  -spec take_smallest(Tree1) -> {Key, Value, Tree2} when        Tree1 :: tree(Key, Value),        Tree2 :: tree(Key, Value). diff --git a/lib/stdlib/src/gen_event.erl b/lib/stdlib/src/gen_event.erl index ccacf658e9..4839fe4f2c 100644 --- a/lib/stdlib/src/gen_event.erl +++ b/lib/stdlib/src/gen_event.erl @@ -32,7 +32,9 @@  %%% Modified by Martin - uses proc_lib, sys and gen! --export([start/0, start/1, start_link/0, start_link/1, stop/1, stop/3, +-export([start/0, start/1, start/2, +         start_link/0, start_link/1, start_link/2, +         stop/1, stop/3,  	 notify/2, sync_notify/2,  	 add_handler/3, add_sup_handler/3, delete_handler/3, swap_handler/3,  	 swap_sup_handler/3, which_handlers/1, call/3, call/4, wake_hib/4]). @@ -117,30 +119,64 @@  -type del_handler_ret()  :: ok | term() | {'EXIT',term()}.  -type emgr_name() :: {'local', atom()} | {'global', atom()} -		   | {'via', atom(), term()}. +                   | {'via', atom(), term()}. +-type debug_flag() :: 'trace' | 'log' | 'statistics' | 'debug' +                    | {'logfile', string()}. +-type option() :: {'timeout', timeout()} +                | {'debug', [debug_flag()]} +                | {'spawn_opt', [proc_lib:spawn_option()]}.  -type emgr_ref()  :: atom() | {atom(), atom()} |  {'global', atom()} -		   | {'via', atom(), term()} | pid(). +                   | {'via', atom(), term()} | pid().  -type start_ret() :: {'ok', pid()} | {'error', term()}.  %%---------------------------------------------------------------------------  -define(NO_CALLBACK, 'no callback module'). +%% ----------------------------------------------------------------- +%% Starts a generic event handler. +%% start() +%% start(MgrName | Options) +%% start(MgrName, Options) +%% start_link() +%% start_link(MgrName | Options) +%% start_link(MgrName, Options) +%%    MgrName ::= {local, atom()} | {global, atom()} | {via, atom(), term()} +%%    Options ::= [{timeout, Timeout} | {debug, [Flag]} | {spawn_opt,SOpts}] +%%       Flag ::= trace | log | {logfile, File} | statistics | debug +%%          (debug == log && statistics) +%% Returns: {ok, Pid} | +%%          {error, {already_started, Pid}} | +%%          {error, Reason} +%% ----------------------------------------------------------------- +  -spec start() -> start_ret().  start() ->      gen:start(?MODULE, nolink, ?NO_CALLBACK, [], []). --spec start(emgr_name()) -> start_ret(). -start(Name) -> -    gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], []). +-spec start(emgr_name() | [option()]) -> start_ret(). +start(Name) when is_tuple(Name) -> +    gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], []); +start(Options) when is_list(Options) -> +    gen:start(?MODULE, nolink, ?NO_CALLBACK, [], Options). + +-spec start(emgr_name(), [option()]) -> start_ret(). +start(Name, Options) -> +    gen:start(?MODULE, nolink, Name, ?NO_CALLBACK, [], Options).  -spec start_link() -> start_ret().  start_link() ->      gen:start(?MODULE, link, ?NO_CALLBACK, [], []). --spec start_link(emgr_name()) -> start_ret(). -start_link(Name) -> -    gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], []). +-spec start_link(emgr_name() | [option()]) -> start_ret(). +start_link(Name) when is_tuple(Name) -> +    gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], []); +start_link(Options) when is_list(Options) -> +    gen:start(?MODULE, link, ?NO_CALLBACK, [], Options). + +-spec start_link(emgr_name(), [option()]) -> start_ret(). +start_link(Name, Options) -> +    gen:start(?MODULE, link, Name, ?NO_CALLBACK, [], Options).  %% -spec init_it(pid(), 'self' | pid(), emgr_name(), module(), [term()], [_]) ->   init_it(Starter, self, Name, Mod, Args, Options) -> @@ -160,7 +196,7 @@ add_sup_handler(M, Handler, Args)  ->      rpc(M, {add_sup_handler, Handler, Args, self()}).  -spec notify(emgr_ref(), term()) -> 'ok'. -notify(M, Event) -> send(M, {notify, Event}).  +notify(M, Event) -> send(M, {notify, Event}).  -spec sync_notify(emgr_ref(), term()) -> 'ok'.  sync_notify(M, Event) -> rpc(M, {sync_notify, Event}). @@ -193,7 +229,7 @@ stop(M) ->  stop(M, Reason, Timeout) ->      gen:stop(M, Reason, Timeout). -rpc(M, Cmd) ->  +rpc(M, Cmd) ->      {ok, Reply} = gen:call(M, self(), Cmd, infinity),      Reply. @@ -421,7 +457,7 @@ server_add_handler({Mod,Id}, Args, MSL) ->      Handler = #handler{module = Mod,  		       id = Id},      server_add_handler(Mod, Handler, Args, MSL); -server_add_handler(Mod, Args, MSL) ->  +server_add_handler(Mod, Args, MSL) ->      Handler = #handler{module = Mod},      server_add_handler(Mod, Handler, Args, MSL). @@ -446,7 +482,7 @@ server_add_sup_handler({Mod,Id}, Args, MSL, Parent) ->  		       id = Id,  		       supervised = Parent},      server_add_handler(Mod, Handler, Args, MSL); -server_add_sup_handler(Mod, Args, MSL, Parent) ->  +server_add_sup_handler(Mod, Args, MSL, Parent) ->      link(Parent),      Handler = #handler{module = Mod,  		       supervised = Parent}, @@ -454,7 +490,7 @@ server_add_sup_handler(Mod, Args, MSL, Parent) ->  %% server_delete_handler(HandlerId, Args, MSL) -> {Ret, MSL'} -server_delete_handler(HandlerId, Args, MSL, SName) ->  +server_delete_handler(HandlerId, Args, MSL, SName) ->      case split(HandlerId, MSL) of  	{Mod, Handler, MSL1} ->  	    {do_terminate(Mod, Handler, Args, @@ -511,7 +547,7 @@ split_and_terminate(HandlerId, Args, MSL, SName, Handler2, Sup) ->  %% server_notify(Event, Func, MSL, SName) -> MSL' -server_notify(Event, Func, [Handler|T], SName) ->  +server_notify(Event, Func, [Handler|T], SName) ->      case server_update(Handler, Func, Event, SName) of  	{ok, Handler1} ->  	    {Hib, NewHandlers} = server_notify(Event, Func, T, SName), @@ -531,9 +567,9 @@ server_update(Handler1, Func, Event, SName) ->      Mod1 = Handler1#handler.module,      State = Handler1#handler.state,      case catch Mod1:Func(Event, State) of -	{ok, State1} ->  +	{ok, State1} ->  	    {ok, Handler1#handler{state = State1}}; -	{ok, State1, hibernate} ->  +	{ok, State1, hibernate} ->  	    {hibernate, Handler1#handler{state = State1}};  	{swap_handler, Args1, State1, Handler2, Args2} ->  	    do_swap(Mod1, Handler1, Args1, State1, Handler2, Args2, SName); @@ -644,14 +680,14 @@ server_call_update(Handler1, Query, SName) ->      Mod1 = Handler1#handler.module,      State = Handler1#handler.state,      case catch Mod1:handle_call(Query, State) of -	{ok, Reply, State1} ->  +	{ok, Reply, State1} ->  	    {{ok, Handler1#handler{state = State1}}, Reply}; -	{ok, Reply, State1, hibernate} ->  -	    {{hibernate, Handler1#handler{state = State1}},  +	{ok, Reply, State1, hibernate} -> +	    {{hibernate, Handler1#handler{state = State1}},  	     Reply};  	{swap_handler, Reply, Args1, State1, Handler2, Args2} ->  	    {do_swap(Mod1,Handler1,Args1,State1,Handler2,Args2,SName), Reply}; -	{remove_handler, Reply} ->  +	{remove_handler, Reply} ->  	    do_terminate(Mod1, Handler1, remove_handler, State,  			 remove, SName, normal),  	    {no, Reply}; @@ -686,7 +722,7 @@ report_error(_Handler, normal, _, _, _)             -> ok;  report_error(_Handler, shutdown, _, _, _)           -> ok;  report_error(_Handler, {swapped,_,_}, _, _, _)      -> ok;  report_error(Handler, Reason, State, LastIn, SName) -> -    Reason1 =  +    Reason1 =  	case Reason of  	    {'EXIT',{undef,[{M,F,A,L}|MFAs]}} ->  		case code:is_loaded(M) of diff --git a/lib/stdlib/src/gen_server.erl b/lib/stdlib/src/gen_server.erl index 5800aca66f..284810c971 100644 --- a/lib/stdlib/src/gen_server.erl +++ b/lib/stdlib/src/gen_server.erl @@ -386,7 +386,7 @@ decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->  	    sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,  				  [Name, State, Mod, Time], Hib);  	{'EXIT', Parent, Reason} -> -	    terminate(Reason, Name, Msg, Mod, State, Debug); +	    terminate(Reason, Name, undefined, Msg, Mod, State, Debug);  	_Msg when Debug =:= [] ->  	    handle_msg(Msg, Parent, Name, State, Mod);  	_Msg -> @@ -658,14 +658,14 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod) ->  	    loop(Parent, Name, NState, Mod, Time1, []);  	{ok, {stop, Reason, Reply, NState}} ->  	    {'EXIT', R} =  -		(catch terminate(Reason, Name, Msg, Mod, NState, [])), +		(catch terminate(Reason, Name, From, Msg, Mod, NState, [])),  	    reply(From, Reply),  	    exit(R); -	Other -> handle_common_reply(Other, Parent, Name, Msg, Mod, State) +	Other -> handle_common_reply(Other, Parent, Name, From, Msg, Mod, State)      end;  handle_msg(Msg, Parent, Name, State, Mod) ->      Reply = try_dispatch(Msg, Mod, State), -    handle_common_reply(Reply, Parent, Name, Msg, Mod, State). +    handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod, State).  handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) ->      Result = try_handle_call(Mod, Msg, From, State), @@ -686,31 +686,31 @@ handle_msg({'$gen_call', From, Msg}, Parent, Name, State, Mod, Debug) ->  	    loop(Parent, Name, NState, Mod, Time1, Debug1);  	{ok, {stop, Reason, Reply, NState}} ->  	    {'EXIT', R} =  -		(catch terminate(Reason, Name, Msg, Mod, NState, Debug)), +		(catch terminate(Reason, Name, From, Msg, Mod, NState, Debug)),  	    _ = reply(Name, From, Reply, NState, Debug),  	    exit(R);  	Other -> -	    handle_common_reply(Other, Parent, Name, Msg, Mod, State, Debug) +	    handle_common_reply(Other, Parent, Name, From, Msg, Mod, State, Debug)      end;  handle_msg(Msg, Parent, Name, State, Mod, Debug) ->      Reply = try_dispatch(Msg, Mod, State), -    handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug). +    handle_common_reply(Reply, Parent, Name, undefined, Msg, Mod, State, Debug). -handle_common_reply(Reply, Parent, Name, Msg, Mod, State) -> +handle_common_reply(Reply, Parent, Name, From, Msg, Mod, State) ->      case Reply of  	{ok, {noreply, NState}} ->  	    loop(Parent, Name, NState, Mod, infinity, []);  	{ok, {noreply, NState, Time1}} ->  	    loop(Parent, Name, NState, Mod, Time1, []);  	{ok, {stop, Reason, NState}} -> -	    terminate(Reason, Name, Msg, Mod, NState, []); +	    terminate(Reason, Name, From, Msg, Mod, NState, []);  	{'EXIT', ExitReason, ReportReason} -> -	    terminate(ExitReason, ReportReason, Name, Msg, Mod, State, []); +	    terminate(ExitReason, ReportReason, Name, From, Msg, Mod, State, []);  	{ok, BadReply} -> -	    terminate({bad_return_value, BadReply}, Name, Msg, Mod, State, []) +	    terminate({bad_return_value, BadReply}, Name, From, Msg, Mod, State, [])      end. -handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) -> +handle_common_reply(Reply, Parent, Name, From, Msg, Mod, State, Debug) ->      case Reply of  	{ok, {noreply, NState}} ->  	    Debug1 = sys:handle_debug(Debug, fun print_event/3, Name, @@ -721,11 +721,11 @@ handle_common_reply(Reply, Parent, Name, Msg, Mod, State, Debug) ->  				      {noreply, NState}),  	    loop(Parent, Name, NState, Mod, Time1, Debug1);  	{ok, {stop, Reason, NState}} -> -	    terminate(Reason, Name, Msg, Mod, NState, Debug); +	    terminate(Reason, Name, From, Msg, Mod, NState, Debug);  	{'EXIT', ExitReason, ReportReason} -> -	    terminate(ExitReason, ReportReason, Name, Msg, Mod, State, Debug); +	    terminate(ExitReason, ReportReason, Name, From, Msg, Mod, State, Debug);  	{ok, BadReply} -> -	    terminate({bad_return_value, BadReply}, Name, Msg, Mod, State, Debug) +	    terminate({bad_return_value, BadReply}, Name, From, Msg, Mod, State, Debug)      end.  reply(Name, {To, Tag}, Reply, State, Debug) -> @@ -743,7 +743,7 @@ system_continue(Parent, Debug, [Name, State, Mod, Time]) ->  -spec system_terminate(_, _, _, [_]) -> no_return().  system_terminate(Reason, _Parent, Debug, [Name, State, Mod, _Time]) -> -    terminate(Reason, Name, [], Mod, State, Debug). +    terminate(Reason, Name, undefined, [], Mod, State, Debug).  system_code_change([Name, State, Mod, Time], _Module, OldVsn, Extra) ->      case catch Mod:code_change(OldVsn, State, Extra) of @@ -786,17 +786,17 @@ print_event(Dev, Event, Name) ->  %%% Terminate the server.  %%% --------------------------------------------------- --spec terminate(_, _, _, _, _, _) -> no_return(). -terminate(Reason, Name, Msg, Mod, State, Debug) -> -    terminate(Reason, Reason, Name, Msg, Mod, State, Debug). -  -spec terminate(_, _, _, _, _, _, _) -> no_return(). -terminate(ExitReason, ReportReason, Name, Msg, Mod, State, Debug) -> +terminate(Reason, Name, From, Msg, Mod, State, Debug) -> +    terminate(Reason, Reason, Name, From, Msg, Mod, State, Debug). + +-spec terminate(_, _, _, _, _, _, _, _) -> no_return(). +terminate(ExitReason, ReportReason, Name, From, Msg, Mod, State, Debug) ->      Reply = try_terminate(Mod, ExitReason, State),      case Reply of  	{'EXIT', ExitReason1, ReportReason1} ->  	    FmtState = format_status(terminate, Mod, get(), State), -	    error_info(ReportReason1, Name, Msg, FmtState, Debug), +	    error_info(ReportReason1, Name, From, Msg, FmtState, Debug),  	    exit(ExitReason1);  	_ ->  	    case ExitReason of @@ -808,17 +808,17 @@ terminate(ExitReason, ReportReason, Name, Msg, Mod, State, Debug) ->  		    exit(Shutdown);  		_ ->  		    FmtState = format_status(terminate, Mod, get(), State), -		    error_info(ReportReason, Name, Msg, FmtState, Debug), +		    error_info(ReportReason, Name, From, Msg, FmtState, Debug),  		    exit(ExitReason)  	    end      end. -error_info(_Reason, application_controller, _Msg, _State, _Debug) -> +error_info(_Reason, application_controller, _From, _Msg, _State, _Debug) ->      %% OTP-5811 Don't send an error report if it's the system process      %% application_controller which is terminating - let init take care      %% of it instead      ok; -error_info(Reason, Name, Msg, State, Debug) -> +error_info(Reason, Name, From, Msg, State, Debug) ->      Reason1 =   	case Reason of  	    {undef,[{M,F,A,L}|MFAs]} -> @@ -835,15 +835,36 @@ error_info(Reason, Name, Msg, State, Debug) ->  		end;  	    _ ->  		Reason -	end,     +	end, +    {ClientFmt, ClientArgs} = client_stacktrace(From),      format("** Generic server ~p terminating \n"             "** Last message in was ~p~n"             "** When Server state == ~p~n" -           "** Reason for termination == ~n** ~p~n", -	   [Name, Msg, State, Reason1]), +           "** Reason for termination == ~n** ~p~n" ++ ClientFmt, +	   [Name, Msg, State, Reason1] ++ ClientArgs),      sys:print_log(Debug),      ok. +client_stacktrace(undefined) -> +    {"", []}; +client_stacktrace({From, _Tag}) -> +    client_stacktrace(From); +client_stacktrace(From) when is_pid(From), node(From) =:= node() -> +    case process_info(From, [current_stacktrace, registered_name]) of +        undefined -> +            {"** Client ~p is dead~n", [From]}; +        [{current_stacktrace, Stacktrace}, {registered_name, []}]  -> +            {"** Client ~p stacktrace~n" +             "** ~p~n", +             [From, Stacktrace]}; +        [{current_stacktrace, Stacktrace}, {registered_name, Name}]  -> +            {"** Client ~p stacktrace~n" +             "** ~p~n", +             [Name, Stacktrace]} +    end; +client_stacktrace(From) when is_pid(From) -> +    {"** Client ~p is remote on node ~p~n", [From, node(From)]}. +  %%-----------------------------------------------------------------  %% Status information  %%----------------------------------------------------------------- diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl index 1da866dc88..c7b75961cb 100644 --- a/lib/stdlib/src/io_lib_format.erl +++ b/lib/stdlib/src/io_lib_format.erl @@ -343,7 +343,8 @@ term(T, F, Adj, P0, Pad) ->  %% print(Term, Depth, Field, Adjust, Precision, PadChar, Encoding,  %%       Indentation) -%%  Print a term. +%% Print a term. Field width sets maximum line length, Precision sets +%% initial indentation.  print(T, D, none, Adj, P, Pad, E, Str, I) ->      print(T, D, 80, Adj, P, Pad, E, Str, I); diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl index 16ca2f41dc..94376408d1 100644 --- a/lib/stdlib/src/io_lib_pretty.erl +++ b/lib/stdlib/src/io_lib_pretty.erl @@ -97,31 +97,42 @@ print(Term, Col, Ll, D, RecDefFun) ->  print(Term, Col, Ll, D, M, RecDefFun) ->      print(Term, Col, Ll, D, M, RecDefFun, latin1, true). +%% D = Depth, default -1 (infinite), or LINEMAX=30 when printing from shell +%% Col = current column, default 1 +%% Ll = line length/~p field width, default 80 +%% M = CHAR_MAX (-1 if no max, 60 when printing from shell)  print(_, _, _, 0, _M, _RF, _Enc, _Str) -> "...";  print(Term, Col, Ll, D, M, RecDefFun, Enc, Str) when Col =< 0 -> +    %% ensure Col is at least 1      print(Term, 1, Ll, D, M, RecDefFun, Enc, Str);  print(Term, Col, Ll, D, M0, RecDefFun, Enc, Str) when is_tuple(Term);                                                        is_list(Term);                                                        is_map(Term);                                                        is_bitstring(Term) -> +    %% preprocess and compute total number of chars      If = {_S, Len} = print_length(Term, D, RecDefFun, Enc, Str), +    %% use Len as CHAR_MAX if M0 = -1      M = max_cs(M0, Len),      if          Len < Ll - Col, Len =< M -> +            %% write the whole thing on a single line when there is room              write(If);          true -> +            %% compute the indentation TInd for tagged tuples and records              TInd = while_fail([-1, 4],                                 fun(I) -> cind(If, Col, Ll, M, I, 0, 0) end,                                 1),              pp(If, Col, Ll, M, TInd, indent(Col), 0, 0)      end;  print(Term, _Col, _Ll, _D, _M, _RF, _Enc, _Str) -> +    %% atomic data types (bignums, atoms, ...) are never truncated      io_lib:write(Term).  %%%  %%% Local functions  %%% +%% use M only if nonnegative, otherwise use Len as default value  max_cs(M, Len) when M < 0 ->      Len;  max_cs(M, _Len) -> @@ -153,6 +164,7 @@ pp({S, _Len}, _Col, _Ll, _M, _TInd, _Ind, _LD, _W) ->  %%  Print a tagged tuple by indenting the rest of the elements  %%  differently to the tag. Tuple has size >= 2.  pp_tag_tuple([{Tag,Tlen} | L], Col, Ll, M, TInd, Ind, LD, W) -> +    %% this uses TInd      TagInd = Tlen + 2,      Tcol = Col + TagInd,      S = $,, @@ -207,6 +219,7 @@ pp_field({{field, Name, NameL, F}, _Len}, Col0, Ll, M, TInd, Ind0, LD, W0) ->      {[Name, " = ", S | pp(F, Col, Ll, M, TInd, Ind, LD, W)], Ll}. % force nl  rec_indent(RInd, TInd, Col0, Ind0, W0) -> +    %% this uses TInd      Nl = (TInd > 0) and (RInd > TInd),      DCol = case Nl of                 true -> TInd; @@ -285,6 +298,7 @@ pp_binary(S, N, _N0, Ind) ->              S      end. +%% write the whole thing on a single line  write({{tuple, _IsTagged, L}, _}) ->      [${, write_list(L, $,), $}];  write({{list, L}, _}) -> @@ -344,8 +358,10 @@ print_length({}, _D, _RF, _Enc, _Str) ->  print_length(#{}=M, _D, _RF, _Enc, _Str) when map_size(M) =:= 0 ->      {"#{}", 3};  print_length(List, D, RF, Enc, Str) when is_list(List) -> +    %% only flat lists are "printable"      case Str andalso printable_list(List, D, Enc) of          true -> +            %% print as string, escaping double-quotes in the list              S = write_string(List, Enc),              {S, length(S)};          %% Truncated lists could break some existing code. @@ -401,6 +417,7 @@ print_length(<<_/bitstring>>=Bin, D, _RF, Enc, Str) ->      end;      print_length(Term, _D, _RF, _Enc, _Str) ->      S = io_lib:write(Term), +    %% S can contain unicode, so iolist_size(S) cannot be used here      {S, lists:flatlength(S)}.  print_length_map(_Map, 1, _RF, _Enc, _Str) -> @@ -483,6 +500,7 @@ list_length_tail({_, Len}, Acc) ->  %% ?CHARS printable characters has depth 1.  -define(CHARS, 4). +%% only flat lists are "printable"  printable_list(_L, 1, _Enc) ->      false;  printable_list(L, _D, latin1) -> @@ -736,9 +754,11 @@ while_fail([], _F, V) ->  while_fail([A | As], F, V) ->      try F(A) catch _ -> while_fail(As, F, V) end. +%% make a string of N spaces  indent(N) when is_integer(N), N > 0 ->      chars($\s, N-1). +%% prepend N spaces onto Ind  indent(1, Ind) -> % Optimization of common case      [$\s | Ind];  indent(4, Ind) -> % Optimization of common case diff --git a/lib/stdlib/src/math.erl b/lib/stdlib/src/math.erl index 97c965e27a..3a3b384d8f 100644 --- a/lib/stdlib/src/math.erl +++ b/lib/stdlib/src/math.erl @@ -25,7 +25,9 @@  -export([sin/1, cos/1, tan/1, asin/1, acos/1, atan/1, atan2/2, sinh/1,           cosh/1, tanh/1, asinh/1, acosh/1, atanh/1, exp/1, log/1, -         log2/1, log10/1, pow/2, sqrt/1, erf/1, erfc/1]). +         log2/1, log10/1, pow/2, sqrt/1, erf/1, erfc/1, +         ceil/1, floor/1, +         fmod/2]).  -spec acos(X) -> float() when        X :: number(). @@ -63,6 +65,11 @@ atan2(_, _) ->  atanh(_) ->      erlang:nif_error(undef). +-spec ceil(X) -> float() when +      X :: number(). +ceil(_) -> +    erlang:nif_error(undef). +  -spec cos(X) -> float() when        X :: number().  cos(_) -> @@ -88,6 +95,16 @@ erfc(_) ->  exp(_) ->      erlang:nif_error(undef). +-spec floor(X) -> float() when +      X :: number(). +floor(_) -> +    erlang:nif_error(undef). + +-spec fmod(X, Y) -> float() when +      X :: number(), Y :: number(). +fmod(_, _) -> +    erlang:nif_error(undef). +  -spec log(X) -> float() when        X :: number().  log(_) -> diff --git a/lib/stdlib/src/orddict.erl b/lib/stdlib/src/orddict.erl index 37cf0084f0..caa59099af 100644 --- a/lib/stdlib/src/orddict.erl +++ b/lib/stdlib/src/orddict.erl @@ -22,7 +22,7 @@  %% Standard interface.  -export([new/0,is_key/2,to_list/1,from_list/1,size/1,is_empty/1]). --export([fetch/2,find/2,fetch_keys/1,erase/2]). +-export([fetch/2,find/2,fetch_keys/1,erase/2,take/2]).  -export([store/3,append/3,append_list/3,update/3,update/4,update_counter/3]).  -export([fold/3,map/2,filter/2,merge/3]). @@ -106,6 +106,23 @@ erase(Key, [{K,_}=E|Dict]) when Key > K ->  erase(_Key, [{_K,_Val}|Dict]) -> Dict;		%Key == K  erase(_, []) -> []. +-spec take(Key, Orddict) -> {Value, Orddict1} | error when +      Orddict :: orddict(Key, Value), +      Orddict1 :: orddict(Key, Value), +      Key :: term(), +      Value :: term(). + +take(Key, Dict) -> +    take_1(Key, Dict, []). + +take_1(Key, [{K,_}|_], _Acc) when Key < K -> +    error; +take_1(Key, [{K,_}=P|D], Acc) when Key > K -> +    take_1(Key, D, [P|Acc]); +take_1(_Key, [{_K,Value}|D], Acc) -> +    {Value,lists:reverse(Acc, D)}; +take_1(_, [], _) -> error. +  -spec store(Key, Value, Orddict1) -> Orddict2 when        Orddict1 :: orddict(Key, Value),        Orddict2 :: orddict(Key, Value). diff --git a/lib/stdlib/src/otp_internal.erl b/lib/stdlib/src/otp_internal.erl index 3bd338071b..5bf77a5160 100644 --- a/lib/stdlib/src/otp_internal.erl +++ b/lib/stdlib/src/otp_internal.erl @@ -47,9 +47,6 @@ obsolete(Module, Name, Arity) ->  obsolete_1(net, _, _) ->      {deprecated, "module 'net' obsolete; use 'net_adm'"}; -obsolete_1(erlang, hash, 2) -> -    {deprecated, {erlang, phash2, 2}}; -  obsolete_1(erlang, now, 0) ->      {deprecated,       "Deprecated BIF. See the \"Time and Time Correction in Erlang\" " @@ -408,7 +405,7 @@ obsolete_1(docb_xml_check, _, _) ->  %% Added in R15B  obsolete_1(asn1rt, F, _) when F == load_driver; F == unload_driver -> -    {deprecated,"deprecated (will be removed in OTP 18); has no effect as drivers are no longer used"}; +    {removed,"removed (will be removed in OTP 18); has no effect as drivers are no longer used"};  obsolete_1(ssl, pid, 1) ->      {removed,"was removed in R16; is no longer needed"};  obsolete_1(inviso, _, _) -> @@ -416,7 +413,7 @@ obsolete_1(inviso, _, _) ->  %% Added in R15B01.  obsolete_1(gs, _, _) -> -    {deprecated,"the gs application has been deprecated and will be removed in OTP 18; use the wx application instead"}; +    {removed,"the gs application has been removed; use the wx application instead"};  obsolete_1(ssh, sign_data, 2) ->      {deprecated,"deprecated (will be removed in R16A); use public_key:pem_decode/1, public_key:pem_entry_decode/1 "       "and public_key:sign/3 instead"}; @@ -463,21 +460,23 @@ obsolete_1(wxCursor, new, 4) ->  %% Added in OTP 17.  obsolete_1(asn1ct, decode,3) -> -    {deprecated,"deprecated; use Mod:decode/2 instead"}; +    {removed,"removed; use Mod:decode/2 instead"}; +obsolete_1(asn1ct, encode, 2) -> +    {removed,"removed; use Mod:encode/2 instead"};  obsolete_1(asn1ct, encode, 3) -> -    {deprecated,"deprecated; use Mod:encode/2 instead"}; +    {removed,"removed; use Mod:encode/2 instead"};  obsolete_1(asn1rt, decode,3) -> -    {deprecated,"deprecated; use Mod:decode/2 instead"}; +    {removed,"removed; use Mod:decode/2 instead"};  obsolete_1(asn1rt, encode, 2) -> -    {deprecated,"deprecated; use Mod:encode/2 instead"}; +    {removed,"removed; use Mod:encode/2 instead"};  obsolete_1(asn1rt, encode, 3) -> -    {deprecated,"deprecated; use Mod:encode/2 instead"}; +    {removed,"removed; use Mod:encode/2 instead"};  obsolete_1(asn1rt, info, 1) -> -    {deprecated,"deprecated; use Mod:info/0 instead"}; +    {removed,"removed; use Mod:info/0 instead"};  obsolete_1(asn1rt, utf8_binary_to_list, 1) -> -    {deprecated,{unicode,characters_to_list,1}}; +    {removed,{unicode,characters_to_list,1},"OTP 20"};  obsolete_1(asn1rt, utf8_list_to_binary, 1) -> -    {deprecated,{unicode,characters_to_binary,1}}; +    {removed,{unicode,characters_to_binary,1},"OTP 20"};  %% Added in OTP 18.  obsolete_1(core_lib, get_anno, 1) -> @@ -551,6 +550,13 @@ obsolete_1(overload, _, _) ->  obsolete_1(rpc, safe_multi_server_call, A) when A =:= 2; A =:= 3 ->      {removed, {rpc, multi_server_call, A}}; +%% Removed in OTP 20. + +obsolete_1(erlang, hash, 2) -> +    {removed, {erlang, phash2, 2}, "20.0"}; + +%% not obsolete +  obsolete_1(_, _, _) ->      no. diff --git a/lib/stdlib/src/proc_lib.erl b/lib/stdlib/src/proc_lib.erl index 3dc1848550..363705b0f4 100644 --- a/lib/stdlib/src/proc_lib.erl +++ b/lib/stdlib/src/proc_lib.erl @@ -232,7 +232,7 @@ init_p(Parent, Ancestors, Fun) when is_function(Fun) ->  	Fun()      catch  	Class:Reason -> -	    exit_p(Class, Reason) +	    exit_p(Class, Reason, erlang:get_stacktrace())      end.  -spec init_p(pid(), [pid()], atom(), atom(), [term()]) -> term(). @@ -247,7 +247,7 @@ init_p_do_apply(M, F, A) ->  	apply(M, F, A)       catch  	Class:Reason -> -	    exit_p(Class, Reason) +	    exit_p(Class, Reason, erlang:get_stacktrace())      end.  -spec wake_up(atom(), atom(), [term()]) -> term(). @@ -257,22 +257,29 @@ wake_up(M, F, A) when is_atom(M), is_atom(F), is_list(A) ->  	apply(M, F, A)       catch  	Class:Reason -> -	    exit_p(Class, Reason) +	    exit_p(Class, Reason, erlang:get_stacktrace())      end. -exit_p(Class, Reason) -> +exit_p(Class, Reason, Stacktrace) ->      case get('$initial_call') of  	{M,F,A} when is_atom(M), is_atom(F), is_integer(A) ->  	    MFA = {M,F,make_dummy_args(A, [])},  	    crash_report(Class, Reason, MFA), -	    exit(Reason); +	    erlang:raise(exit, exit_reason(Class, Reason, Stacktrace), Stacktrace);  	_ ->  	    %% The process dictionary has been cleared or  	    %% possibly modified.  	    crash_report(Class, Reason, []), -	    exit(Reason) +	    erlang:raise(exit, exit_reason(Class, Reason, Stacktrace), Stacktrace)      end. +exit_reason(error, Reason, Stacktrace) -> +    {Reason, Stacktrace}; +exit_reason(exit, Reason, _Stacktrace) -> +    Reason; +exit_reason(throw, Reason, Stacktrace) -> +    {{nocatch, Reason}, Stacktrace}. +  -spec start(Module, Function, Args) -> Ret when        Module :: module(),        Function :: atom(), diff --git a/lib/stdlib/src/proplists.erl b/lib/stdlib/src/proplists.erl index 5356467b19..21de8c45c1 100644 --- a/lib/stdlib/src/proplists.erl +++ b/lib/stdlib/src/proplists.erl @@ -1,8 +1,3 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2001-2016. All Rights Reserved. -%%  %% Licensed under the Apache License, Version 2.0 (the "License");  %% you may not use this file except in compliance with the License.  %% You may obtain a copy of the License at @@ -15,14 +10,8 @@  %% See the License for the specific language governing permissions and  %% limitations under the License.  %% -%% %CopyrightEnd% -%% -%% ===================================================================== -%% Support functions for property lists -%% -%% Copyright (C) 2000-2003 Richard Carlsson -%% --------------------------------------------------------------------- -%% +%% @copyright 2000-2003 Richard Carlsson +%% @author Richard Carlsson <[email protected]>  %% @doc Support functions for property lists.  %%  %% <p>Property lists are ordinary lists containing entries in the form diff --git a/lib/stdlib/src/qlc_pt.erl b/lib/stdlib/src/qlc_pt.erl index 0db63b81f4..28221ea75f 100644 --- a/lib/stdlib/src/qlc_pt.erl +++ b/lib/stdlib/src/qlc_pt.erl @@ -41,6 +41,7 @@          }).  -record(state, {imp, +		overridden,                  maxargs,                  records,                  xwarnings = [], @@ -184,7 +185,9 @@ initiate(Forms0, Imported) ->      exclude_integers_from_unique_line_numbers(Forms0, NodeInfo),      ?DEBUG("node info0 ~p~n",             [lists:sort(ets:tab2list(NodeInfo))]), +    IsOverridden = set_up_overridden(Forms0),      State0 = #state{imp = Imported, +		    overridden = IsOverridden,                      maxargs = ?EVAL_MAX_NUM_OF_ARGS,                      records = record_attributes(Forms0),                      node_info = NodeInfo}, @@ -1519,36 +1522,35 @@ filter_info(FilterData, AllIVs, Dependencies, State) ->  %% to be placed after further generators (the docs states otherwise, but   %% this seems to be common practice).  filter_list(FilterData, Dependencies, State) -> -    RDs = State#state.records, -    sel_gf(FilterData, 1, Dependencies, RDs, [], []). +    sel_gf(FilterData, 1, Dependencies, State, [], []).  sel_gf([], _N, _Deps, _RDs, _Gens, _Gens1) ->      []; -sel_gf([{#qid{no = N}=Id,{fil,F}}=Fil | FData], N, Deps, RDs, Gens, Gens1) -> -    case erl_lint:is_guard_test(F, RDs) of +sel_gf([{#qid{no = N}=Id,{fil,F}}=Fil | FData], N, Deps, State, Gens, Gens1) -> +    case is_guard_test(F, State) of          true ->              {Id,GIds} = lists:keyfind(Id, 1, Deps),              case length(GIds) =< 1 of                  true ->                      case generators_in_scope(GIds, Gens1) of                          true -> -                            [Fil|sel_gf(FData, N+1, Deps, RDs, Gens, Gens1)]; +                            [Fil|sel_gf(FData, N+1, Deps, State, Gens, Gens1)];                          false -> -                            sel_gf(FData, N + 1, Deps, RDs, [], []) +                            sel_gf(FData, N + 1, Deps, State, [], [])                      end;                  false ->                      case generators_in_scope(GIds, Gens) of                          true -> -                            [Fil | sel_gf(FData, N + 1, Deps, RDs, Gens, [])]; +                            [Fil | sel_gf(FData, N + 1, Deps, State, Gens, [])];                          false -> -                            sel_gf(FData, N + 1, Deps, RDs, [], []) +                            sel_gf(FData, N + 1, Deps, State, [], [])                      end              end;          false -> -            sel_gf(FData, N + 1, Deps, RDs, [], []) +            sel_gf(FData, N + 1, Deps, State, [], [])      end; -sel_gf(FData, N, Deps, RDs, Gens, Gens1) -> -    sel_gf(FData, N + 1, Deps, RDs, [N | Gens], [N | Gens1]). +sel_gf(FData, N, Deps, State, Gens, Gens1) -> +    sel_gf(FData, N + 1, Deps, State, [N | Gens], [N | Gens1]).  generators_in_scope(GenIds, GenNumbers) ->      lists:all(fun(#qid{no=N}) -> lists:member(N, GenNumbers) end, GenIds). @@ -1870,7 +1872,8 @@ prep_expr(E, F, S, BF, Imported) ->  unify_column(Frame, Var, Col, BindFun, Imported) ->      A = anno0(), -    Call = {call,A,{atom,A,element},[{integer,A,Col}, {var,A,Var}]}, +    Call = {call,A,{remote,A,{atom,A,erlang},{atom,A,element}}, +	    [{integer,A,Col}, {var,A,Var}]},      element_calls(Call, Frame, BindFun, Imported).  %% cons_tuple is used for representing {V1, ..., Vi | TupleTail}. @@ -1880,6 +1883,8 @@ unify_column(Frame, Var, Col, BindFun, Imported) ->  %% about the size of the tuple is known.  element_calls({call,_,{remote,_,{atom,_,erlang},{atom,_,element}},                 [{integer,_,I},Term0]}, F0, BF, Imported) when I > 0 -> +    %% Note: erl_expand_records ensures that all calls to element/2 +    %% have an explicit "erlang:" prefix.      TupleTail = unique_var(),      VarsL = [unique_var() || _ <- lists:seq(1, I)],      Vars = VarsL ++ TupleTail, @@ -1887,10 +1892,6 @@ element_calls({call,_,{remote,_,{atom,_,erlang},{atom,_,element}},      VarI = lists:nth(I, VarsL),      {Term, F} = element_calls(Term0, F0, BF, Imported),      {VarI, unify('=:=', Tuple, Term, F, BF, Imported)}; -element_calls({call,L1,{atom,_,element}=E,As}, F0, BF, Imported) -> -    %% erl_expand_records should add "erlang:"... -    element_calls({call,L1,{remote,L1,{atom,L1,erlang},E}, As}, F0, BF, -                  Imported);  element_calls(T, F0, BF, Imported) when is_tuple(T) ->      {L, F} = element_calls(tuple_to_list(T), F0, BF, Imported),      {list_to_tuple(L), F}; @@ -2484,7 +2485,7 @@ filter(E, L, QIVs, S, RL, Fun, Go, GoI, IVs, State) ->      %% This is the "guard semantics" used in ordinary list      %% comprehension: if a filter looks like a guard test, it returns      %% 'false' rather than fails. -    Body = case erl_lint:is_guard_test(E, State#state.records) of +    Body = case is_guard_test(E, State) of                 true ->                      CT = {clause,L,[],[[E]],[{call,L,?V(Fun),NAsT}]},                     CF = {clause,L,[],[[?A(true)]],[{call,L,?V(Fun),NAsF}]}, @@ -2888,6 +2889,26 @@ family_list(L) ->  family(L) ->      sofs:relation_to_family(sofs:relation(L)). +is_guard_test(E, #state{records = RDs, overridden = IsOverridden}) -> +    erl_lint:is_guard_test(E, RDs, IsOverridden). + +%% In code that has been run through erl_expand_records, a guard +%% test will never contain calls without an explicit module +%% prefix.  Unfortunately, this module runs *some* of the code +%% through erl_expand_records, but not all of it. +%% +%% Therefore, we must set up our own list of local and imported functions +%% that will override a BIF with the same name. + +set_up_overridden(Forms) -> +    Locals = [{Name,Arity} || {function,_,Name,Arity,_} <- Forms], +    Imports0 = [Fs || {attribute,_,import,Fs} <- Forms], +    Imports1 = lists:flatten(Imports0), +    Imports2 = [Fs || {_,Fs} <- Imports1], +    Imports = lists:flatten(Imports2), +    Overridden = gb_sets:from_list(Imports ++ Locals), +    fun(FA) -> gb_sets:is_element(FA, Overridden) end. +  -ifdef(debug).  display_forms(Forms) ->      io:format("Forms ***~n"), diff --git a/lib/stdlib/src/rand.erl b/lib/stdlib/src/rand.erl index 93409d95df..1f457b9e0e 100644 --- a/lib/stdlib/src/rand.erl +++ b/lib/stdlib/src/rand.erl @@ -19,7 +19,7 @@  %%  %% =====================================================================  %% Multiple PRNG module for Erlang/OTP -%% Copyright (c) 2015 Kenji Rikitake +%% Copyright (c) 2015-2016 Kenji Rikitake  %% =====================================================================  -module(rand). @@ -27,11 +27,14 @@  -export([seed_s/1, seed_s/2, seed/1, seed/2,  	 export_seed/0, export_seed_s/1,           uniform/0, uniform/1, uniform_s/1, uniform_s/2, +         jump/0, jump/1,  	 normal/0, normal_s/1  	]).  -compile({inline, [exs64_next/1, exsplus_next/1, +		   exsplus_jump/1,  		   exs1024_next/1, exs1024_calc/2, +		   exs1024_jump/1,  		   get_52/1, normal_kiwi/1]}).  -define(DEFAULT_ALG_HANDLER, exsplus). @@ -48,7 +51,8 @@  			 max       := integer(),  			 next      := fun(),  			 uniform   := fun(), -			 uniform_n := fun()}. +			 uniform_n := fun(), +			 jump      := fun()}.  %% Internal state  -opaque state() :: {alg_handler(), alg_seed()}. @@ -79,9 +83,7 @@ export_seed_s({#{type:=Alg}, Seed}) -> {Alg, Seed}.  -spec seed(AlgOrExpState::alg() | export_state()) -> state().  seed(Alg) -> -    R = seed_s(Alg), -    _ = seed_put(R), -    R. +    seed_put(seed_s(Alg)).  -spec seed_s(AlgOrExpState::alg() | export_state()) -> state().  seed_s(Alg) when is_atom(Alg) -> @@ -97,9 +99,7 @@ seed_s({Alg0, Seed}) ->  -spec seed(Alg :: alg(), {integer(), integer(), integer()}) -> state().  seed(Alg0, S0) -> -    State = seed_s(Alg0, S0), -    _ = seed_put(State), -    State. +    seed_put(seed_s(Alg0, S0)).  -spec seed_s(Alg :: alg(), {integer(), integer(), integer()}) -> state().  seed_s(Alg0, S0 = {_, _, _}) -> @@ -150,6 +150,25 @@ uniform_s(N, State0 = {#{uniform:=Uniform}, _})      {F, State} = Uniform(State0),      {trunc(F * N) + 1, State}. +%% jump/1: given a state, jump/1 +%% returns a new state which is equivalent to that +%% after a large number of call defined for each algorithm. +%% The large number is algorithm dependent. + +-spec jump(state()) -> NewS :: state(). +jump(State = {#{jump:=Jump}, _}) -> +    Jump(State). + +%% jump/0: read the internal state and +%% apply the jump function for the state as in jump/1 +%% and write back the new value to the internal state, +%% then returns the new value. + +-spec jump() -> NewS :: state(). + +jump() -> +    seed_put(jump(seed_get())). +  %% normal/0: returns a random float with standard normal distribution  %% updating the state in the process dictionary. @@ -192,9 +211,10 @@ normal_s(State0) ->  -type uint64() :: 0..16#ffffffffffffffff.  -type uint58() :: 0..16#03ffffffffffffff. --spec seed_put(state()) -> undefined | state(). +-spec seed_put(state()) -> state().  seed_put(Seed) -> -    put(?SEED_DICT, Seed). +    put(?SEED_DICT, Seed), +    Seed.  seed_get() ->      case get(?SEED_DICT) of @@ -205,15 +225,18 @@ seed_get() ->  %% Setup alg record  mk_alg(exs64) ->      {#{type=>exs64, max=>?UINT64MASK, next=>fun exs64_next/1, -       uniform=>fun exs64_uniform/1, uniform_n=>fun exs64_uniform/2}, +       uniform=>fun exs64_uniform/1, uniform_n=>fun exs64_uniform/2, +       jump=>fun exs64_jump/1},       fun exs64_seed/1};  mk_alg(exsplus) ->      {#{type=>exsplus, max=>?UINT58MASK, next=>fun exsplus_next/1, -       uniform=>fun exsplus_uniform/1, uniform_n=>fun exsplus_uniform/2}, +       uniform=>fun exsplus_uniform/1, uniform_n=>fun exsplus_uniform/2, +       jump=>fun exsplus_jump/1},       fun exsplus_seed/1};  mk_alg(exs1024) ->      {#{type=>exs1024, max=>?UINT64MASK, next=>fun exs1024_next/1, -       uniform=>fun exs1024_uniform/1, uniform_n=>fun exs1024_uniform/2}, +       uniform=>fun exs1024_uniform/1, uniform_n=>fun exs1024_uniform/2, +       jump=>fun exs1024_jump/1},       fun exs1024_seed/1}.  %% ===================================================================== @@ -246,6 +269,9 @@ exs64_uniform(Max, {Alg, R}) ->      {V, R1} = exs64_next(R),      {(V rem Max) + 1, {Alg, R1}}. +exs64_jump(_) -> +    erlang:error(not_implemented). +  %% =====================================================================  %% exsplus PRNG: Xorshift116+  %% Algorithm by Sebastiano Vigna @@ -283,6 +309,40 @@ exsplus_uniform(Max, {Alg, R}) ->      {V, R1} = exsplus_next(R),      {(V rem Max) + 1, {Alg, R1}}. +%% This is the jump function for the exsplus generator, equivalent +%% to 2^64 calls to next/1; it can be used to generate 2^52 +%% non-overlapping subsequences for parallel computations. +%% Note: the jump function takes 116 times of the execution time of +%% next/1. + +%% -define(JUMPCONST, 16#000d174a83e17de2302f8ea6bc32c797). +%% split into 58-bit chunks +%% and two iterative executions + +-define(JUMPCONST1, 16#02f8ea6bc32c797). +-define(JUMPCONST2, 16#345d2a0f85f788c). +-define(JUMPELEMLEN, 58). + +-dialyzer({no_improper_lists, exsplus_jump/1}). +-spec exsplus_jump(state()) -> state(). +exsplus_jump({Alg, S}) -> +    {S1, AS1} = exsplus_jump(S, [0|0], ?JUMPCONST1, ?JUMPELEMLEN), +    {_,  AS2} = exsplus_jump(S1, AS1,  ?JUMPCONST2, ?JUMPELEMLEN), +    {Alg, AS2}. + +-dialyzer({no_improper_lists, exsplus_jump/4}). +exsplus_jump(S, AS, _, 0) -> +    {S, AS}; +exsplus_jump(S, [AS0|AS1], J, N) -> +    {_, NS} = exsplus_next(S), +    case (J band 1) of +        1 -> +            [S0|S1] = S, +            exsplus_jump(NS, [(AS0 bxor S0)|(AS1 bxor S1)], J bsr 1, N-1); +        0 -> +            exsplus_jump(NS, [AS0|AS1], J bsr 1, N-1) +    end. +  %% =====================================================================  %% exs1024 PRNG: Xorshift1024*  %% Algorithm by Sebastiano Vigna @@ -340,6 +400,60 @@ exs1024_uniform(Max, {Alg, R}) ->      {V, R1} = exs1024_next(R),      {(V rem Max) + 1, {Alg, R1}}. +%% This is the jump function for the exs1024 generator, equivalent +%% to 2^512 calls to next(); it can be used to generate 2^512 +%% non-overlapping subsequences for parallel computations. +%% Note: the jump function takes ~2000 times of the execution time of +%% next/1. + +%% Jump constant here split into 58 bits for speed +-define(JUMPCONSTHEAD, 16#00242f96eca9c41d). +-define(JUMPCONSTTAIL, +        [16#0196e1ddbe5a1561, +         16#0239f070b5837a3c, +         16#03f393cc68796cd2, +         16#0248316f404489af, +         16#039a30088bffbac2, +         16#02fea70dc2d9891f, +         16#032ae0d9644caec4, +         16#0313aac17d8efa43, +         16#02f132e055642626, +         16#01ee975283d71c93, +         16#00552321b06f5501, +         16#00c41d10a1e6a569, +         16#019158ecf8aa1e44, +         16#004e9fc949d0b5fc, +         16#0363da172811fdda, +         16#030e38c3b99181f2, +         16#0000000a118038fc]). +-define(JUMPTOTALLEN, 1024). +-define(RINGLEN, 16). + +-spec exs1024_jump(state()) -> state(). + +exs1024_jump({Alg, {L, RL}}) -> +    P = length(RL), +    AS = exs1024_jump({L, RL}, +         [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], +         ?JUMPCONSTTAIL, ?JUMPCONSTHEAD, ?JUMPELEMLEN, ?JUMPTOTALLEN), +    {ASL, ASR} = lists:split(?RINGLEN - P, AS), +    {Alg, {ASL, lists:reverse(ASR)}}. + +exs1024_jump(_, AS, _, _, _, 0) -> +    AS; +exs1024_jump(S, AS, [H|T], _, 0, TN) -> +    exs1024_jump(S, AS, T, H, ?JUMPELEMLEN, TN); +exs1024_jump({L, RL}, AS, JL, J, N, TN) -> +    {_, NS} = exs1024_next({L, RL}), +    case (J band 1) of +        1 -> +            AS2 = lists:zipwith(fun(X, Y) -> X bxor Y end, +                        AS, L ++ lists:reverse(RL)), +            exs1024_jump(NS, AS2, JL, J bsr 1, N-1, TN-1); +        0 -> +            exs1024_jump(NS, AS, JL, J bsr 1, N-1, TN-1) +    end. +  %% =====================================================================  %% Ziggurat cont  %% ===================================================================== diff --git a/lib/stdlib/src/sets.erl b/lib/stdlib/src/sets.erl index 3e70450320..c65a13b22e 100644 --- a/lib/stdlib/src/sets.erl +++ b/lib/stdlib/src/sets.erl @@ -128,14 +128,14 @@ is_element(E, S) ->        Set2 :: set(Element).  add_element(E, S0) ->      Slot = get_slot(S0, E), -    {S1,Ic} = on_bucket(fun (B0) -> add_bkt_el(E, B0, B0) end, S0, Slot), -    maybe_expand(S1, Ic). - --spec add_bkt_el(T, [T], [T]) -> {[T], 0 | 1}. -add_bkt_el(E, [E|_], Bkt) -> {Bkt,0}; -add_bkt_el(E, [_|B], Bkt) -> -    add_bkt_el(E, B, Bkt); -add_bkt_el(E, [], Bkt) -> {[E|Bkt],1}. +    Bkt = get_bucket(S0, Slot), +    case lists:member(E, Bkt) of +        true -> +            S0; +        false -> +            S1 = update_bucket(S0, Slot, [E | Bkt]), +            maybe_expand(S1) +    end.  %% del_element(Element, Set) -> Set.  %%  Return Set but with Element removed. @@ -144,15 +144,28 @@ add_bkt_el(E, [], Bkt) -> {[E|Bkt],1}.        Set2 :: set(Element).  del_element(E, S0) ->      Slot = get_slot(S0, E), -    {S1,Dc} = on_bucket(fun (B0) -> del_bkt_el(E, B0) end, S0, Slot), -    maybe_contract(S1, Dc). +    Bkt = get_bucket(S0, Slot), +    case lists:member(E, Bkt) of +        false -> +            S0; +        true -> +            S1 = update_bucket(S0, Slot, lists:delete(E, Bkt)), +            maybe_contract(S1, 1) +    end. --spec del_bkt_el(T, [T]) -> {[T], 0 | 1}. -del_bkt_el(E, [E|Bkt]) -> {Bkt,1}; -del_bkt_el(E, [Other|Bkt0]) -> -    {Bkt1,Dc} = del_bkt_el(E, Bkt0), -    {[Other|Bkt1],Dc}; -del_bkt_el(_, []) -> {[],0}. +%% update_bucket(Set, Slot, NewBucket) -> UpdatedSet. +%%  Replace bucket in Slot by NewBucket +-spec update_bucket(Set1, Slot, Bkt) -> Set2 when +      Set1 :: set(Element), +      Set2 :: set(Element), +      Slot :: non_neg_integer(), +      Bkt :: [Element]. +update_bucket(Set, Slot, NewBucket) -> +    SegI = ((Slot-1) div ?seg_size) + 1, +    BktI = ((Slot-1) rem ?seg_size) + 1, +    Segs = Set#set.segs, +    Seg = element(SegI, Segs), +    Set#set{segs = setelement(SegI, Segs, setelement(BktI, Seg, NewBucket))}.  %% union(Set1, Set2) -> Set  %%  Return the union of Set1 and Set2. @@ -272,19 +285,6 @@ get_slot(T, Key) ->  -spec get_bucket(set(), non_neg_integer()) -> term().  get_bucket(T, Slot) -> get_bucket_s(T#set.segs, Slot). -%% on_bucket(Fun, Hashdb, Slot) -> {NewHashDb,Result}. -%%  Apply Fun to the bucket in Slot and replace the returned bucket. --spec on_bucket(fun((_) -> {[_], 0 | 1}), set(E), non_neg_integer()) -> -	  {set(E), 0 | 1}. -on_bucket(F, T, Slot) -> -    SegI = ((Slot-1) div ?seg_size) + 1, -    BktI = ((Slot-1) rem ?seg_size) + 1, -    Segs = T#set.segs, -    Seg = element(SegI, Segs), -    B0 = element(BktI, Seg), -    {B1, Res} = F(B0),				%Op on the bucket. -    {T#set{segs = setelement(SegI, Segs, setelement(BktI, Seg, B1))},Res}. -  %% fold_set(Fun, Acc, Dictionary) -> Dictionary.  %% filter_set(Fun, Dictionary) -> Dictionary. @@ -349,8 +349,8 @@ put_bucket_s(Segs, Slot, Bkt) ->      Seg = setelement(BktI, element(SegI, Segs), Bkt),      setelement(SegI, Segs, Seg). --spec maybe_expand(set(E), 0 | 1) -> set(E). -maybe_expand(T0, Ic) when T0#set.size + Ic > T0#set.exp_size -> +-spec maybe_expand(set(E)) -> set(E). +maybe_expand(T0) when T0#set.size + 1 > T0#set.exp_size ->      T = maybe_expand_segs(T0),			%Do we need more segments.      N = T#set.n + 1,				%Next slot to expand into      Segs0 = T#set.segs, @@ -360,12 +360,12 @@ maybe_expand(T0, Ic) when T0#set.size + Ic > T0#set.exp_size ->      {B1,B2} = rehash(B, Slot1, Slot2, T#set.maxn),      Segs1 = put_bucket_s(Segs0, Slot1, B1),      Segs2 = put_bucket_s(Segs1, Slot2, B2), -    T#set{size = T#set.size + Ic, +    T#set{size = T#set.size + 1,  	  n = N,  	  exp_size = N * ?expand_load,  	  con_size = N * ?contract_load,  	  segs = Segs2}; -maybe_expand(T, Ic) -> T#set{size = T#set.size + Ic}. +maybe_expand(T) -> T#set{size = T#set.size + 1}.  -spec maybe_expand_segs(set(E)) -> set(E).  maybe_expand_segs(T) when T#set.n =:= T#set.maxn -> diff --git a/lib/stdlib/src/shell_default.erl b/lib/stdlib/src/shell_default.erl index 6947cf181b..cd63ab28b5 100644 --- a/lib/stdlib/src/shell_default.erl +++ b/lib/stdlib/src/shell_default.erl @@ -23,7 +23,7 @@  -module(shell_default). --export([help/0,lc/1,c/1,c/2,nc/1,nl/1,l/1,i/0,pid/3,i/3,m/0,m/1, +-export([help/0,lc/1,c/1,c/2,nc/1,nl/1,l/1,i/0,pid/3,i/3,m/0,m/1,lm/0,mm/0,           memory/0,memory/1,uptime/0,  	 erlangrc/1,bi/1, regs/0, flush/0,pwd/0,ls/0,ls/1,cd/1,            y/1, y/2, @@ -83,6 +83,8 @@ ls()            -> c:ls().  ls(S)           -> c:ls(S).  m() 		-> c:m().  m(Mod) 		-> c:m(Mod). +lm()            -> c:lm(). +mm()            -> c:mm().  memory()        -> c:memory().  memory(Type)    -> c:memory(Type).  nc(X)     	-> c:nc(X). diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src index 09176d2ca0..82ab484ea6 100644 --- a/lib/stdlib/src/stdlib.app.src +++ b/lib/stdlib/src/stdlib.app.src @@ -31,7 +31,6 @@               dets_server,  	     dets_sup,  	     dets_utils, -	     dets_v8,  	     dets_v9,  	     dict,  	     digraph, @@ -106,7 +105,7 @@                 dets]},    {applications, [kernel]},    {env, []}, -  {runtime_dependencies, ["sasl-3.0","kernel-5.0","erts-8.0","crypto-3.3", +  {runtime_dependencies, ["sasl-3.0","kernel-5.0","erts-9.0","crypto-3.3",  			  "compiler-5.0"]}  ]}. diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src index e917b7ea1f..979161fef7 100644 --- a/lib/stdlib/src/stdlib.appup.src +++ b/lib/stdlib/src/stdlib.appup.src @@ -18,9 +18,7 @@  %% %CopyrightEnd%  {"%VSN%",   %% Up from - max one major revision back - [{<<"3\\.[0-1](\\.[0-9]+)*">>,[restart_new_emulator]},   % OTP-19.* -  {<<"2\\.[5-8](\\.[0-9]+)*">>,[restart_new_emulator]}],  % OTP-18.* + [{<<"3\\.[0-1](\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-19.*   %% Down to - max one major revision back - [{<<"3\\.[0-1](\\.[0-9]+)*">>,[restart_new_emulator]},  % OTP-19.* -  {<<"2\\.[5-8](\\.[0-9]+)*">>,[restart_new_emulator]}]  % OTP-18.* + [{<<"3\\.[0-1](\\.[0-9]+)*">>,[restart_new_emulator]}]  % OTP-19.*  }. diff --git a/lib/stdlib/src/timer.erl b/lib/stdlib/src/timer.erl index ca868627a9..df10790ea0 100644 --- a/lib/stdlib/src/timer.erl +++ b/lib/stdlib/src/timer.erl @@ -165,7 +165,7 @@ tc(F) ->      T1 = erlang:monotonic_time(),      Val = F(),      T2 = erlang:monotonic_time(), -    Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds), +    Time = erlang:convert_time_unit(T2 - T1, native, microsecond),      {Time, Val}.  %% @@ -180,7 +180,7 @@ tc(F, A) ->      T1 = erlang:monotonic_time(),      Val = apply(F, A),      T2 = erlang:monotonic_time(), -    Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds), +    Time = erlang:convert_time_unit(T2 - T1, native, microsecond),      {Time, Val}.  %% @@ -196,7 +196,7 @@ tc(M, F, A) ->      T1 = erlang:monotonic_time(),      Val = apply(M, F, A),      T2 = erlang:monotonic_time(), -    Time = erlang:convert_time_unit(T2 - T1, native, micro_seconds), +    Time = erlang:convert_time_unit(T2 - T1, native, microsecond),      {Time, Val}.  %%  | 
