diff options
Diffstat (limited to 'lib')
47 files changed, 2372 insertions, 1493 deletions
diff --git a/lib/common_test/doc/src/basics_chapter.xml b/lib/common_test/doc/src/basics_chapter.xml index 95599ca1f1..899a52fa31 100644 --- a/lib/common_test/doc/src/basics_chapter.xml +++ b/lib/common_test/doc/src/basics_chapter.xml @@ -125,7 +125,7 @@ The test case is the smallest unit that the <c>Common Test</c> test server deals with. </p> <p> - Subsets of test cases, called test case groups, can also be defined. A test case + Sets of test cases, called test case groups, can also be defined. A test case group can have execution properties associated with it. Execution properties specify if the test cases in the group are to be executed in random order, in parallel, or in sequence, and if the execution of the group diff --git a/lib/compiler/src/beam_jump.erl b/lib/compiler/src/beam_jump.erl index 6f50bfdb9c..74f80ca70e 100644 --- a/lib/compiler/src/beam_jump.erl +++ b/lib/compiler/src/beam_jump.erl @@ -179,8 +179,9 @@ function({function,Name,Arity,CLabel,Asm0}, Lc0) -> eliminate_moves(Is) -> eliminate_moves(Is, #{}, []). -eliminate_moves([{select,select_val,Reg,_,List}=I|Is], D0, Acc) -> - D = update_value_dict(List, Reg, D0), +eliminate_moves([{select,select_val,Reg,{f,Fail},List}=I|Is], D0, Acc) -> + D1 = add_unsafe_label(Fail, D0), + D = update_value_dict(List, Reg, D1), eliminate_moves(Is, D, [I|Acc]); eliminate_moves([{test,is_eq_exact,_,[Reg,Val]}=I, {block,BlkIs0}|Is], D0, Acc) -> @@ -229,6 +230,9 @@ update_value_dict([Lit,{f,Lbl}|T], Reg, D0) -> update_value_dict(T, Reg, D); update_value_dict([], _, D) -> D. +add_unsafe_label(L, D) -> + D#{L=>unsafe}. + update_unsafe_labels(I, D) -> Ls = instr_labels(I), update_unsafe_labels_1(Ls, D). diff --git a/lib/compiler/src/beam_kernel_to_ssa.erl b/lib/compiler/src/beam_kernel_to_ssa.erl index 410bafe0bb..df95749fb3 100644 --- a/lib/compiler/src/beam_kernel_to_ssa.erl +++ b/lib/compiler/src/beam_kernel_to_ssa.erl @@ -327,7 +327,7 @@ select_bin_seg(#k_val_clause{val=#k_bin_seg{size=Size,unit=U,type=T, {Mis,St1} = select_extract_bin(Next, Size, U, T, Fs, Fail, Ctx, LineAnno, St0), {Extracted,St2} = new_ssa_var(Seg#k_var.name, St1), - {Bis,St} = bin_match_cg(Size, B, Fail, St2), + {Bis,St} = match_cg(B, Fail, St2), BsGet = #b_set{op=bs_extract,dst=Extracted,args=[ssa_arg(Next, St)]}, Is = Mis ++ [BsGet] ++ Bis, {Is,St}; @@ -362,14 +362,6 @@ select_bin_seg(#k_val_clause{val=#k_bin_int{size=Sz,unit=U,flags=Fs, end, {Is,St}. -bin_match_cg(#k_atom{val=all}, B0, Fail, St) -> - #k_select{types=Types} = B0, - [#k_type_clause{type=k_bin_end,values=Values}] = Types, - [#k_val_clause{val=#k_bin_end{},body=B}] = Values, - match_cg(B, Fail, St); -bin_match_cg(_, B, Fail, St) -> - match_cg(B, Fail, St). - get_context(#k_var{}=Var, St) -> ssa_arg(Var, St). diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl index 2cca9ebadf..bb43a550ae 100644 --- a/lib/compiler/src/beam_ssa_dead.erl +++ b/lib/compiler/src/beam_ssa_dead.erl @@ -27,7 +27,8 @@ -export([opt/1]). -include("beam_ssa.hrl"). --import(lists, [append/1,last/1,member/2,takewhile/2,reverse/1]). +-import(lists, [append/1,keymember/3,last/1,member/2, + takewhile/2,reverse/1]). -type used_vars() :: #{beam_ssa:label():=ordsets:ordset(beam_ssa:var_name())}. @@ -58,7 +59,7 @@ opt(Linear) -> Blocks0 = maps:from_list(Linear), St0 = #st{bs=Blocks0,us=Used,skippable=Skippable}, St = shortcut_opt(St0), - #st{bs=Blocks} = combine_eqs(St), + #st{bs=Blocks} = combine_eqs(St#st{us=#{}}), beam_ssa:linearize(Blocks). %%% @@ -87,13 +88,22 @@ shortcut_opt(#st{bs=Blocks}=St) -> %% opportunities for optimizations compared to post order. (Based on %% running scripts/diffable with both PO and RPO and looking at %% the diff.) + %% + %% Unfortunately, processing the blocks in reverse post order + %% potentially makes the time complexity quadratic or even cubic if + %% the ordset of unset variables grows large, instead of + %% linear for post order processing. We try to still get reasonable + %% compilation times by optimizations that will keep the constant + %% factor as low as possible, and we try to avoid the cubic time + %% complexity by trying to keep the set of unset variables as small + %% as possible. + Ls = beam_ssa:rpo(Blocks), - shortcut_opt(Ls, #{from=>0}, St). + shortcut_opt(Ls, #{}, St). -shortcut_opt([L|Ls], Bs0, #st{bs=Blocks0}=St) -> +shortcut_opt([L|Ls], Bs, #st{bs=Blocks0}=St) -> #b_blk{is=Is,last=Last0} = Blk0 = get_block(L, St), - Bs = Bs0#{from:=L}, - case shortcut_terminator(Last0, Is, Bs, St) of + case shortcut_terminator(Last0, Is, L, Bs, St) of Last0 -> %% No change. No need to update the block. shortcut_opt(Ls, Bs, St); @@ -107,17 +117,17 @@ shortcut_opt([L|Ls], Bs0, #st{bs=Blocks0}=St) -> shortcut_opt([], _, St) -> St. shortcut_terminator(#b_br{bool=#b_literal{val=true},succ=Succ0}, - _Is, Bs, St0) -> + _Is, From, Bs, St0) -> St = St0#st{rel_op=none}, - shortcut(Succ0, Bs, St); + shortcut(Succ0, From, Bs, St); shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br, - Is, Bs, St0) -> + Is, From, Bs, St0) -> St = St0#st{target=one_way}, RelOp = get_rel_op(Bool, Is), SuccBs = bind_var(Bool, #b_literal{val=true}, Bs), - BrSucc = shortcut(Succ0, SuccBs, St#st{rel_op=RelOp}), + BrSucc = shortcut(Succ0, From, SuccBs, St#st{rel_op=RelOp}), FailBs = bind_var(Bool, #b_literal{val=false}, Bs), - BrFail = shortcut(Fail0, FailBs, St#st{rel_op=invert_op(RelOp)}), + BrFail = shortcut(Fail0, From, FailBs, St#st{rel_op=invert_op(RelOp)}), case {BrSucc,BrFail} of {#b_br{bool=#b_literal{val=true},succ=Succ}, #b_br{bool=#b_literal{val=true},succ=Fail}} @@ -128,25 +138,25 @@ shortcut_terminator(#b_br{bool=#b_var{}=Bool,succ=Succ0,fail=Fail0}=Br, %% No change. Br end; -shortcut_terminator(#b_switch{arg=Bool,list=List0}=Sw, _Is, Bs, St) -> - List = shortcut_switch(List0, Bool, Bs, St), +shortcut_terminator(#b_switch{arg=Bool,list=List0}=Sw, _Is, From, Bs, St) -> + List = shortcut_switch(List0, Bool, From, Bs, St), beam_ssa:normalize(Sw#b_switch{list=List}); -shortcut_terminator(Last, _Is, _Bs, _St) -> +shortcut_terminator(Last, _Is, _Bs, _From, _St) -> Last. -shortcut_switch([{Lit,L0}|T], Bool, Bs, St0) -> +shortcut_switch([{Lit,L0}|T], Bool, From, Bs, St0) -> RelOp = {'=:=',Bool,Lit}, St = St0#st{rel_op=RelOp}, #b_br{bool=#b_literal{val=true},succ=L} = - shortcut(L0, bind_var(Bool, Lit, Bs), St#st{target=one_way}), - [{Lit,L}|shortcut_switch(T, Bool, Bs, St0)]; -shortcut_switch([], _, _, _) -> []. + shortcut(L0, From, bind_var(Bool, Lit, Bs), St#st{target=one_way}), + [{Lit,L}|shortcut_switch(T, Bool, From, Bs, St0)]; +shortcut_switch([], _, _, _, _) -> []. -shortcut(L, Bs, St) -> - shortcut_1(L, Bs, ordsets:new(), St). +shortcut(L, From, Bs, St) -> + shortcut_1(L, From, Bs, ordsets:new(), St). -shortcut_1(L, Bs0, UnsetVars0, St) -> - case shortcut_2(L, Bs0, UnsetVars0, St) of +shortcut_1(L, From, Bs0, UnsetVars0, St) -> + case shortcut_2(L, From, Bs0, UnsetVars0, St) of none -> %% No more shortcuts found. Package up the previous %% label in an unconditional branch. @@ -156,13 +166,13 @@ shortcut_1(L, Bs0, UnsetVars0, St) -> Br; {#b_br{bool=#b_literal{val=true},succ=Succ},Bs,UnsetVars} -> %% This is a safe `br`, but try to find a better one. - shortcut_1(Succ, Bs#{from:=L}, UnsetVars, St) + shortcut_1(Succ, L, Bs, UnsetVars, St) end. %% Try to shortcut this block, branching to a successor. -shortcut_2(L, Bs0, UnsetVars0, St) -> +shortcut_2(L, From, Bs0, UnsetVars0, St) -> #b_blk{is=Is,last=Last} = get_block(L, St), - case eval_is(Is, Bs0, St) of + case eval_is(Is, From, Bs0, St) of none -> %% It is not safe to avoid this block because it %% has instructions with potential side effects. @@ -181,139 +191,147 @@ shortcut_2(L, Bs0, UnsetVars0, St) -> %% We have a potentially suitable br. %% Now update the set of variables that will never %% be set if this block will be skipped. - SetInThisBlock = [V || #b_set{dst=V} <- Is], - UnsetVars = update_unset_vars(L, Br, SetInThisBlock, - UnsetVars0, St), - - %% Continue checking whether this br is suitable. - shortcut_3(Br, Bs#{from:=L}, UnsetVars, St) + case update_unset_vars(L, Is, Br, UnsetVars0, St) of + unsafe -> + %% It is unsafe to use this br, + %% because it refers to a variable defined + %% in this block. + shortcut_unsafe_br(Br, L, Bs, UnsetVars0, St); + UnsetVars -> + %% Continue checking whether this br is + %% suitable. + shortcut_test_br(Br, L, Bs, UnsetVars, St) + end end end. -shortcut_3(Br, Bs, UnsetVars, #st{target=Target}=St) -> +shortcut_test_br(Br, From, Bs, UnsetVars, St) -> case is_br_safe(UnsetVars, Br, St) of false -> - %% Branching using this `br` is unsafe, either because it - %% is an unconditional branch to a phi node, or because - %% one or more of the variables that are not set will be - %% used. Try to follow branches of this `br`, to find a - %% safe `br`. - case Br of - #b_br{bool=#b_literal{val=true},succ=L} -> - case Target of - L -> - %% We have reached the forced target, and it - %% is unsafe. Give up. - none; - _ -> - %% Try following this branch to see whether it - %% leads to a safe `br`. - shortcut_2(L, Bs, UnsetVars, St) - end; - #b_br{bool=#b_var{},succ=Succ,fail=Fail} -> - case {Succ,Fail} of - {L,Target} -> - %% The failure label is the forced target. - %% Try following the success label to see - %% whether it also ultimately ends up at the - %% forced target. - shortcut_2(L, Bs, UnsetVars, St); - {Target,L} -> - %% The success label is the forced target. - %% Try following the failure label to see - %% whether it also ultimately ends up at the - %% forced target. - shortcut_2(L, Bs, UnsetVars, St); - {_,_} -> - case Target of - any -> - %% This two-way branch is unsafe. Try reducing - %% it to a one-way branch. - shortcut_two_way(Br, Bs, UnsetVars, St); - one_way -> - %% This two-way branch is unsafe. Try reducing - %% it to a one-way branch. - shortcut_two_way(Br, Bs, UnsetVars, St); - _ when is_integer(Target) -> - %% This two-way branch is unsafe, and - %% there already is a forced target. - %% Give up. - none - end - end - end; + shortcut_unsafe_br(Br, From, Bs, UnsetVars, St); true -> - %% This `br` instruction is safe. It does not - %% branch to a phi node, and all variables that - %% will be used are guaranteed to be defined. - case Br of - #b_br{bool=#b_literal{val=true},succ=L} -> - %% This is a one-way branch. + shortcut_safe_br(Br, From, Bs, UnsetVars, St) + end. + +shortcut_unsafe_br(Br, From, Bs, UnsetVars, #st{target=Target}=St) -> + %% Branching using this `br` is unsafe, either because it + %% is an unconditional branch to a phi node, or because + %% one or more of the variables that are not set will be + %% used. Try to follow branches of this `br`, to find a + %% safe `br`. + case Br of + #b_br{bool=#b_literal{val=true},succ=L} -> + case Target of + L -> + %% We have reached the forced target, and it + %% is unsafe. Give up. + none; + _ -> + %% Try following this branch to see whether it + %% leads to a safe `br`. + shortcut_2(L, From, Bs, UnsetVars, St) + end; + #b_br{bool=#b_var{},succ=Succ,fail=Fail} -> + case {Succ,Fail} of + {L,Target} -> + %% The failure label is the forced target. + %% Try following the success label to see + %% whether it also ultimately ends up at the + %% forced target. + shortcut_2(L, From, Bs, UnsetVars, St); + {Target,L} -> + %% The success label is the forced target. + %% Try following the failure label to see + %% whether it also ultimately ends up at the + %% forced target. + shortcut_2(L, From, Bs, UnsetVars, St); + {_,_} -> case Target of any -> - %% No forced target. Success! - {Br,Bs,UnsetVars}; + %% This two-way branch is unsafe. Try + %% reducing it to a one-way branch. + shortcut_two_way(Br, From, Bs, UnsetVars, St); one_way -> - %% The target must be a one-way branch, which this - %% `br` is. Success! - {Br,Bs,UnsetVars}; - L when is_integer(Target) -> - %% The forced target is L. Success! - {Br,Bs,UnsetVars}; + %% This two-way branch is unsafe. Try + %% reducing it to a one-way branch. + shortcut_two_way(Br, From, Bs, UnsetVars, St); _ when is_integer(Target) -> - %% Wrong forced target. Try following this branch - %% to see if it ultimately ends up at the forced - %% target. - shortcut_2(L, Bs, UnsetVars, St) - end; - #b_br{bool=#b_var{}} -> - %% This is a two-way branch. - if - Target =:= any; Target =:= one_way -> - %% No specific forced target. Try to reduce the - %% two-way branch to an one-way branch. - case shortcut_two_way(Br, Bs, UnsetVars, St) of - none when Target =:= any -> - %% This `br` can't be reduced to a one-way - %% branch. Return the `br` as-is. - {Br,Bs,UnsetVars}; - none when Target =:= one_way -> - %% This `br` can't be reduced to a one-way - %% branch. The caller wants a one-way branch. - %% Give up. - none; - {_,_,_}=Res -> - %% This `br` was successfully reduced to a - %% one-way branch. - Res - end; - is_integer(Target) -> - %% There is a forced target, which can't - %% be reached because this `br` is a two-way - %% branch. Give up. + %% This two-way branch is unsafe, and + %% there already is a forced target. + %% Give up. none end end end. -update_unset_vars(L, Br, SetInThisBlock, UnsetVars, #st{skippable=Skippable}) -> +shortcut_safe_br(Br, From, Bs, UnsetVars, #st{target=Target}=St) -> + %% This `br` instruction is safe. It does not branch to a phi + %% node, and all variables that will be used are guaranteed to be + %% defined. + case Br of + #b_br{bool=#b_literal{val=true},succ=L} -> + %% This is a one-way branch. + case Target of + any -> + %% No forced target. Success! + {Br,Bs,UnsetVars}; + one_way -> + %% The target must be a one-way branch, which this + %% `br` is. Success! + {Br,Bs,UnsetVars}; + L when is_integer(Target) -> + %% The forced target is L. Success! + {Br,Bs,UnsetVars}; + _ when is_integer(Target) -> + %% Wrong forced target. Try following this branch + %% to see if it ultimately ends up at the forced + %% target. + shortcut_2(L, From, Bs, UnsetVars, St) + end; + #b_br{bool=#b_var{}} -> + %% This is a two-way branch. + if + Target =:= any; Target =:= one_way -> + %% No specific forced target. Try to reduce the + %% two-way branch to an one-way branch. + case shortcut_two_way(Br, From, Bs, UnsetVars, St) of + none when Target =:= any -> + %% This `br` can't be reduced to a one-way + %% branch. Return the `br` as-is. + {Br,Bs,UnsetVars}; + none when Target =:= one_way -> + %% This `br` can't be reduced to a one-way + %% branch. The caller wants a one-way + %% branch. Give up. + none; + {_,_,_}=Res -> + %% This `br` was successfully reduced to a + %% one-way branch. + Res + end; + is_integer(Target) -> + %% There is a forced target, which can't + %% be reached because this `br` is a two-way + %% branch. Give up. + none + end + end. + +update_unset_vars(L, Is, Br, UnsetVars, #st{skippable=Skippable}) -> case is_map_key(L, Skippable) of true -> %% None of the variables used in this block are used in - %% the successors. We can speed up compilation by avoiding - %% adding variables to the UnsetVars if the presence of - %% those variable would not change the outcome of the - %% tests in is_br_safe/2. + %% the successors. Thus, there is no need to add the + %% variables to the set of unset variables. case Br of - #b_br{bool=Bool} -> - case member(Bool, SetInThisBlock) of + #b_br{bool=#b_var{}=Bool} -> + case keymember(Bool, #b_set.dst, Is) of true -> %% Bool is a variable defined in this - %% block. It will change the outcome of - %% the `not member(V, UnsetVars)` check in - %% is_br_safe/2. The other variables - %% defined in this block will not. - ordsets:add_element(Bool, UnsetVars); + %% block. Using the br instruction from + %% this block (and skipping the body of + %% the block) is unsafe. + unsafe; false -> %% Bool is either a variable not defined %% in this block or a literal. Adding it @@ -321,18 +339,24 @@ update_unset_vars(L, Br, SetInThisBlock, UnsetVars, #st{skippable=Skippable}) -> %% the outcome of the tests in %% is_br_safe/2. UnsetVars - end + end; + #b_br{} -> + UnsetVars end; false -> + %% Some variables defined in this block are used by + %% successors. We must update the set of unset variables. + SetInThisBlock = [V || #b_set{dst=V} <- Is], ordsets:union(UnsetVars, ordsets:from_list(SetInThisBlock)) end. -shortcut_two_way(#b_br{succ=Succ,fail=Fail}, Bs0, UnsetVars0, St) -> - case shortcut_2(Succ, Bs0, UnsetVars0, St#st{target=Fail}) of +shortcut_two_way(#b_br{succ=Succ,fail=Fail}, From, Bs0, UnsetVars0, St0) -> + case shortcut_2(Succ, From, Bs0, UnsetVars0, St0#st{target=Fail}) of {#b_br{bool=#b_literal{},succ=Fail},_,_}=Res -> Res; none -> - case shortcut_2(Fail, Bs0, UnsetVars0, St#st{target=Succ}) of + St = St0#st{target=Succ}, + case shortcut_2(Fail, From, Bs0, UnsetVars0, St) of {#b_br{bool=#b_literal{},succ=Succ},_,_}=Res -> Res; none -> @@ -374,40 +398,42 @@ is_forbidden(L, St) -> %% Return the updated bindings, or 'none' if there is %% any instruction with potential side effects. -eval_is([#b_set{op=phi,dst=Dst,args=Args}|Is], Bs0, St) -> - From = map_get(from, Bs0), - [Val] = [Val || {Val,Pred} <- Args, Pred =:= From], +eval_is([#b_set{op=phi,dst=Dst,args=Args}|Is], From, Bs0, St) -> + Val = get_phi_arg(Args, From), Bs = bind_var(Dst, Val, Bs0), - eval_is(Is, Bs, St); -eval_is([#b_set{op={bif,_},dst=Dst}=I0|Is], Bs, St) -> + eval_is(Is, From, Bs, St); +eval_is([#b_set{op={bif,_},dst=Dst}=I0|Is], From, Bs, St) -> I = sub(I0, Bs), case eval_bif(I, St) of #b_literal{}=Val -> - eval_is(Is, bind_var(Dst, Val, Bs), St); + eval_is(Is, From, bind_var(Dst, Val, Bs), St); none -> - eval_is(Is, Bs, St) + eval_is(Is, From, Bs, St) end; -eval_is([#b_set{op=Op,dst=Dst}=I|Is], Bs, St) +eval_is([#b_set{op=Op,dst=Dst}=I|Is], From, Bs, St) when Op =:= is_tagged_tuple; Op =:= is_nonempty_list -> #b_set{args=Args} = sub(I, Bs), case eval_rel_op(Op, Args, St) of #b_literal{}=Val -> - eval_is(Is, bind_var(Dst, Val, Bs), St); + eval_is(Is, From, bind_var(Dst, Val, Bs), St); none -> - eval_is(Is, Bs, St) + eval_is(Is, From, Bs, St) end; -eval_is([#b_set{}=I|Is], Bs, St) -> +eval_is([#b_set{}=I|Is], From, Bs, St) -> case beam_ssa:no_side_effect(I) of true -> %% This instruction has no side effects. It can %% safely be omitted. - eval_is(Is, Bs, St); + eval_is(Is, From, Bs, St); false -> %% This instruction may have some side effect. %% It is not safe to avoid this instruction. none end; -eval_is([], Bs, _St) -> Bs. +eval_is([], _From, Bs, _St) -> Bs. + +get_phi_arg([{Val,From}|_], From) -> Val; +get_phi_arg([_|As], From) -> get_phi_arg(As, From). eval_terminator(#b_br{bool=#b_var{}=Bool}=Br, Bs, _St) -> Val = get_value(Bool, Bs), @@ -477,20 +503,31 @@ eval_bif(#b_set{op={bif,Bif},args=Args}, St) -> false -> none; true -> - case [Lit || #b_literal{val=Lit} <- Args] of - LitArgs when length(LitArgs) =:= Arity -> + case get_lit_args(Args) of + none -> + %% Not literal arguments. Try to evaluate + %% it based on a previous relational operator. + eval_rel_op({bif,Bif}, Args, St); + LitArgs -> try apply(erlang, Bif, LitArgs) of Val -> #b_literal{val=Val} catch error:_ -> none - end; - _ -> - %% Not literal arguments. Try to evaluate - %% it based on a previous relational operator. - eval_rel_op({bif,Bif}, Args, St) + end end end. +get_lit_args([#b_literal{val=Lit1}]) -> + [Lit1]; +get_lit_args([#b_literal{val=Lit1}, + #b_literal{val=Lit2}]) -> + [Lit1,Lit2]; +get_lit_args([#b_literal{val=Lit1}, + #b_literal{val=Lit2}, + #b_literal{val=Lit3}]) -> + [Lit1,Lit2,Lit3]; +get_lit_args(_) -> none. + %%% %%% Handling of relational operators. %%% @@ -1026,11 +1063,12 @@ used_vars_is([], Used) -> sub(#b_set{args=Args}=I, Sub) -> I#b_set{args=[sub_arg(A, Sub) || A <- Args]}. -sub_arg(Old, Sub) -> +sub_arg(#b_var{}=Old, Sub) -> case Sub of #{Old:=New} -> New; #{} -> Old - end. + end; +sub_arg(Old, _Sub) -> Old. rel2fam(S0) -> S1 = sofs:relation(S0), diff --git a/lib/compiler/src/beam_ssa_opt.erl b/lib/compiler/src/beam_ssa_opt.erl index 6e548dd529..bcf55f3fda 100644 --- a/lib/compiler/src/beam_ssa_opt.erl +++ b/lib/compiler/src/beam_ssa_opt.erl @@ -175,6 +175,7 @@ epilogue_passes(Opts) -> ?PASS(ssa_opt_blockify), ?PASS(ssa_opt_sink), ?PASS(ssa_opt_merge_blocks), + ?PASS(ssa_opt_get_tuple_element), ?PASS(ssa_opt_trim_unreachable)], passes_1(Ps, Opts). @@ -682,6 +683,14 @@ record_opt_is([#b_set{op={bif,is_tuple},dst=Bool,args=[Tuple]}=Set], no -> [Set] end; +record_opt_is([I|Is]=Is0, #b_br{bool=Bool}=Last, Blocks) -> + case is_tagged_tuple_1(Is0, Last, Blocks) of + {yes,_Fail,Tuple,Arity,Tag} -> + Args = [Tuple,Arity,Tag], + [I#b_set{op=is_tagged_tuple,dst=Bool,args=Args}]; + no -> + [I|record_opt_is(Is, Last, Blocks)] + end; record_opt_is([I|Is], Last, Blocks) -> [I|record_opt_is(Is, Last, Blocks)]; record_opt_is([], _Last, _Blocks) -> []. @@ -689,29 +698,30 @@ record_opt_is([], _Last, _Blocks) -> []. is_tagged_tuple(#b_var{}=Tuple, Bool, #b_br{bool=Bool,succ=Succ,fail=Fail}, Blocks) -> - SuccBlk = map_get(Succ, Blocks), - is_tagged_tuple_1(SuccBlk, Tuple, Fail, Blocks); + #b_blk{is=Is,last=Last} = map_get(Succ, Blocks), + case is_tagged_tuple_1(Is, Last, Blocks) of + {yes,Fail,Tuple,Arity,Tag} -> + {yes,Arity,Tag}; + _ -> + no + end; is_tagged_tuple(_, _, _, _) -> no. -is_tagged_tuple_1(#b_blk{is=Is,last=Last}, Tuple, Fail, Blocks) -> - case Is of - [#b_set{op={bif,tuple_size},dst=ArityVar, - args=[#b_var{}=Tuple]}, - #b_set{op={bif,'=:='}, - dst=Bool, - args=[ArityVar, #b_literal{val=ArityVal}=Arity]}] - when is_integer(ArityVal) -> - case Last of - #b_br{bool=Bool,succ=Succ,fail=Fail} -> - SuccBlk = map_get(Succ, Blocks), - case is_tagged_tuple_2(SuccBlk, Tuple, Fail) of - no -> - no; - {yes,Tag} -> - {yes,Arity,Tag} - end; - _ -> - no +is_tagged_tuple_1(Is, Last, Blocks) -> + case {Is,Last} of + {[#b_set{op={bif,tuple_size},dst=ArityVar, + args=[#b_var{}=Tuple]}, + #b_set{op={bif,'=:='}, + dst=Bool, + args=[ArityVar, #b_literal{val=ArityVal}=Arity]}], + #b_br{bool=Bool,succ=Succ,fail=Fail}} + when is_integer(ArityVal) -> + SuccBlk = map_get(Succ, Blocks), + case is_tagged_tuple_2(SuccBlk, Tuple, Fail) of + no -> + no; + {yes,Tag} -> + {yes,Fail,Tuple,Arity,Tag} end; _ -> no @@ -2174,6 +2184,46 @@ insert_def_is([#b_set{op=Op}=I|Is]=Is0, V, Def) -> insert_def_is([], _V, Def) -> [Def]. +%%% +%%% Order consecutive get_tuple_element instructions in ascending +%%% position order. This will give the loader more opportunities +%%% for combining get_tuple_element instructions. +%%% + +ssa_opt_get_tuple_element({#st{ssa=Blocks0}=St, FuncDb}) -> + Blocks = opt_get_tuple_element(maps:to_list(Blocks0), Blocks0), + {St#st{ssa=Blocks}, FuncDb}. + +opt_get_tuple_element([{L,#b_blk{is=Is0}=Blk0}|Bs], Blocks) -> + case opt_get_tuple_element_is(Is0, false, []) of + {yes,Is} -> + Blk = Blk0#b_blk{is=Is}, + opt_get_tuple_element(Bs, Blocks#{L:=Blk}); + no -> + opt_get_tuple_element(Bs, Blocks) + end; +opt_get_tuple_element([], Blocks) -> Blocks. + +opt_get_tuple_element_is([#b_set{op=get_tuple_element, + args=[#b_var{}=Src,_]}=I0|Is0], + _AnyChange, Acc) -> + {GetIs0,Is} = collect_get_tuple_element(Is0, Src, [I0]), + GetIs1 = sort([{Pos,I} || #b_set{args=[_,Pos]}=I <- GetIs0]), + GetIs = [I || {_,I} <- GetIs1], + opt_get_tuple_element_is(Is, true, reverse(GetIs, Acc)); +opt_get_tuple_element_is([I|Is], AnyChange, Acc) -> + opt_get_tuple_element_is(Is, AnyChange, [I|Acc]); +opt_get_tuple_element_is([], AnyChange, Acc) -> + case AnyChange of + true -> {yes,reverse(Acc)}; + false -> no + end. + +collect_get_tuple_element([#b_set{op=get_tuple_element, + args=[Src,_]}=I|Is], Src, Acc) -> + collect_get_tuple_element(Is, Src, [I|Acc]); +collect_get_tuple_element(Is, _Src, Acc) -> + {Acc,Is}. %%% %%% Common utilities. diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl index aa4720d222..c01ea4af91 100644 --- a/lib/compiler/src/beam_ssa_type.erl +++ b/lib/compiler/src/beam_ssa_type.erl @@ -170,7 +170,8 @@ opt_finish_1([], [], ParamInfo) -> validator_anno(#t_tuple{size=Size,exact=Exact,elements=Elements0}) -> Elements = maps:fold(fun(Index, Type, Acc) -> - Acc#{ Index => validator_anno(Type) } + Key = beam_validator:type_anno(integer, Index), + Acc#{ Key => validator_anno(Type) } end, #{}, Elements0), beam_validator:type_anno(tuple, Size, Exact, Elements); validator_anno(#t_integer{elements={Same,Same}}) -> diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl index 5175be3ad5..296c095be2 100644 --- a/lib/compiler/src/beam_validator.erl +++ b/lib/compiler/src/beam_validator.erl @@ -28,8 +28,7 @@ -export([module/2, format_error/1]). -export([type_anno/1, type_anno/2, type_anno/4]). --import(lists, [any/2,dropwhile/2,foldl/3,map/2,member/2,reverse/1, - seq/2,sort/1,zip/2]). +-import(lists, [dropwhile/2,foldl/3,member/2,reverse/1,sort/1,zip/2]). %% To be called by the compiler. @@ -51,7 +50,7 @@ module({Mod,Exp,Attr,Fs,Lc}=Code, _Opts) -spec type_anno(term()) -> term(). type_anno(atom) -> {atom,[]}; type_anno(bool) -> bool; -type_anno({binary,_}) -> term; +type_anno({binary,_}) -> binary; type_anno(cons) -> cons; type_anno(float) -> {float,[]}; type_anno(integer) -> {integer,[]}; @@ -62,9 +61,9 @@ type_anno(number) -> number; type_anno(nil) -> nil. -spec type_anno(term(), term()) -> term(). -type_anno(atom, Value) -> {atom, Value}; -type_anno(float, Value) -> {float, Value}; -type_anno(integer, Value) -> {integer, Value}. +type_anno(atom, Value) when is_atom(Value) -> {atom, Value}; +type_anno(float, Value) when is_float(Value) -> {float, Value}; +type_anno(integer, Value) when is_integer(Value) -> {integer, Value}. -spec type_anno(term(), term(), term(), term()) -> term(). type_anno(tuple, Size, Exact, Elements) when is_integer(Size), Size >= 0, @@ -137,43 +136,100 @@ validate_0(Module, [{function,Name,Ar,Entry,Code}|Fs], Ft) -> erlang:raise(Class, Error, Stack) end. +-record(value_ref, {id :: index()}). +-record(value, {op :: term(), args :: [argument()], type :: type()}). + +-type argument() :: #value_ref{} | literal(). + -type index() :: non_neg_integer(). --type reg_tab() :: gb_trees:tree(index(), 'none' | {'value', _}). - --record(st, %Emulation state - {x :: reg_tab(), %x register info. - y :: reg_tab(), %y register info. - f=init_fregs(), % - numy=none, %Number of y registers. - h=0, %Available heap size. - hf=0, %Available heap size for floats. - fls=undefined, %Floating point state. - ct=[], %List of hot catch/try labels - setelem=false, %Previous instruction was setelement/3. - puts_left=none, %put/1 instructions left. - defs=#{}, %Defining expression for each register. - aliases=#{} - }). + +-type literal() :: {atom, [] | atom()} | + {float, [] | float()} | + {integer, [] | integer()} | + {literal, term()} | + nil. + +-type tuple_sz() :: [non_neg_integer()] | %% Inexact + non_neg_integer(). %% Exact. + +%% Match context type. +-record(ms, + {id=make_ref() :: reference(), %Unique ID. + valid=0 :: non_neg_integer(), %Valid slots + slots=0 :: non_neg_integer() %Number of slots + }). + +-type type() :: binary | + cons | + list | + map | + nil | + #ms{} | + ms_position | + none | + number | + term | + tuple_in_progress | + {tuple, tuple_sz(), #{ literal() => type() }} | + literal(). + +-type tag() :: initialized | + uninitialized | + {catchtag, [label()]} | + {trytag, [label()]}. + +-type x_regs() :: #{ {x, index()} => #value_ref{} }. +-type y_regs() :: #{ {y, index()} => tag() | #value_ref{} }. + +%% Emulation state +-record(st, + {%% All known values. + vs=#{} :: #{ #value_ref{} => #value{} }, + %% Register states. + xs=#{} :: x_regs(), + ys=#{} :: y_regs(), + f=init_fregs(), + %% A set of all registers containing "fragile" terms. That is, terms + %% that don't exist on our process heap and would be destroyed by a + %% GC. + fragile=cerl_sets:new() :: cerl_sets:set(), + %% Number of Y registers. + %% + %% Note that this may be 0 if there's a frame without saved values, + %% such as on a body-recursive call. + numy=none :: none | undecided | index(), + %% Available heap size. + h=0, + %Available heap size for floats. + hf=0, + %% Floating point state. + fls=undefined, + %% List of hot catch/try labels + ct=[], + %% Previous instruction was setelement/3. + setelem=false, + %% put/1 instructions left. + puts_left=none + }). -type label() :: integer(). -type label_set() :: gb_sets:set(label()). -type branched_tab() :: gb_trees:tree(label(), #st{}). -type ft_tab() :: gb_trees:tree(). --record(vst, %Validator state - {current=none :: #st{} | 'none', %Current state - branched=gb_trees:empty() :: branched_tab(), %States at jumps - labels=gb_sets:empty() :: label_set(), %All defined labels - ft=gb_trees:empty() :: ft_tab() %Some other functions - % in the module (those that start with bs_start_match2). - }). - -%% Match context type. --record(ms, - {id=make_ref() :: reference(), %Unique ID. - valid=0 :: non_neg_integer(), %Valid slots - slots=0 :: non_neg_integer() %Number of slots - }). +%% Validator state +-record(vst, + {%% Current state + current=none :: #st{} | 'none', + %% States at labels + branched=gb_trees:empty() :: branched_tab(), + %% All defined labels + labels=gb_sets:empty() :: label_set(), + %% Argument information of other functions in the module + ft=gb_trees:empty() :: ft_tab(), + %% Counter for #value_ref{} creation + ref_ctr=0 :: index() + }). index_parameter_types([{function,_,_,Entry,Code0}|Fs], Acc0) -> Code = dropwhile(fun({label,L}) when L =:= Entry -> false; @@ -238,7 +294,7 @@ validate_fun_info_branches_1(X, {Mod,Name,Arity}=MFA, Vst) -> #vst{current=#st{numy=Size}} -> error({unexpected_stack_frame,Size}) end, - get_term_type({x,X}, Vst) + assert_term({x,X}, Vst) catch Error -> I = {func_info,{atom,Mod},{atom,Name},Arity}, Offset = 2, @@ -260,24 +316,21 @@ labels_1(Is, R) -> {reverse(R),Is}. init_vst(Arity, Ls1, Ls2, Ft) -> - Xs = init_regs(Arity, term), - Ys = init_regs(0, initialized), - St = #st{x=Xs,y=Ys}, - Branches = gb_trees_from_list([{L,St} || L <- Ls1]), + Vst0 = init_function_args(Arity - 1, #vst{current=#st{}}), + Branches = gb_trees_from_list([{L,Vst0#vst.current} || L <- Ls1]), Labels = gb_sets:from_list(Ls1++Ls2), - #vst{branched=Branches, - current=St, - labels=Labels, - ft=Ft}. + Vst0#vst{branched=Branches, + labels=Labels, + ft=Ft}. + +init_function_args(-1, Vst) -> + Vst; +init_function_args(X, Vst) -> + init_function_args(X - 1, create_term(term, argument, [], {x,X}, Vst)). kill_heap_allocation(St) -> St#st{h=0,hf=0}. -init_regs(0, _) -> - gb_trees:empty(); -init_regs(N, Type) -> - gb_trees_from_list([{R,Type} || R <- seq(0, N-1)]). - valfun([], MFA, _Offset, #vst{branched=Targets0,labels=Labels0}=Vst) -> Targets = gb_trees:keys(Targets0), Labels = gb_sets:to_list(Labels0), @@ -298,20 +351,25 @@ valfun([I|Is], MFA, Offset, Vst0) -> %% Instructions that are allowed in dead code or when failing, %% that is while the state is undecided in some way. -valfun_1({label,Lbl}, #vst{current=St0,branched=B,labels=Lbls}=Vst) -> - St = merge_states(Lbl, St0, B), - Vst#vst{current=St,branched=gb_trees:enter(Lbl, St, B), - labels=gb_sets:add(Lbl, Lbls)}; +valfun_1({label,Lbl}, #vst{current=St0, + ref_ctr=Counter0, + branched=B, + labels=Lbls}=Vst) -> + {St, Counter} = merge_states(Lbl, St0, B, Counter0), + Vst#vst{current=St, + ref_ctr=Counter, + branched=gb_trees:enter(Lbl, St, B), + labels=gb_sets:add(Lbl, Lbls)}; valfun_1(_I, #vst{current=none}=Vst) -> %% Ignore instructions after erlang:error/1,2, which %% the original R10B compiler thought would return. Vst; valfun_1({badmatch,Src}, Vst) -> - assert_not_fragile(Src, Vst), + assert_durable_term(Src, Vst), verify_y_init(Vst), kill_state(Vst); valfun_1({case_end,Src}, Vst) -> - assert_not_fragile(Src, Vst), + assert_durable_term(Src, Vst), verify_y_init(Vst), kill_state(Vst); valfun_1(if_end, Vst) -> @@ -319,7 +377,7 @@ valfun_1(if_end, Vst) -> kill_state(Vst); valfun_1({try_case_end,Src}, Vst) -> verify_y_init(Vst), - assert_not_fragile(Src, Vst), + assert_durable_term(Src, Vst), kill_state(Vst); %% Instructions that cannot cause exceptions valfun_1({bs_get_tail,Ctx,Dst,Live}, Vst0) -> @@ -363,17 +421,17 @@ valfun_1({bif,Op,{f,_},Ss,Dst}=I, Vst) -> end; %% Put instructions. valfun_1({put_list,A,B,Dst}, Vst0) -> - assert_not_fragile(A, Vst0), - assert_not_fragile(B, Vst0), + assert_term(A, Vst0), + assert_term(B, Vst0), Vst = eat_heap(2, Vst0), create_term(cons, put_list, [A, B], Dst, Vst); valfun_1({put_tuple2,Dst,{list,Elements}}, Vst0) -> - _ = [assert_not_fragile(El, Vst0) || El <- Elements], + _ = [assert_term(El, Vst0) || El <- Elements], Size = length(Elements), Vst = eat_heap(Size+1, Vst0), {Es,_} = foldl(fun(Val, {Es0, Index}) -> Type = get_term_type(Val, Vst0), - Es = set_element_type(Index, Type, Es0), + Es = set_element_type({integer,Index}, Type, Es0), {Es, Index + 1} end, {#{}, 1}, Elements), Type = {tuple,Size,Es}, @@ -385,18 +443,19 @@ valfun_1({put_tuple,Sz,Dst}, Vst0) when is_integer(Sz) -> St = St0#st{puts_left={Sz,{Dst,Sz,#{}}}}, Vst#vst{current=St}; valfun_1({put,Src}, Vst0) -> - assert_not_fragile(Src, Vst0), + assert_term(Src, Vst0), Vst = eat_heap(1, Vst0), #vst{current=St0} = Vst, case St0 of #st{puts_left=none} -> error(not_building_a_tuple); - #st{puts_left={1,{Dst,Sz,Es}}} -> + #st{puts_left={1,{Dst,Sz,Es0}}} -> + Es = Es0#{ {integer,Sz} => get_term_type(Src, Vst0) }, St = St0#st{puts_left=none}, create_term({tuple,Sz,Es}, put_tuple, [], Dst, Vst#vst{current=St}); #st{puts_left={PutsLeft,{Dst,Sz,Es0}}} when is_integer(PutsLeft) -> Index = Sz - PutsLeft + 1, - Es = Es0#{ Index => get_term_type(Src, Vst0) }, + Es = Es0#{ {integer,Index} => get_term_type(Src, Vst0) }, St = St0#st{puts_left={PutsLeft-1,{Dst,Sz,Es}}}, Vst#vst{current=St} end; @@ -417,6 +476,14 @@ valfun_1({'%', {type_info, Reg, Type}}, Vst) -> %% that Reg has a certain type, so that we can accept cross-function type %% optimizations. update_type(fun meet/2, Type, Reg, Vst); +valfun_1({'%', {remove_fragility, Reg}}, Vst) -> + %% This is a hack to make prim_eval:'receive'/2 work. + %% + %% Normally it's illegal to pass fragile terms as a function argument as we + %% have no way of knowing what the callee will do with it, but we know that + %% prim_eval:'receive'/2 won't leak the term, nor cause a GC since it's + %% disabled while matching messages. + remove_fragility(Reg, Vst); valfun_1({'%',_}, Vst) -> Vst; valfun_1({line,_}, Vst) -> @@ -438,13 +505,13 @@ valfun_1(_I, #vst{current=#st{ct=undecided}}) -> %% %% Allocate and deallocate, et.al valfun_1({allocate,Stk,Live}, Vst) -> - allocate(false, Stk, 0, Live, Vst); + allocate(uninitialized, Stk, 0, Live, Vst); valfun_1({allocate_heap,Stk,Heap,Live}, Vst) -> - allocate(false, Stk, Heap, Live, Vst); + allocate(uninitialized, Stk, Heap, Live, Vst); valfun_1({allocate_zero,Stk,Live}, Vst) -> - allocate(true, Stk, 0, Live, Vst); + allocate(initialized, Stk, 0, Live, Vst); valfun_1({allocate_heap_zero,Stk,Heap,Live}, Vst) -> - allocate(true, Stk, Heap, Live, Vst); + allocate(initialized, Stk, Heap, Live, Vst); valfun_1({deallocate,StkSize}, #vst{current=#st{numy=StkSize}}=Vst) -> verify_no_ct(Vst), deallocate(Vst); @@ -508,8 +575,9 @@ valfun_1({get_tl,Src,Dst}, Vst) -> valfun_1({get_tuple_element,Src,N,Dst}, Vst) -> assert_not_literal(Src), assert_type({tuple_element,N+1}, Src, Vst), - Type = get_element_type(N+1, Src, Vst), - extract_term(Type, get_tuple_element, [Src], Dst, Vst); + Index = {integer,N+1}, + Type = get_element_type(Index, Src, Vst), + extract_term(Type, {bif,element}, [Index, Src], Dst, Vst); valfun_1({jump,{f,Lbl}}, Vst) -> kill_state(branch_state(Lbl, Vst)); valfun_1(I, Vst) -> @@ -518,20 +586,26 @@ valfun_1(I, Vst) -> init_try_catch_branch(Tag, Dst, Fail, Vst0) -> Vst1 = create_tag({Tag,[Fail]}, 'try_catch', [], Dst, Vst0), #vst{current=#st{ct=Fails}=St0} = Vst1, - CurrentSt = St0#st{ct=[[Fail]|Fails]}, + St = St0#st{ct=[[Fail]|Fails]}, + Vst = Vst0#vst{current=St}, - %% Set the initial state at the try/catch label. - %% Assume that Y registers contain terms or try/catch - %% tags. - Yregs0 = map(fun({Y,uninitialized}) -> {Y,term}; - ({Y,initialized}) -> {Y,term}; - (E) -> E - end, gb_trees:to_list(CurrentSt#st.y)), - Yregs = gb_trees:from_orddict(Yregs0), - BranchSt = CurrentSt#st{y=Yregs}, + complex_test(Fail, + fun(CatchVst) -> + #vst{current=#st{ys=Ys}} = CatchVst, + maps:fold(fun init_catch_handler_1/3, CatchVst, Ys) + end, + fun(SuccVst) -> + SuccVst + end, Vst). - Vst = branch_state(Fail, Vst1#vst{current=BranchSt}), - Vst#vst{current=CurrentSt}. +%% Set the initial state at the try/catch label. Assume that Y registers +%% contain terms or try/catch tags. +init_catch_handler_1(Reg, initialized, Vst) -> + create_term(term, 'catch_handler', [], Reg, Vst); +init_catch_handler_1(Reg, uninitialized, Vst) -> + create_term(term, 'catch_handler', [], Reg, Vst); +init_catch_handler_1(_, _, Vst) -> + Vst. %% Update branched state if necessary and try next set of instructions. valfun_2(I, #vst{current=#st{ct=[]}}=Vst) -> @@ -609,15 +683,13 @@ valfun_4({make_fun2,_,_,_,Live}, Vst) -> call(make_fun, Live, Vst); %% Other BIFs valfun_4({bif,element,{f,Fail},[Pos,Tuple],Dst}, Vst0) -> - PosType = get_durable_term_type(Pos, Vst0), - ElementType = case PosType of - {integer,I} -> get_element_type(I, Tuple, Vst0); - _ -> term - end, + PosType = get_term_type(Pos, Vst0), + ElementType = get_element_type(PosType, Tuple, Vst0), InferredType = {tuple,[get_tuple_size(PosType)],#{}}, Vst1 = branch_state(Fail, Vst0), - Vst = update_type(fun meet/2, InferredType, Tuple, Vst1), - extract_term(ElementType, {bif,element}, [Tuple], Dst, Vst); + Vst2 = update_type(fun meet/2, InferredType, Tuple, Vst1), + Vst = update_type(fun meet/2, {integer,[]}, Pos, Vst2), + extract_term(ElementType, {bif,element}, [Pos,Tuple], Dst, Vst); valfun_4({bif,raise,{f,0},Src,_Dst}, Vst) -> validate_src(Src, Vst), kill_state(Vst); @@ -634,7 +706,7 @@ valfun_4({bif,Op,{f,Fail},Ss,Dst}, Vst0) -> Vst1 = branch_state(Fail, Vst0), %% Infer argument types. Note that we can't type_test in the general case - %% as the BIF could fail for reasons other than bad arguments. + %% as the BIF could fail for reasons other than bad argument types. ArgTypes = bif_arg_types(Op, Ss), Vst = foldl(fun({Arg, T}, Vsti) -> update_type(fun meet/2, T, Arg, Vsti) @@ -659,17 +731,18 @@ valfun_4({gc_bif,Op,{f,Fail},Live,Ss,Dst}, #vst{current=St0}=Vst0) -> Vst = prune_x_regs(Live, Vst3), extract_term(Type, {gc_bif,Op}, Ss, Dst, Vst, Vst0); valfun_4(return, #vst{current=#st{numy=none}}=Vst) -> - assert_not_fragile({x,0}, Vst), + assert_durable_term({x,0}, Vst), kill_state(Vst); valfun_4(return, #vst{current=#st{numy=NumY}}) -> error({stack_frame,NumY}); valfun_4({loop_rec,{f,Fail},Dst}, Vst0) -> - Vst = branch_state(Fail, Vst0), %% This term may not be part of the root set until %% remove_message/0 is executed. If control transfers %% to the loop_rec_end/1 instruction, no part of %% this term must be stored in a Y register. - create_term({fragile,term}, loop_rec, [], Dst, Vst); + Vst1 = branch_state(Fail, Vst0), + {Ref, Vst} = new_value(term, loop_rec, [], Vst1), + mark_fragile(Dst, set_reg_vref(Ref, Dst, Vst)); valfun_4({wait,_}, Vst) -> verify_y_init(Vst), kill_state(Vst); @@ -680,20 +753,20 @@ valfun_4({wait_timeout,_,Src}, Vst) -> valfun_4({loop_rec_end,_}, Vst) -> verify_y_init(Vst), kill_state(Vst); -valfun_4(timeout, #vst{current=St}=Vst) -> - Vst#vst{current=St#st{x=init_regs(0, term)}}; +valfun_4(timeout, Vst) -> + prune_x_regs(0, Vst); valfun_4(send, Vst) -> call(send, 2, Vst); valfun_4({set_tuple_element,Src,Tuple,N}, Vst) -> I = N + 1, - assert_not_fragile(Src, Vst), + assert_term(Src, Vst), assert_type({tuple_element,I}, Tuple, Vst), %% Manually update the tuple type; we can't rely on the ordinary update %% helpers as we must support overwriting (rather than just widening or %% narrowing) known elements, and we can't use extract_term either since %% the source tuple may be aliased. {tuple, Sz, Es0} = get_term_type(Tuple, Vst), - Es = set_element_type(I, get_term_type(Src, Vst), Es0), + Es = set_element_type({integer,I}, get_term_type(Src, Vst), Es0), override_type({tuple, Sz, Es}, Tuple, Vst); %% Match instructions. valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst0) -> @@ -750,15 +823,19 @@ valfun_4({bs_get_position, Ctx, Dst, Live}, Vst0) -> verify_live(Live, Vst0), verify_y_init(Vst0), Vst = prune_x_regs(Live, Vst0), - create_term(bs_position, bs_get_position, [Ctx], Dst, Vst); + create_term(ms_position, bs_get_position, [Ctx], Dst, Vst, Vst0); valfun_4({bs_set_position, Ctx, Pos}, Vst) -> bsm_validate_context(Ctx, Vst), - assert_type(bs_position, Pos, Vst), + assert_type(ms_position, Pos, Vst), Vst; %% Other test instructions. valfun_4({test,is_atom,{f,Lbl},[Src]}, Vst) -> type_test(Lbl, {atom,[]}, Src, Vst); +valfun_4({test,is_binary,{f,Lbl},[Src]}, Vst) -> + type_test(Lbl, binary, Src, Vst); +valfun_4({test,is_bitstr,{f,Lbl},[Src]}, Vst) -> + type_test(Lbl, binary, Src, Vst); valfun_4({test,is_boolean,{f,Lbl},[Src]}, Vst) -> type_test(Lbl, bool, Src, Vst); valfun_4({test,is_float,{f,Lbl},[Src]}, Vst) -> @@ -769,10 +846,21 @@ valfun_4({test,is_integer,{f,Lbl},[Src]}, Vst) -> type_test(Lbl, {integer,[]}, Src, Vst); valfun_4({test,is_nonempty_list,{f,Lbl},[Src]}, Vst) -> type_test(Lbl, cons, Src, Vst); +valfun_4({test,is_number,{f,Lbl},[Src]}, Vst) -> + type_test(Lbl, number, Src, Vst); valfun_4({test,is_list,{f,Lbl},[Src]}, Vst) -> type_test(Lbl, list, Src, Vst); valfun_4({test,is_nil,{f,Lbl},[Src]}, Vst) -> - type_test(Lbl, nil, Src, Vst); + %% is_nil is an exact check against the 'nil' value, and should not be + %% treated as a simple type test. + assert_term(Src, Vst), + complex_test(Lbl, + fun(FailVst) -> + update_ne_types(Src, nil, FailVst) + end, + fun(SuccVst) -> + update_eq_types(Src, nil, SuccVst) + end, Vst); valfun_4({test,is_map,{f,Lbl},[Src]}, Vst) -> case Src of {Tag,_} when Tag =:= x; Tag =:= y -> @@ -790,7 +878,7 @@ valfun_4({test,test_arity,{f,Lbl},[Tuple,Sz]}, Vst0) when is_integer(Sz) -> valfun_4({test,is_tagged_tuple,{f,Lbl},[Src,Sz,Atom]}, Vst0) -> assert_term(Src, Vst0), Vst = branch_state(Lbl, Vst0), - update_type(fun meet/2, {tuple,Sz,#{ 1 => Atom }}, Src, Vst); + update_type(fun meet/2, {tuple,Sz,#{ {integer,1} => Atom }}, Src, Vst); valfun_4({test,has_map_fields,{f,Lbl},Src,{list,List}}, Vst) -> assert_type(map, Src, Vst), assert_unique_map_keys(List), @@ -817,8 +905,8 @@ valfun_4({test,_Op,{f,Lbl},Src}, Vst) -> validate_src(Src, Vst), branch_state(Lbl, Vst); valfun_4({bs_add,{f,Fail},[A,B,_],Dst}, Vst) -> - assert_not_fragile(A, Vst), - assert_not_fragile(B, Vst), + assert_term(A, Vst), + assert_term(B, Vst), create_term({integer,[]}, bs_add, [A, B], Dst, branch_state(Fail, Vst)); valfun_4({bs_utf8_size,{f,Fail},A,Dst}, Vst) -> assert_term(A, Vst), @@ -833,12 +921,12 @@ valfun_4({bs_init2,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) -> is_integer(Sz) -> ok; true -> - assert_not_fragile(Sz, Vst0) + assert_term(Sz, Vst0) end, Vst1 = heap_alloc(Heap, Vst0), Vst2 = branch_state(Fail, Vst1), Vst = prune_x_regs(Live, Vst2), - create_term(binary, bs_init2, [], Dst, Vst); + create_term(binary, bs_init2, [], Dst, Vst, Vst0); valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) -> verify_live(Live, Vst0), verify_y_init(Vst0), @@ -855,39 +943,39 @@ valfun_4({bs_init_bits,{f,Fail},Sz,Heap,Live,_,Dst}, Vst0) -> valfun_4({bs_append,{f,Fail},Bits,Heap,Live,_Unit,Bin,_Flags,Dst}, Vst0) -> verify_live(Live, Vst0), verify_y_init(Vst0), - assert_not_fragile(Bits, Vst0), - assert_not_fragile(Bin, Vst0), + assert_term(Bits, Vst0), + assert_term(Bin, Vst0), Vst1 = heap_alloc(Heap, Vst0), Vst2 = branch_state(Fail, Vst1), Vst = prune_x_regs(Live, Vst2), - create_term(binary, bs_append, [Bin], Dst, Vst); + create_term(binary, bs_append, [Bin], Dst, Vst, Vst0); valfun_4({bs_private_append,{f,Fail},Bits,_Unit,Bin,_Flags,Dst}, Vst0) -> - assert_not_fragile(Bits, Vst0), - assert_not_fragile(Bin, Vst0), + assert_term(Bits, Vst0), + assert_term(Bin, Vst0), Vst = branch_state(Fail, Vst0), create_term(binary, bs_private_append, [Bin], Dst, Vst); valfun_4({bs_put_string,Sz,_}, Vst) when is_integer(Sz) -> Vst; valfun_4({bs_put_binary,{f,Fail},Sz,_,_,Src}, Vst) -> - assert_not_fragile(Sz, Vst), - assert_not_fragile(Src, Vst), + assert_term(Sz, Vst), + assert_term(Src, Vst), branch_state(Fail, Vst); valfun_4({bs_put_float,{f,Fail},Sz,_,_,Src}, Vst) -> - assert_not_fragile(Sz, Vst), - assert_not_fragile(Src, Vst), + assert_term(Sz, Vst), + assert_term(Src, Vst), branch_state(Fail, Vst); valfun_4({bs_put_integer,{f,Fail},Sz,_,_,Src}, Vst) -> - assert_not_fragile(Sz, Vst), - assert_not_fragile(Src, Vst), + assert_term(Sz, Vst), + assert_term(Src, Vst), branch_state(Fail, Vst); valfun_4({bs_put_utf8,{f,Fail},_,Src}, Vst) -> - assert_not_fragile(Src, Vst), + assert_term(Src, Vst), branch_state(Fail, Vst); valfun_4({bs_put_utf16,{f,Fail},_,Src}, Vst) -> - assert_not_fragile(Src, Vst), + assert_term(Src, Vst), branch_state(Fail, Vst); valfun_4({bs_put_utf32,{f,Fail},_,Src}, Vst) -> - assert_not_fragile(Src, Vst), + assert_term(Src, Vst), branch_state(Fail, Vst); %% Map instructions. valfun_4({put_map_assoc=Op,{f,Fail},Src,Dst,Live,{list,List}}, Vst) -> @@ -945,13 +1033,13 @@ verify_put_map(Op, Fail, Src, Dst, Live, List, Vst0) -> assert_type(map, Src, Vst0), verify_live(Live, Vst0), verify_y_init(Vst0), - [assert_not_fragile(Term, Vst0) || Term <- List], + [assert_term(Term, Vst0) || Term <- List], Vst1 = heap_alloc(0, Vst0), Vst2 = branch_state(Fail, Vst1), Vst = prune_x_regs(Live, Vst2), Keys = extract_map_keys(List), assert_unique_map_keys(Keys), - create_term(map, Op, [Src], Dst, Vst). + create_term(map, Op, [Src], Dst, Vst, Vst0). %% %% Common code for validating bs_start_match* instructions. @@ -962,18 +1050,20 @@ validate_bs_start_match(Fail, Live, Type, Src, Dst, Vst) -> verify_y_init(Vst), %% #ms{} can represent either a match context or a term, so we have to mark - %% the source as a term if it fails, and retain the incoming type if it - %% succeeds (match context or not). - %% - %% The override_type hack is only needed until we get proper union types. + %% the source as a term if it fails with a match context as an input. This + %% hack is only needed until we get proper union types. complex_test(Fail, fun(FailVst) -> - override_type(term, Src, FailVst) + case get_raw_type(Src, FailVst) of + #ms{} -> override_type(term, Src, FailVst); + _ -> FailVst + end end, fun(SuccVst0) -> - SuccVst = prune_x_regs(Live, SuccVst0), + SuccVst1 = update_type(fun meet/2, binary, Src, SuccVst0), + SuccVst = prune_x_regs(Live, SuccVst1), extract_term(Type, bs_start_match, [Src], Dst, - SuccVst, Vst) + SuccVst, SuccVst0) end, Vst). %% @@ -985,7 +1075,7 @@ validate_bs_get(Op, Fail, Ctx, Live, Type, Dst, Vst0) -> verify_y_init(Vst0), Vst1 = prune_x_regs(Live, Vst0), Vst = branch_state(Fail, Vst1), - extract_term(Type, Op, [Ctx], Dst, Vst). + extract_term(Type, Op, [Ctx], Dst, Vst, Vst0). %% %% Common code for validating bs_skip_utf* instructions. @@ -1032,7 +1122,7 @@ call(Name, Live, #vst{current=St0}=Vst0) -> case call_return_type(Name, Vst0) of Type when Type =/= exception -> %% Type is never 'exception' because it has been handled earlier. - St = St0#st{f=init_fregs(),aliases=#{}}, + St = St0#st{f=init_fregs()}, Vst = prune_x_regs(0, Vst0#vst{current=St}), create_term(Type, call, [], {x,0}, Vst) end. @@ -1059,13 +1149,15 @@ verify_call_args(_, Live, _) -> verify_remote_args_1(-1, _) -> ok; verify_remote_args_1(X, Vst) -> - assert_not_fragile({x, X}, Vst), + assert_durable_term({x, X}, Vst), verify_remote_args_1(X - 1, Vst). verify_local_args(-1, _Lbl, _CtxIds, _Vst) -> ok; verify_local_args(X, Lbl, CtxIds, Vst) -> Reg = {x, X}, + assert_movable(Reg, Vst), + assert_not_fragile(Reg, Vst), case get_raw_type(Reg, Vst) of #ms{id=Id}=Type -> case CtxIds of @@ -1075,8 +1167,6 @@ verify_local_args(X, Lbl, CtxIds, Vst) -> verify_arg_type(Lbl, Reg, Type, Vst), verify_local_args(X - 1, Lbl, CtxIds#{ Id => Reg }, Vst) end; - {fragile,_} -> - error({fragile_message_reference, Reg}); Type -> verify_arg_type(Lbl, Reg, Type, Vst), verify_local_args(X - 1, Lbl, CtxIds, Vst) @@ -1092,56 +1182,89 @@ verify_arg_type(Lbl, Reg, #ms{}, #vst{ft=Ft}) -> end; verify_arg_type(Lbl, Reg, GivenType, #vst{ft=Ft}) -> case gb_trees:lookup({Lbl, Reg}, Ft) of - {value, bool} when GivenType =:= {atom, true}; - GivenType =:= {atom, false}; - GivenType =:= {atom, []} -> - %% We don't yet support upgrading true/false to bool, so we - %% assume unknown atoms can be bools when validating calls. - ok; {value, #ms{}} -> %% Functions that accept match contexts also accept all other %% terms. This will change once we support union types. ok; {value, RequiredType} -> - case meet(GivenType, RequiredType) of - none -> error({bad_arg_type, Reg, GivenType, RequiredType}); - _ -> ok + case vat_1(GivenType, RequiredType) of + true -> ok; + false -> error({bad_arg_type, Reg, GivenType, RequiredType}) end; none -> ok end. -allocate(Zero, Stk, Heap, Live, #vst{current=#st{numy=none}}=Vst0) -> +%% Checks whether the Given argument is compatible with the Required one. This +%% is essentially a relaxed version of 'meet(Given, Req) =:= Given', where we +%% accept that the Given value has the right type but not necessarily the exact +%% same value; if {atom,gurka} is required, we'll consider {atom,[]} valid. +%% +%% This will catch all problems that could crash the emulator, like passing a +%% 1-tuple when the callee expects a 3-tuple, but some value errors might slip +%% through. +vat_1(Same, Same) -> true; +vat_1({atom,A}, {atom,B}) -> A =:= B orelse is_list(A) orelse is_list(B); +vat_1({atom,A}, bool) -> is_boolean(A) orelse is_list(A); +vat_1(bool, {atom,B}) -> is_boolean(B) orelse is_list(B); +vat_1(cons, list) -> true; +vat_1({float,A}, {float,B}) -> A =:= B orelse is_list(A) orelse is_list(B); +vat_1({float,_}, number) -> true; +vat_1({integer,A}, {integer,B}) -> A =:= B orelse is_list(A) orelse is_list(B); +vat_1({integer,_}, number) -> true; +vat_1(_, {literal,_}) -> false; +vat_1({literal,_}=Lit, Required) -> vat_1(get_literal_type(Lit), Required); +vat_1(nil, list) -> true; +vat_1({tuple,SzA,EsA}, {tuple,SzB,EsB}) -> + if + is_list(SzB) -> + tuple_sz(SzA) >= tuple_sz(SzB) andalso vat_elements(EsA, EsB); + SzA =:= SzB -> + vat_elements(EsA, EsB); + SzA =/= SzB -> + false + end; +vat_1(_, _) -> false. + +vat_elements(EsA, EsB) -> + maps:fold(fun(Key, Req, Acc) -> + case EsA of + #{ Key := Given } -> Acc andalso vat_1(Given, Req); + #{} -> false + end + end, true, EsB). + +allocate(Tag, Stk, Heap, Live, #vst{current=#st{numy=none}=St}=Vst0) -> verify_live(Live, Vst0), - Vst = #vst{current=St} = prune_x_regs(Live, Vst0), - Ys = init_regs(Stk, case Zero of - true -> initialized; - false -> uninitialized - end), - heap_alloc(Heap, Vst#vst{current=St#st{y=Ys,numy=Stk}}); + Vst1 = Vst0#vst{current=St#st{numy=Stk}}, + Vst2 = prune_x_regs(Live, Vst1), + Vst = init_stack(Tag, Stk - 1, Vst2), + heap_alloc(Heap, Vst); allocate(_, _, _, _, #vst{current=#st{numy=Numy}}) -> error({existing_stack_frame,{size,Numy}}). deallocate(#vst{current=St}=Vst) -> - Vst#vst{current=St#st{y=init_regs(0, initialized),numy=none}}. - -trim_stack(From, To, Top, #st{y=Ys0}=St) when From =:= Top -> - Ys = foldl(fun(Y, Acc) -> - gb_trees:delete(Y, Acc) - end, Ys0, seq(To, From - 1)), - %% Note that all aliases and defs are wiped. This is perhaps a bit too - %% conservative, but preserving them won't be easy until type management - %% is refactored. - St#st{aliases=#{},defs=#{},numy=To,y=Ys}; + Vst#vst{current=St#st{ys=#{},numy=none}}. + +init_stack(_Tag, -1, Vst) -> + Vst; +init_stack(Tag, Y, Vst) -> + init_stack(Tag, Y - 1, create_tag(Tag, allocate, [], {y,Y}, Vst)). + +trim_stack(From, To, Top, #st{ys=Ys0}=St) when From =:= Top -> + Ys = maps:filter(fun({y,Y}, _) -> Y < To end, Ys0), + St#st{numy=To,ys=Ys}; trim_stack(From, To, Top, St0) -> - #st{y=Ys0} = St0, + Src = {y, From}, + Dst = {y, To}, - Ys = case gb_trees:lookup(From, Ys0) of - none -> error({invalid_shift,{y,From},{y,To}}); - {value,Type} -> gb_trees:enter(To, Type, Ys0) + #st{ys=Ys0} = St0, + Ys = case Ys0 of + #{ Src := Ref } -> Ys0#{ Dst => Ref }; + #{} -> error({invalid_shift,Src,Dst}) end, + St = St0#st{ys=Ys}, - St = St0#st{y=Ys}, trim_stack(From + 1, To + 1, Top, St). test_heap(Heap, Live, Vst0) -> @@ -1168,24 +1291,17 @@ heap_alloc_2([{floats,Floats}|T], St0) -> heap_alloc_2(T, St); heap_alloc_2([], St) -> St. -prune_x_regs(Live, #vst{current=St0}=Vst) - when is_integer(Live) -> - #st{x=Xs0,defs=Defs0,aliases=Aliases0} = St0, - Xs1 = gb_trees:to_list(Xs0), - Xs = [P || {R,_}=P <- Xs1, R < Live], - Defs = maps:filter(fun({x,X}, _) -> X < Live; - ({y,_}, _) -> true - end, Defs0), - Aliases = maps:filter(fun({x,X1}, {x,X2}) -> - X1 < Live andalso X2 < Live; - ({x,X}, _) -> - X < Live; - (_, {x,X}) -> - X < Live; - (_, _) -> - true - end, Aliases0), - St = St0#st{x=gb_trees:from_orddict(Xs),defs=Defs,aliases=Aliases}, +prune_x_regs(Live, #vst{current=St0}=Vst) when is_integer(Live) -> + #st{fragile=Fragile0,xs=Xs0} = St0, + Fragile = cerl_sets:filter(fun({x,X}) -> + X < Live; + ({y,_}) -> + true + end, Fragile0), + Xs = maps:filter(fun({x,X}, _) -> + X < Live + end, Xs0), + St = St0#st{fragile=Fragile,xs=Xs}, Vst#vst{current=St}. %% All choices in a select_val list must be integers, floats, or atoms. @@ -1249,8 +1365,7 @@ get_fls(#vst{current=#st{fls=Fls}}) when is_atom(Fls) -> Fls. init_fregs() -> 0. -set_freg({fr,Fr}=Freg, #vst{current=#st{f=Fregs0}=St}=Vst) - when is_integer(Fr), 0 =< Fr -> +set_freg({fr,Fr}=Freg, #vst{current=#st{f=Fregs0}=St}=Vst) -> check_limit(Freg), Bit = 1 bsl Fr, if @@ -1308,19 +1423,13 @@ bsm_validate_context(Reg, Vst) -> _ = bsm_get_context(Reg, Vst), ok. -bsm_get_context({x,X}=Reg, #vst{current=#st{x=Xs}}=_Vst) when is_integer(X) -> - case gb_trees:lookup(X, Xs) of - {value,#ms{}=Ctx} -> Ctx; - {value,{fragile,#ms{}=Ctx}} -> Ctx; - _ -> error({no_bsm_context,Reg}) - end; -bsm_get_context({y,Y}=Reg, #vst{current=#st{y=Ys}}=_Vst) when is_integer(Y) -> - case gb_trees:lookup(Y, Ys) of - {value,#ms{}=Ctx} -> Ctx; - {value,{fragile,#ms{}=Ctx}} -> Ctx; - _ -> error({no_bsm_context,Reg}) +bsm_get_context({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y-> + case get_raw_type(Reg, Vst) of + #ms{}=Ctx -> Ctx; + _ -> error({no_bsm_context,Reg}) end; -bsm_get_context(Reg, _) -> error({bad_source,Reg}). +bsm_get_context(Reg, _) -> + error({bad_source,Reg}). bsm_save(Reg, {atom,start}, Vst) -> %% Save point refering to where the match started. @@ -1357,17 +1466,17 @@ select_val_branches(Fail, Src, Choices, Vst0) -> svb_1([Val,{f,L}|T], Src, Vst0) -> Vst = complex_test(L, fun(BranchVst) -> - update_eq_types(Val, Src, BranchVst) + update_eq_types(Src, Val, BranchVst) end, fun(FailVst) -> - update_ne_types(Val, Src, FailVst) + update_ne_types(Src, Val, FailVst) end, Vst0), svb_1(T, Src, Vst); svb_1([], _, Vst) -> Vst. select_arity_branches(Fail, List, Tuple, Vst0) -> - Type = get_durable_term_type(Tuple, Vst0), + Type = get_term_type(Tuple, Vst0), Vst = sab_1(List, Tuple, Type, Vst0), kill_state(branch_state(Fail, Vst)). @@ -1389,42 +1498,54 @@ sab_1([_,{f,_}|T], Tuple, Type, Vst) -> sab_1([], _, _, #vst{}=Vst) -> Vst. -infer_types(Src, Vst) -> - case get_def(Src, Vst) of - {{bif,tuple_size}, [Tuple]} -> - fun({integer,Arity}, S) -> - update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S); - (_, S) -> S - end; - {{bif,'=:='},[ArityReg,{integer,_}=Val]} when ArityReg =/= Src -> - fun({atom,true}, S) -> - Infer = infer_types(ArityReg, S), - Infer(Val, S); - (_, S) -> S - end; - {{bif,is_atom},[Src]} -> - infer_type_test_bif({atom,[]}, Src); - {{bif,is_boolean},[Src]} -> - infer_type_test_bif(bool, Src); - {{bif,is_binary},[Src]} -> - infer_type_test_bif(binary, Src); - {{bif,is_bitstring},[Src]} -> - infer_type_test_bif(binary, Src); - {{bif,is_float},[Src]} -> - infer_type_test_bif(float, Src); - {{bif,is_integer},[Src]} -> - infer_type_test_bif({integer,{}}, Src); - {{bif,is_list},[Src]} -> - infer_type_test_bif(list, Src); - {{bif,is_map},[Src]} -> - infer_type_test_bif(map, Src); - {{bif,is_number},[Src]} -> - infer_type_test_bif(number, Src); - {{bif,is_tuple},[Src]} -> - infer_type_test_bif({tuple,[0],#{}}, Src); - _ -> - fun(_, S) -> S end - end. +infer_types({Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y -> + infer_types(get_reg_vref(Reg, Vst), Vst); +infer_types(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) -> + case Vs of + #{ Ref := Entry } -> infer_types_1(Entry); + #{} -> fun(_, S) -> S end + end; +infer_types(_, #vst{}) -> + fun(_, S) -> S end. + +infer_types_1(#value{op={bif,'=:='},args=[LHS,RHS]}) -> + fun({atom,true}, S) -> + Infer = infer_types(RHS, S), + Infer(LHS, S); + (_, S) -> S + end; +infer_types_1(#value{op={bif,element},args=[{integer,Index}=Key,Tuple]}) -> + fun(Val, S) -> + Type = get_term_type(Val, S), + update_type(fun meet/2,{tuple,[Index],#{ Key => Type }}, Tuple, S) + end; +infer_types_1(#value{op={bif,is_atom},args=[Src]}) -> + infer_type_test_bif({atom,[]}, Src); +infer_types_1(#value{op={bif,is_boolean},args=[Src]}) -> + infer_type_test_bif(bool, Src); +infer_types_1(#value{op={bif,is_binary},args=[Src]}) -> + infer_type_test_bif(binary, Src); +infer_types_1(#value{op={bif,is_bitstring},args=[Src]}) -> + infer_type_test_bif(binary, Src); +infer_types_1(#value{op={bif,is_float},args=[Src]}) -> + infer_type_test_bif(float, Src); +infer_types_1(#value{op={bif,is_integer},args=[Src]}) -> + infer_type_test_bif({integer,{}}, Src); +infer_types_1(#value{op={bif,is_list},args=[Src]}) -> + infer_type_test_bif(list, Src); +infer_types_1(#value{op={bif,is_map},args=[Src]}) -> + infer_type_test_bif(map, Src); +infer_types_1(#value{op={bif,is_number},args=[Src]}) -> + infer_type_test_bif(number, Src); +infer_types_1(#value{op={bif,is_tuple},args=[Src]}) -> + infer_type_test_bif({tuple,[0],#{}}, Src); +infer_types_1(#value{op={bif,tuple_size}, args=[Tuple]}) -> + fun({integer,Arity}, S) -> + update_type(fun meet/2, {tuple,Arity,#{}}, Tuple, S); + (_, S) -> S + end; +infer_types_1(_) -> + fun(_, S) -> S end. infer_type_test_bif(Type, Src) -> fun({atom,true}, S) -> @@ -1445,38 +1566,67 @@ assign({y,_}=Src, {y,_}=Dst, Vst) -> initialized -> create_tag(initialized, init, [], Dst, Vst); _ -> assign_1(Src, Dst, Vst) end; -assign({Kind,_}=Reg, Dst, Vst) when Kind =:= x; Kind =:= y -> - assign_1(Reg, Dst, Vst); +assign({Kind,_}=Src, Dst, Vst) when Kind =:= x; Kind =:= y -> + assign_1(Src, Dst, Vst); assign(Literal, Dst, Vst) -> - Type = get_term_type(Literal, Vst), + Type = get_literal_type(Literal), create_term(Type, move, [Literal], Dst, Vst). -%% Creates a special tag value that isn't a regular term, such as -%% 'initialized' or 'catchtag' -create_tag(Type, Op, Ss, {y,_}=Dst, Vst) -> - set_type_reg_expr(Type, {Op, Ss}, Dst, Vst); -create_tag(_Type, _Op, _Ss, Dst, _Vst) -> +%% Creates a special tag value that isn't a regular term, such as 'initialized' +%% or 'catchtag' +create_tag(Tag, _Op, _Ss, {y,_}=Dst, #vst{current=#st{ys=Ys0}=St0}=Vst) -> + case maps:get(Dst, Ys0, uninitialized) of + {catchtag,_}=Prev -> + error(Prev); + {trytag,_}=Prev -> + error(Prev); + _ -> + check_try_catch_tags(Tag, Dst, Vst), + Ys = Ys0#{ Dst => Tag }, + St = St0#st{ys=Ys}, + remove_fragility(Dst, Vst#vst{current=St}) + end; +create_tag(_Tag, _Op, _Ss, Dst, _Vst) -> error({invalid_tag_register, Dst}). %% Wipes a special tag, leaving the register initialized but empty. -kill_tag({y,Y}=Reg, #vst{current=#st{y=Ys0}=St0}=Vst) -> +kill_tag({y,_}=Reg, #vst{current=#st{ys=Ys0}=St0}=Vst) -> _ = get_tag_type(Reg, Vst), %Assertion. - Ys = gb_trees:update(Y, initialized, Ys0), - Vst#vst{current=St0#st{y=Ys}}. + Ys = Ys0#{ Reg => initialized }, + Vst#vst{current=St0#st{ys=Ys}}. %% Creates a completely new term with the given type. -create_term(Type, Op, Ss, Dst, Vst) -> - set_type_reg_expr(Type, {Op, Ss}, Dst, Vst). +create_term(Type, Op, Ss0, Dst, Vst0) -> + create_term(Type, Op, Ss0, Dst, Vst0, Vst0). -%% Extracts a term from Ss, propagating fragility. -extract_term(Type, Op, Ss, Dst, Vst) -> - extract_term(Type, Op, Ss, Dst, Vst, Vst). +%% As create_term/4, but uses the incoming Vst for argument resolution in +%% case x-regs have been pruned and the sources can no longer be found. +create_term(Type, Op, Ss0, Dst, Vst0, OrigVst) -> + {Ref, Vst1} = new_value(Type, Op, resolve_args(Ss0, OrigVst), Vst0), + Vst = remove_fragility(Dst, Vst1), + set_reg_vref(Ref, Dst, Vst). -%% As extract_term/4, but uses the incoming Vst for fragility in case x-regs -%% have been pruned and the sources can no longer be found. -extract_term(Type0, Op, Ss, Dst, Vst, OrigVst) -> - Type = propagate_fragility(Type0, Ss, OrigVst), - set_type_reg_expr(Type, {Op, Ss}, Dst, Vst). +%% Extracts a term from Ss, propagating fragility. +extract_term(Type, Op, Ss0, Dst, Vst0) -> + extract_term(Type, Op, Ss0, Dst, Vst0, Vst0). + +%% As extract_term/4, but uses the incoming Vst for argument resolution in +%% case x-regs have been pruned and the sources can no longer be found. +extract_term(Type, Op, Ss0, Dst, Vst0, OrigVst) -> + {Ref, Vst1} = new_value(Type, Op, resolve_args(Ss0, OrigVst), Vst0), + Vst = propagate_fragility(Dst, Ss0, Vst1), + set_reg_vref(Ref, Dst, Vst). + +%% Translates instruction arguments into the argument() type, decoupling them +%% from their registers, allowing us to infer their types after they've been +%% clobbered or moved. +resolve_args([{Kind,_}=Src | Args], Vst) when Kind =:= x; Kind =:= y -> + [get_reg_vref(Src, Vst) | resolve_args(Args, Vst)]; +resolve_args([Lit | Args], Vst) -> + assert_literal(Lit), + [Lit | resolve_args(Args, Vst)]; +resolve_args([], _Vst) -> + []. %% Helper functions for tests that alter state on both the success and fail %% branches, keeping the states from tainting each other. @@ -1501,13 +1651,11 @@ type_test(Fail, Type, Reg, Vst) -> %% Overrides the type of Reg. This is ugly but a necessity for certain %% destructive operations. override_type(Type, Reg, Vst) -> - %% Once the new type format is in, this should be expressed as: - %% update_type(fun(_, T) -> T end, Type, Reg, Vst). - set_aliased_type(Type, Reg, Vst). + update_type(fun(_, T) -> T end, Type, Reg, Vst). %% This is used when linear code finds out more and more information about a %% type, so that the type gets more specialized. -update_type(Merge, Type0, Reg, Vst) -> +update_type(Merge, Type0, #value_ref{}=Ref, Vst) -> %% If the old type can't be merged with the new one, the type information %% is inconsistent and we know that some instructions will never be %% executed at run-time. For example: @@ -1518,21 +1666,26 @@ update_type(Merge, Type0, Reg, Vst) -> %% %% Note that the test_arity instruction can never be reached, so we use the %% new type instead of 'none'. - Type = case Merge(get_durable_term_type(Reg, Vst), Type0) of + Type = case Merge(get_raw_type(Ref, Vst), Type0) of none -> Type0; T -> T end, - set_aliased_type(Type, Reg, Vst). + set_type(Type, Ref, Vst); +update_type(Merge, Type, {Kind,_}=Reg, Vst) when Kind =:= x; Kind =:= y -> + update_type(Merge, Type, get_reg_vref(Reg, Vst), Vst); +update_type(_Merge, _Type, Literal, Vst) -> + assert_literal(Literal), + Vst. update_ne_types(LHS, RHS, Vst) -> - update_type(fun subtract/2, get_durable_term_type(RHS, Vst), LHS, Vst). + update_type(fun subtract/2, get_term_type(RHS, Vst), LHS, Vst). update_eq_types(LHS, RHS, Vst0) -> Infer = infer_types(LHS, Vst0), Vst1 = Infer(RHS, Vst0), - T1 = get_durable_term_type(LHS, Vst1), - T2 = get_durable_term_type(RHS, Vst1), + T1 = get_term_type(LHS, Vst1), + T2 = get_term_type(RHS, Vst1), Vst = update_type(fun meet/2, T2, LHS, Vst1), update_type(fun meet/2, T1, RHS, Vst). @@ -1540,102 +1693,69 @@ update_eq_types(LHS, RHS, Vst0) -> %% Helper functions for the above. assign_1(Src, Dst, Vst0) -> - Type = get_move_term_type(Src, Vst0), - Def = get_def(Src, Vst0), - - Vst = set_type_reg_expr(Type, Def, Dst, Vst0), - - #vst{current=St0} = Vst, - #st{aliases=Aliases0} = St0, - - Aliases = Aliases0#{Src=>Dst,Dst=>Src}, - - St = St0#st{aliases=Aliases}, - Vst#vst{current=St}. - -set_aliased_type(Type, Reg, #vst{current=#st{aliases=Aliases}}=Vst0) -> - Vst1 = set_type(Type, Reg, Vst0), - case Aliases of - #{Reg:=OtherReg} -> - Vst = set_type_reg(Type, OtherReg, Vst1), - #vst{current=St} = Vst, - Vst#vst{current=St#st{aliases=Aliases}}; + assert_movable(Src, Vst0), + Vst = propagate_fragility(Dst, [Src], Vst0), + set_reg_vref(get_reg_vref(Src, Vst), Dst, Vst). + +set_reg_vref(Ref, {x,_}=Dst, Vst) -> + check_limit(Dst), + #vst{current=#st{xs=Xs0}=St0} = Vst, + St = St0#st{xs=Xs0#{ Dst => Ref }}, + Vst#vst{current=St}; +set_reg_vref(Ref, {y,_}=Dst, #vst{current=#st{ys=Ys0}=St0} = Vst) -> + check_limit(Dst), + case Ys0 of + #{ Dst := {catchtag,_}=Tag } -> + error(Tag); + #{ Dst := {trytag,_}=Tag } -> + error(Tag); + #{ Dst := _ } -> + St = St0#st{ys=Ys0#{ Dst => Ref }}, + Vst#vst{current=St}; #{} -> - Vst1 + %% Storing into a non-existent Y register means that we haven't set + %% up a (sufficiently large) stack. + error({invalid_store, Dst}) end. -kill_aliases(Reg, #st{aliases=Aliases0}=St) -> - case Aliases0 of - #{Reg:=OtherReg} -> - Aliases = maps:without([Reg,OtherReg], Aliases0), - St#st{aliases=Aliases}; +get_reg_vref({x,_}=Src, #vst{current=#st{xs=Xs}}) -> + check_limit(Src), + case Xs of + #{ Src := #value_ref{}=Ref } -> + Ref; #{} -> - St + error({uninitialized_reg, Src}) + end; +get_reg_vref({y,_}=Src, #vst{current=#st{ys=Ys}}) -> + check_limit(Src), + case Ys of + #{ Src := #value_ref{}=Ref } -> + Ref; + #{ Src := initialized } -> + error({unassigned, Src}); + #{ Src := Tag } when Tag =/= uninitialized -> + error(Tag); + #{} -> + error({uninitialized_reg, Src}) end. -set_type(Type, {x,_}=Reg, Vst) -> - set_type_reg(Type, Reg, Reg, Vst); -set_type(Type, {y,_}=Reg, Vst) -> - set_type_reg(Type, Reg, Reg, Vst); -set_type(_, _, #vst{}=Vst) -> Vst. - -set_type_reg(Type, Src, Dst, Vst) -> - case get_raw_type(Src, Vst) of - uninitialized -> - error({uninitialized_reg, Src}); - {fragile,_} -> - set_type_reg(make_fragile(Type), Dst, Vst); - _ -> - set_type_reg(Type, Dst, Vst) +set_type(Type, #value_ref{}=Ref, #vst{current=#st{vs=Vs0}=St}=Vst) -> + case Vs0 of + #{ Ref := #value{}=Entry } -> + Vs = Vs0#{ Ref => Entry#value{type=Type} }, + Vst#vst{current=St#st{vs=Vs}}; + #{} -> + %% Dead references may happen during type inference and are not an + %% error in and of themselves. If a problem were to arise from this + %% it'll explode elsewhere. + Vst end. -set_type_reg(Type, Reg, Vst) -> - set_type_reg_expr(Type, none, Reg, Vst). - -set_type_reg_expr(Type, Expr, {x,_}=Reg, Vst) -> - set_type_x(Type, Expr, Reg, Vst); -set_type_reg_expr(Type, Expr, Reg, Vst) -> - set_type_y(Type, Expr, Reg, Vst). - -set_type_x(Type, Expr, {x,X}=Reg, #vst{current=#st{x=Xs0,defs=Defs0}=St0}=Vst) - when is_integer(X), 0 =< X -> - check_limit(Reg), - Xs = case gb_trees:lookup(X, Xs0) of - none -> - gb_trees:insert(X, Type, Xs0); - {value,{fragile,_}} -> - gb_trees:update(X, make_fragile(Type), Xs0); - {value,_} -> - gb_trees:update(X, Type, Xs0) - end, - Defs = Defs0#{Reg=>Expr}, - St = kill_aliases(Reg, St0), - Vst#vst{current=St#st{x=Xs,defs=Defs}}; -set_type_x(Type, _Expr, Reg, #vst{}) -> - error({invalid_store,Reg,Type}). - -set_type_y(Type, Expr, {y,Y}=Reg, #vst{current=#st{y=Ys0,defs=Defs0}=St0}=Vst) - when is_integer(Y), 0 =< Y -> - check_limit(Reg), - Ys = case gb_trees:lookup(Y, Ys0) of - none -> - error({invalid_store,Reg,Type}); - {value,{catchtag,_}=Tag} -> - error(Tag); - {value,{trytag,_}=Tag} -> - error(Tag); - {value,_} -> - gb_trees:update(Y, Type, Ys0) - end, - check_try_catch_tags(Type, Reg, Vst), - Defs = Defs0#{Reg=>Expr}, - St = kill_aliases(Reg, St0), - Vst#vst{current=St#st{y=Ys,defs=Defs}}; -set_type_y(Type, _Expr, Reg, #vst{}) -> - error({invalid_store,Reg,Type}). - -make_fragile({fragile,_}=Type) -> Type; -make_fragile(Type) -> {fragile,Type}. +new_value(Type, Op, Ss, #vst{current=#st{vs=Vs0}=St,ref_ctr=Counter}=Vst) -> + Ref = #value_ref{id=Counter}, + Vs = Vs0#{ Ref => #value{op=Op,args=Ss,type=Type} }, + + {Ref, Vst#vst{current=St#st{vs=Vs},ref_ctr=Counter+1}}. kill_catch_tag(Reg, #vst{current=#st{ct=[Fail|Fails]}=St}=Vst0) -> Vst = Vst0#vst{current=St#st{ct=Fails,fls=undefined}}, @@ -1657,25 +1777,17 @@ check_try_catch_tags(Type, {y,N}=Reg, Vst) -> ok end. -is_reg_defined({x,_}=Reg, Vst) -> is_type_defined_x(Reg, Vst); -is_reg_defined({y,_}=Reg, Vst) -> is_type_defined_y(Reg, Vst); +is_reg_defined({x,_}=Reg, #vst{current=#st{xs=Xs}}) -> is_map_key(Reg, Xs); +is_reg_defined({y,_}=Reg, #vst{current=#st{ys=Ys}}) -> is_map_key(Reg, Ys); is_reg_defined(V, #vst{}) -> error({not_a_register, V}). -is_type_defined_x({x,X}, #vst{current=#st{x=Xs}}) -> - gb_trees:is_defined(X,Xs). - -is_type_defined_y({y,Y}, #vst{current=#st{y=Ys}}) -> - gb_trees:is_defined(Y,Ys). - assert_term(Src, Vst) -> - get_term_type(Src, Vst), + _ = get_term_type(Src, Vst), ok. -assert_not_fragile(Src, Vst) -> - case get_term_type(Src, Vst) of - {fragile, _} -> error({fragile_message_reference, Src}); - _ -> ok - end. +assert_movable(Src, Vst) -> + _ = get_move_term_type(Src, Vst), + ok. assert_literal(nil) -> ok; assert_literal({atom,A}) when is_atom(A) -> ok; @@ -1753,16 +1865,106 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}). %% %% none A conflict in types. There will be an exception at runtime. %% -%% FRAGILITY -%% --------- -%% -%% The loop_rec/2 instruction may return a reference to a term that is -%% not part of the root set. That term or any part of it must not be -%% included in a garbage collection. Therefore, the term (or any part -%% of it) must not be stored in an Y register. -%% -%% Such terms are wrapped in a {fragile,Type} tuple, where Type is one -%% of the types described above. + +%% join(Type1, Type2) -> Type +%% Return the most specific type possible. +join(Same, Same) -> + Same; +join(none, Other) -> + Other; +join(Other, none) -> + Other; +join({literal,_}=T1, T2) -> + join_literal(T1, T2); +join(T1, {literal,_}=T2) -> + join_literal(T2, T1); +join({tuple,Size,EsA}, {tuple,Size,EsB}) -> + Es = join_tuple_elements(tuple_sz(Size), EsA, EsB), + {tuple, Size, Es}; +join({tuple,A,EsA}, {tuple,B,EsB}) -> + Size = min(tuple_sz(A), tuple_sz(B)), + Es = join_tuple_elements(Size, EsA, EsB), + {tuple, [Size], Es}; +join({Type,A}, {Type,B}) + when Type =:= atom; Type =:= integer; Type =:= float -> + if A =:= B -> {Type,A}; + true -> {Type,[]} + end; +join({Type,_}, number) + when Type =:= integer; Type =:= float -> + number; +join(number, {Type,_}) + when Type =:= integer; Type =:= float -> + number; +join({integer,_}, {float,_}) -> + number; +join({float,_}, {integer,_}) -> + number; +join(bool, {atom,A}) -> + join_bool(A); +join({atom,A}, bool) -> + join_bool(A); +join({atom,A}, {atom,B}) when is_boolean(A), is_boolean(B) -> + bool; +join({atom,_}, {atom,_}) -> + {atom,[]}; +join(#ms{id=Id1,valid=B1,slots=Slots1}, + #ms{id=Id2,valid=B2,slots=Slots2}) -> + Id = if + Id1 =:= Id2 -> Id1; + true -> make_ref() + end, + #ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)}; +join(T1, T2) when T1 =/= T2 -> + %% We've exhaused all other options, so the type must either be a list or + %% a 'term'. + join_list(T1, T2). + +join_tuple_elements(Limit, EsA, EsB) -> + Es0 = join_elements(EsA, EsB), + maps:filter(fun({integer,Index}, _Type) -> Index =< Limit end, Es0). + +join_elements(Es1, Es2) -> + Keys = if + map_size(Es1) =< map_size(Es2) -> maps:keys(Es1); + map_size(Es1) > map_size(Es2) -> maps:keys(Es2) + end, + join_elements_1(Keys, Es1, Es2, #{}). + +join_elements_1([Key | Keys], Es1, Es2, Acc0) -> + Type = case {Es1, Es2} of + {#{ Key := Same }, #{ Key := Same }} -> Same; + {#{ Key := Type1 }, #{ Key := Type2 }} -> join(Type1, Type2); + {#{}, #{}} -> term + end, + Acc = set_element_type(Key, Type, Acc0), + join_elements_1(Keys, Es1, Es2, Acc); +join_elements_1([], _Es1, _Es2, Acc) -> + Acc. + +%% Joins types of literals; note that the left argument must either be a +%% literal or exactly equal to the second argument. +join_literal(Same, Same) -> + Same; +join_literal({literal,_}=Lit, T) -> + join_literal(T, get_literal_type(Lit)); +join_literal(T1, T2) -> + %% We're done extracting the types, try merging them again. + join(T1, T2). + +join_list(nil, cons) -> list; +join_list(nil, list) -> list; +join_list(cons, list) -> list; +join_list(T, nil) -> join_list(nil, T); +join_list(T, cons) -> join_list(cons, T); +join_list(_, _) -> + %% Not a list, so it must be a term. + term. + +join_bool([]) -> {atom,[]}; +join_bool(true) -> bool; +join_bool(false) -> bool; +join_bool(_) -> {atom,[]}. %% meet(Type1, Type2) -> Type %% Return the meet of two types. The meet is a more specific type. @@ -1770,14 +1972,23 @@ assert_not_literal(Literal) -> error({literal_not_allowed,Literal}). meet(Same, Same) -> Same; -meet({literal,_}=T1, T2) -> - meet_literal(T1, T2); -meet(T1, {literal,_}=T2) -> - meet_literal(T2, T1); meet(term, Other) -> Other; meet(Other, term) -> Other; +meet(#ms{}, binary) -> + #ms{}; +meet(binary, #ms{}) -> + #ms{}; +meet({literal,_}, {literal,_}) -> + none; +meet(T1, {literal,_}=T2) -> + meet(T2, T1); +meet({literal,_}=T1, T2) -> + case meet(get_literal_type(T1), T2) of + none -> none; + _ -> T1 + end; meet(T1, T2) -> case {erlang:min(T1, T2),erlang:max(T1, T2)} of {{atom,_}=A,{atom,[]}} -> A; @@ -1810,13 +2021,6 @@ meet(T1, T2) -> {_,_} -> none end. -%% Meets types of literals. -meet_literal({literal,_}=Lit, T) -> - meet_literal(T, get_literal_type(Lit)); -meet_literal(T1, T2) -> - %% We're done extracting the types, try merging them again. - meet(T1, T2). - meet_elements(Es1, Es2) -> Keys = maps:keys(Es1) ++ maps:keys(Es2), meet_elements_1(Keys, Es1, Es2, #{}). @@ -1838,7 +2042,7 @@ meet_elements_1([], _Es1, _Es2, Acc) -> %% No tuple elements may have an index above the known size. assert_tuple_elements(Limit, Es) -> - true = maps:fold(fun(Index, _T, true) -> + true = maps:fold(fun({integer,Index}, _T, true) -> Index =< Limit end, true, Es). %Assertion. @@ -1855,7 +2059,7 @@ subtract(bool, {atom,true}) -> {atom, false}; subtract(Type, _) -> Type. assert_type(WantedType, Term, Vst) -> - Type = get_durable_term_type(Term, Vst), + Type = get_term_type(Term, Vst), assert_type(WantedType, Type). assert_type(Correct, Correct) -> ok; @@ -1876,11 +2080,11 @@ assert_type(Needed, Actual) -> error({bad_type,{needed,Needed},{actual,Actual}}). get_element_type(Key, Src, Vst) -> - get_element_type_1(Key, get_durable_term_type(Src, Vst)). + get_element_type_1(Key, get_term_type(Src, Vst)). -get_element_type_1(Index, {tuple,Sz,Es}) -> +get_element_type_1({integer,Index}=Key, {tuple,Sz,Es}) -> case Es of - #{ Index := Type } -> Type; + #{ Key := Type } -> Type; #{} when Index =< Sz -> term; #{} -> none end; @@ -1912,17 +2116,6 @@ get_term_type(Src, Vst) -> Type -> Type end. -%% get_durable_term_type(Src, ValidatorState) -> Type -%% Get the type of the source Src. The returned type Type will be -%% a standard Erlang type (no catch/try tags or match contexts). -%% Fragility will be stripped. - -get_durable_term_type(Src, Vst) -> - case get_term_type(Src, Vst) of - {fragile,Type} -> Type; - Type -> Type - end. - %% get_move_term_type(Src, ValidatorState) -> Type %% Get the type of the source Src. The returned type Type will be %% a standard Erlang type (no catch/try tags). Match contexts are OK. @@ -1953,25 +2146,27 @@ get_tag_type(Src, _) -> %% get_raw_type(Src, ValidatorState) -> Type %% Return the type of a register without doing any validity checks. -get_raw_type({x,X}, #vst{current=#st{x=Xs}}) when is_integer(X) -> - case gb_trees:lookup(X, Xs) of - {value,Type} -> Type; - none -> uninitialized +get_raw_type({x,X}=Src, #vst{current=#st{xs=Xs}}=Vst) when is_integer(X) -> + check_limit(Src), + case Xs of + #{ Src := #value_ref{}=Ref } -> get_raw_type(Ref, Vst); + #{} -> uninitialized end; -get_raw_type({y,Y}, #vst{current=#st{y=Ys}}) when is_integer(Y) -> - case gb_trees:lookup(Y, Ys) of - {value,Type} -> Type; - none -> uninitialized +get_raw_type({y,Y}=Src, #vst{current=#st{ys=Ys}}=Vst) when is_integer(Y) -> + check_limit(Src), + case Ys of + #{ Src := #value_ref{}=Ref } -> get_raw_type(Ref, Vst); + #{ Src := Tag } -> Tag; + #{} -> uninitialized + end; +get_raw_type(#value_ref{}=Ref, #vst{current=#st{vs=Vs}}) -> + case Vs of + #{ Ref := #value{type=Type} } -> Type; + #{} -> none end; get_raw_type(Src, #vst{}) -> get_literal_type(Src). -get_def(Src, #vst{current=#st{defs=Defs}}) -> - case Defs of - #{Src:=Def} -> Def; - #{} -> none - end. - get_literal_type(nil=T) -> T; get_literal_type({atom,A}=T) when is_atom(A) -> T; get_literal_type({float,F}=T) when is_float(F) -> T; @@ -1979,22 +2174,23 @@ get_literal_type({integer,I}=T) when is_integer(I) -> T; get_literal_type({literal,[_|_]}) -> cons; get_literal_type({literal,Bitstring}) when is_bitstring(Bitstring) -> binary; get_literal_type({literal,Map}) when is_map(Map) -> map; -get_literal_type({literal,Tuple}) when is_tuple(Tuple) -> value_to_type(Tuple); +get_literal_type({literal,Tuple}) when is_tuple(Tuple) -> glt_1(Tuple); get_literal_type({literal,_}) -> term; get_literal_type(T) -> error({not_literal,T}). -value_to_type([]) -> nil; -value_to_type(A) when is_atom(A) -> {atom, A}; -value_to_type(F) when is_float(F) -> {float, F}; -value_to_type(I) when is_integer(I) -> {integer, I}; -value_to_type(T) when is_tuple(T) -> - {Es,_} = foldl(fun(Val, {Es0, Index}) -> - Type = value_to_type(Val), - Es = set_element_type(Index, Type, Es0), - {Es, Index + 1} - end, {#{}, 1}, tuple_to_list(T)), - {tuple, tuple_size(T), Es}; -value_to_type(L) -> {literal, L}. +glt_1([]) -> nil; +glt_1(A) when is_atom(A) -> {atom, A}; +glt_1(F) when is_float(F) -> {float, F}; +glt_1(I) when is_integer(I) -> {integer, I}; +glt_1(T) when is_tuple(T) -> + {Es,_} = foldl(fun(Val, {Es0, Index}) -> + Type = glt_1(Val), + Es = set_element_type({integer,Index}, Type, Es0), + {Es, Index + 1} + end, {#{}, 1}, tuple_to_list(T)), + {tuple, tuple_size(T), Es}; +glt_1(L) -> + {literal, L}. branch_state(0, #vst{}=Vst) -> %% If the instruction fails, the stack may be scanned @@ -2002,36 +2198,134 @@ branch_state(0, #vst{}=Vst) -> %% must be initialized at this point. verify_y_init(Vst), Vst; -branch_state(L, #vst{current=St,branched=B}=Vst) -> - Vst#vst{ - branched=case gb_trees:is_defined(L, B) of - false -> - gb_trees:insert(L, St, B); - true -> - MergedSt = merge_states(L, St, B), - gb_trees:update(L, MergedSt, B) - end}. - -%% merge_states/3 is used when there are more than one way to arrive -%% at this point, and the type states for the different paths has -%% to be merged. The type states are downgraded to the least common -%% subset for the subsequent code. - -merge_states(L, St, Branched) when L =/= 0 -> +branch_state(L, #vst{current=St,branched=B,ref_ctr=Counter0}=Vst) -> + case gb_trees:is_defined(L, B) of + true -> + {MergedSt, Counter} = merge_states(L, St, B, Counter0), + Branched = gb_trees:update(L, MergedSt, B), + Vst#vst{branched=Branched,ref_ctr=Counter}; + false -> + Vst#vst{branched=gb_trees:insert(L, St, B)} + end. + +%% merge_states/3 is used when there's more than one way to arrive at a +%% certain point, requiring the states to be merged down to the least +%% common subset for the subsequent code. + +merge_states(L, St, Branched, Counter) when L =/= 0 -> case gb_trees:lookup(L, Branched) of - none -> St; - {value,OtherSt} when St =:= none -> OtherSt; - {value,OtherSt} -> merge_states_1(St, OtherSt) + none -> + {St, Counter}; + {value,OtherSt} when St =:= none -> + {OtherSt, Counter}; + {value,OtherSt} -> + merge_states_1(St, OtherSt, Counter) end. -merge_states_1(#st{x=Xs0,y=Ys0,numy=NumY0,h=H0,ct=Ct0,aliases=Aliases0}, - #st{x=Xs1,y=Ys1,numy=NumY1,h=H1,ct=Ct1,aliases=Aliases1}) -> - NumY = merge_stk(NumY0, NumY1), - Xs = merge_regs(Xs0, Xs1), - Ys = merge_y_regs(Ys0, Ys1), - Ct = merge_ct(Ct0, Ct1), - Aliases = merge_aliases(Aliases0, Aliases1), - #st{x=Xs,y=Ys,numy=NumY,h=min(H0, H1),ct=Ct,aliases=Aliases}. +merge_states_1(#st{xs=XsA,ys=YsA,vs=VsA,fragile=FragA,numy=NumYA,h=HA,ct=CtA}, + #st{xs=XsB,ys=YsB,vs=VsB,fragile=FragB,numy=NumYB,h=HB,ct=CtB}, + Counter0) -> + %% When merging registers we drop all registers that aren't defined in both + %% states, and resolve conflicts by creating new values (similar to phi + %% nodes in SSA). + %% + %% While doing this we build a "merge map" detailing which values need to + %% be kept and which new values need to be created to resolve conflicts. + %% This map is then used to create a new value database where the types of + %% all values have been joined. + {Xs, Merge0, Counter1} = merge_regs(XsA, XsB, #{}, Counter0), + {Ys, Merge, Counter} = merge_regs(YsA, YsB, Merge0, Counter1), + Vs = merge_values(Merge, VsA, VsB), + + Fragile = merge_fragility(FragA, FragB), + NumY = merge_stk(NumYA, NumYB), + Ct = merge_ct(CtA, CtB), + + St = #st{xs=Xs,ys=Ys,vs=Vs,fragile=Fragile,numy=NumY,h=min(HA, HB),ct=Ct}, + {St, Counter}. + +%% Merges the contents of two register maps, returning the updated "merge map" +%% and the new registers. +merge_regs(RsA, RsB, Merge, Counter) -> + Keys = if + map_size(RsA) =< map_size(RsB) -> maps:keys(RsA); + map_size(RsA) > map_size(RsB) -> maps:keys(RsB) + end, + merge_regs_1(Keys, RsA, RsB, #{}, Merge, Counter). + +merge_regs_1([Reg | Keys], RsA, RsB, Regs, Merge0, Counter0) -> + case {RsA, RsB} of + {#{ Reg := #value_ref{}=RefA }, #{ Reg := #value_ref{}=RefB }} -> + {Ref, Merge, Counter} = merge_vrefs(RefA, RefB, Merge0, Counter0), + merge_regs_1(Keys, RsA, RsB, Regs#{ Reg => Ref }, Merge, Counter); + {#{ Reg := TagA }, #{ Reg := TagB }} -> + %% Tags describe the state of the register rather than the value it + %% contains, so if a register contains a tag in one state we have + %% to merge it as a tag regardless of whether the other state says + %% it's a value. + {y, _} = Reg, %Assertion. + merge_regs_1(Keys, RsA, RsB, Regs#{ Reg => merge_tags(TagA,TagB) }, + Merge0, Counter0); + {#{}, #{}} -> + merge_regs_1(Keys, RsA, RsB, Regs, Merge0, Counter0) + end; +merge_regs_1([], _, _, Regs, Merge, Counter) -> + {Regs, Merge, Counter}. + +merge_tags(Same, Same) -> + Same; +merge_tags(uninitialized, _) -> + uninitialized; +merge_tags(_, uninitialized) -> + uninitialized; +merge_tags({catchtag,T0}, {catchtag,T1}) -> + {catchtag, ordsets:from_list(T0 ++ T1)}; +merge_tags({trytag,T0}, {trytag,T1}) -> + {trytag, ordsets:from_list(T0 ++ T1)}; +merge_tags(_A, _B) -> + %% All other combinations leave the register initialized. Errors arising + %% from this will be caught later on. + initialized. + +merge_vrefs(Ref, Ref, Merge, Counter) -> + %% We have two (potentially) different versions of the same value, so we + %% should join their types into the same value. + {Ref, Merge#{ Ref => Ref }, Counter}; +merge_vrefs(RefA, RefB, Merge, Counter) -> + %% We have two different values, so we need to create a new value from + %% their joined type if we haven't already done so. + Key = {RefA, RefB}, + case Merge of + #{ Key := Ref } -> + {Ref, Merge, Counter}; + #{} -> + Ref = #value_ref{id=Counter}, + {Ref, Merge#{ Key => Ref }, Counter + 1} + end. + +merge_values(Merge, VsA, VsB) -> + maps:fold(fun(Spec, New, Acc) -> + merge_values_1(Spec, New, VsA, VsB, Acc) + end, #{}, Merge). + +merge_values_1(Same, Same, VsA, VsB, Acc) -> + %% We're merging different versions of the same value, so it's safe to + %% reuse old entries if the type's unchanged. + #value{type=TypeA}=EntryA = map_get(Same, VsA), + #value{type=TypeB}=EntryB = map_get(Same, VsB), + Entry = case join(TypeA, TypeB) of + TypeA -> EntryA; + TypeB -> EntryB; + JoinedType -> EntryA#value{type=JoinedType} + end, + Acc#{ Same => Entry }; +merge_values_1({RefA, RefB}, New, VsA, VsB, Acc) -> + #value{type=TypeA} = map_get(RefA, VsA), + #value{type=TypeB} = map_get(RefB, VsB), + Acc#{ New => #value{op=join,args=[],type=join(TypeA, TypeB)} }. + +merge_fragility(FragileA, FragileB) -> + cerl_sets:union(FragileA, FragileB). merge_stk(S, S) -> S; merge_stk(_, _) -> undecided. @@ -2044,174 +2338,17 @@ merge_ct_1([C0|Ct0], [C1|Ct1]) -> merge_ct_1([], []) -> []; merge_ct_1(_, _) -> undecided. -merge_regs(Rs0, Rs1) -> - Rs = merge_regs_1(gb_trees:to_list(Rs0), gb_trees:to_list(Rs1)), - gb_trees_from_list(Rs). - -merge_regs_1([Same|Rs1], [Same|Rs2]) -> - [Same|merge_regs_1(Rs1, Rs2)]; -merge_regs_1([{R1,_}|Rs1], [{R2,_}|_]=Rs2) when R1 < R2 -> - merge_regs_1(Rs1, Rs2); -merge_regs_1([{R1,_}|_]=Rs1, [{R2,_}|Rs2]) when R1 > R2 -> - merge_regs_1(Rs1, Rs2); -merge_regs_1([{R,Type1}|Rs1], [{R,Type2}|Rs2]) -> - [{R,join(Type1, Type2)}|merge_regs_1(Rs1, Rs2)]; -merge_regs_1([], []) -> []; -merge_regs_1([], [_|_]) -> []; -merge_regs_1([_|_], []) -> []. - -merge_y_regs(Rs0, Rs1) -> - case {gb_trees:size(Rs0),gb_trees:size(Rs1)} of - {Sz0,Sz1} when Sz0 < Sz1 -> - merge_y_regs_1(Sz0-1, Rs1, Rs0); - {_,Sz1} -> - merge_y_regs_1(Sz1-1, Rs0, Rs1) - end. - -merge_y_regs_1(Y, S, Regs0) when Y >= 0 -> - Type0 = gb_trees:get(Y, Regs0), - case gb_trees:get(Y, S) of - Type0 -> - merge_y_regs_1(Y-1, S, Regs0); - Type1 -> - Type = join(Type0, Type1), - Regs = gb_trees:update(Y, Type, Regs0), - merge_y_regs_1(Y-1, S, Regs) - end; -merge_y_regs_1(_, _, Regs) -> Regs. - -%% join(Type1, Type2) -> Type -%% Return the most specific type possible. -%% Note: Type1 must NOT be the same as Type2. -join({literal,_}=T1, T2) -> - join_literal(T1, T2); -join(T1, {literal,_}=T2) -> - join_literal(T2, T1); -join({fragile,Same}=Type, Same) -> - Type; -join({fragile,T1}, T2) -> - make_fragile(join(T1, T2)); -join(Same, {fragile,Same}=Type) -> - Type; -join(T1, {fragile,T2}) -> - make_fragile(join(T1, T2)); -join(uninitialized=I, _) -> I; -join(_, uninitialized=I) -> I; -join(initialized=I, _) -> I; -join(_, initialized=I) -> I; -join({catchtag,T0},{catchtag,T1}) -> - {catchtag,ordsets:from_list(T0++T1)}; -join({trytag,T0},{trytag,T1}) -> - {trytag,ordsets:from_list(T0++T1)}; -join({tuple,Size,EsA}, {tuple,Size,EsB}) -> - Es = join_tuple_elements(tuple_sz(Size), EsA, EsB), - {tuple, Size, Es}; -join({tuple,A,EsA}, {tuple,B,EsB}) -> - Size = min(tuple_sz(A), tuple_sz(B)), - Es = join_tuple_elements(Size, EsA, EsB), - {tuple, [Size], Es}; -join({Type,A}, {Type,B}) - when Type =:= atom; Type =:= integer; Type =:= float -> - if A =:= B -> {Type,A}; - true -> {Type,[]} - end; -join({Type,_}, number) - when Type =:= integer; Type =:= float -> - number; -join(number, {Type,_}) - when Type =:= integer; Type =:= float -> - number; -join(bool, {atom,A}) -> - join_bool(A); -join({atom,A}, bool) -> - join_bool(A); -join({atom,_}, {atom,_}) -> - {atom,[]}; -join(#ms{id=Id1,valid=B1,slots=Slots1}, - #ms{id=Id2,valid=B2,slots=Slots2}) -> - Id = if - Id1 =:= Id2 -> Id1; - true -> make_ref() - end, - #ms{id=Id,valid=B1 band B2,slots=min(Slots1, Slots2)}; -join(T1, T2) when T1 =/= T2 -> - %% We've exhaused all other options, so the type must either be a list or - %% a 'term'. - join_list(T1, T2). - -join_tuple_elements(Limit, EsA, EsB) -> - Es0 = join_elements(EsA, EsB), - maps:filter(fun(Index, _Type) -> Index =< Limit end, Es0). - -join_elements(Es1, Es2) -> - Keys = if - map_size(Es1) =< map_size(Es2) -> maps:keys(Es1); - map_size(Es1) > map_size(Es2) -> maps:keys(Es2) - end, - join_elements_1(Keys, Es1, Es2, #{}). - -join_elements_1([Key | Keys], Es1, Es2, Acc0) -> - Type = case {Es1, Es2} of - {#{ Key := Same }, #{ Key := Same }} -> Same; - {#{ Key := Type1 }, #{ Key := Type2 }} -> join(Type1, Type2); - {#{}, #{}} -> term - end, - Acc = set_element_type(Key, Type, Acc0), - join_elements_1(Keys, Es1, Es2, Acc); -join_elements_1([], _Es1, _Es2, Acc) -> - Acc. - -%% Joins types of literals; note that the left argument must either be a -%% literal or exactly equal to the second argument. -join_literal(Same, Same) -> - Same; -join_literal({literal,_}=Lit, T) -> - join_literal(T, get_literal_type(Lit)); -join_literal(T1, T2) -> - %% We're done extracting the types, try merging them again. - join(T1, T2). - -join_list(nil, cons) -> list; -join_list(nil, list) -> list; -join_list(cons, list) -> list; -join_list(T, nil) -> join_list(nil, T); -join_list(T, cons) -> join_list(cons, T); -join_list(_, _) -> - %% Not a list, so it must be a term. - term. - -join_bool([]) -> {atom,[]}; -join_bool(true) -> bool; -join_bool(false) -> bool; -join_bool(_) -> {atom,[]}. - tuple_sz([Sz]) -> Sz; tuple_sz(Sz) -> Sz. -merge_aliases(Al0, Al1) when map_size(Al0) =< map_size(Al1) -> - maps:filter(fun(K, V) -> - case Al1 of - #{K:=V} -> true; - #{} -> false - end - end, Al0); -merge_aliases(Al0, Al1) -> - merge_aliases(Al1, Al0). - -verify_y_init(#vst{current=#st{numy=NumY,y=Ys}}=Vst) - when is_integer(NumY), NumY > 0 -> - {HighestY, _} = gb_trees:largest(Ys), +verify_y_init(#vst{current=#st{numy=NumY,ys=Ys}}=Vst) when is_integer(NumY) -> + HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys), true = NumY > HighestY, %Assertion. verify_y_init_1(NumY - 1, Vst), ok; -verify_y_init(#vst{current=#st{numy=undecided,y=Ys}}=Vst) -> - case gb_trees:is_empty(Ys) of - true -> - ok; - false -> - {HighestY, _} = gb_trees:largest(Ys), - verify_y_init_1(HighestY, Vst) - end; +verify_y_init(#vst{current=#st{numy=undecided,ys=Ys}}=Vst) -> + HighestY = maps:fold(fun({y,Y}, _, Acc) -> max(Y, Acc) end, -1, Ys), + verify_y_init_1(HighestY, Vst); verify_y_init(#vst{}) -> ok. @@ -2219,15 +2356,10 @@ verify_y_init_1(-1, _Vst) -> ok; verify_y_init_1(Y, Vst) -> Reg = {y, Y}, + assert_not_fragile(Reg, Vst), case get_raw_type(Reg, Vst) of - uninitialized -> - error({uninitialized_reg,Reg}); - {fragile, _} -> - %% Unsafe. This term may be outside any heap belonging to the - %% process and would be corrupted by a GC. - error({fragile_message_reference,Reg}); - _ -> - verify_y_init_1(Y - 1, Vst) + uninitialized -> error({uninitialized_reg,Reg}); + _ -> verify_y_init_1(Y - 1, Vst) end. verify_live(0, _Vst) -> @@ -2287,27 +2419,68 @@ eat_heap_float(#vst{current=#st{hf=HeapFloats0}=St}=Vst) -> Vst#vst{current=St#st{hf=HeapFloats}} end. -remove_fragility(#vst{current=#st{x=Xs0,y=Ys0}=St0}=Vst) -> - F = fun(_, {fragile,Type}) -> Type; - (_, Type) -> Type - end, - Xs = gb_trees:map(F, Xs0), - Ys = gb_trees:map(F, Ys0), - St = St0#st{x=Xs,y=Ys}, +%%% FRAGILITY +%%% +%%% The loop_rec/2 instruction may return a reference to a term that is not +%%% part of the root set. That term or any part of it must not be included in a +%%% garbage collection. Therefore, the term (or any part of it) must not be +%%% passed to another function, placed in another term, or live in a Y register +%%% over an instruction that may GC. +%%% +%%% Fragility is marked on a per-register (rather than per-value) basis. + +%% Marks Reg as fragile. +mark_fragile(Reg, Vst) -> + #vst{current=#st{fragile=Fragile0}=St0} = Vst, + Fragile = cerl_sets:add_element(Reg, Fragile0), + St = St0#st{fragile=Fragile}, Vst#vst{current=St}. -propagate_fragility(Type, Ss, Vst) -> - F = fun(S) -> - case get_raw_type(S, Vst) of - {fragile,_} -> true; - _ -> false - end - end, - case any(F, Ss) of - true -> make_fragile(Type); - false -> Type +propagate_fragility(Reg, Args, #vst{current=St0}=Vst) -> + #st{fragile=Fragile0} = St0, + + Sources = cerl_sets:from_list(Args), + Fragile = case cerl_sets:is_disjoint(Sources, Fragile0) of + true -> cerl_sets:del_element(Reg, Fragile0); + false -> cerl_sets:add_element(Reg, Fragile0) + end, + + St = St0#st{fragile=Fragile}, + Vst#vst{current=St}. + +%% Marks Reg as durable, must be used when assigning a newly created value to +%% a register. +remove_fragility(Reg, Vst) -> + #vst{current=#st{fragile=Fragile0}=St0} = Vst, + case cerl_sets:is_element(Reg, Fragile0) of + true -> + Fragile = cerl_sets:del_element(Reg, Fragile0), + St = St0#st{fragile=Fragile}, + Vst#vst{current=St}; + false -> + Vst end. +%% Marks all registers as durable. +remove_fragility(#vst{current=St0}=Vst) -> + St = St0#st{fragile=cerl_sets:new()}, + Vst#vst{current=St}. + +assert_durable_term(Src, Vst) -> + assert_term(Src, Vst), + assert_not_fragile(Src, Vst). + +assert_not_fragile({Kind,_}=Src, Vst) when Kind =:= x; Kind =:= y -> + check_limit(Src), + #vst{current=#st{fragile=Fragile}} = Vst, + case cerl_sets:is_element(Src, Fragile) of + true -> error({fragile_message_reference, Src}); + false -> ok + end; +assert_not_fragile(Lit, #vst{}) -> + assert_literal(Lit), + ok. + %%% %%% Return/argument types of BIFs %%% @@ -2319,7 +2492,7 @@ bif_return_type('+', Src, Vst) -> bif_return_type('*', Src, Vst) -> arith_return_type(Src, Vst); bif_return_type(abs, [Num], Vst) -> - case get_durable_term_type(Num, Vst) of + case get_term_type(Num, Vst) of {float,_}=T -> T; {integer,_}=T -> T; _ -> number @@ -2327,8 +2500,10 @@ bif_return_type(abs, [Num], Vst) -> bif_return_type(float, _, _) -> {float,[]}; bif_return_type('/', _, _) -> {float,[]}; %% Binary operations -bif_return_type('byte_size', _, _) -> {integer,[]}; -bif_return_type('bit_size', _, _) -> {integer,[]}; +bif_return_type('binary_part', [_,_], _) -> binary; +bif_return_type('binary_part', [_,_,_], _) -> binary; +bif_return_type('bit_size', [_], _) -> {integer,[]}; +bif_return_type('byte_size', [_], _) -> {integer,[]}; %% Integer operations. bif_return_type(ceil, [_], _) -> {integer,[]}; bif_return_type('div', [_,_], _) -> {integer,[]}; @@ -2362,6 +2537,7 @@ bif_return_type(is_boolean, [_], _) -> bool; bif_return_type(is_binary, [_], _) -> bool; bif_return_type(is_float, [_], _) -> bool; bif_return_type(is_function, [_], _) -> bool; +bif_return_type(is_function, [_,_], _) -> bool; bif_return_type(is_integer, [_], _) -> bool; bif_return_type(is_list, [_], _) -> bool; bif_return_type(is_map, [_], _) -> bool; @@ -2372,6 +2548,7 @@ bif_return_type(is_reference, [_], _) -> bool; bif_return_type(is_tuple, [_], _) -> bool; %% Misc. bif_return_type(tuple_size, [_], _) -> {integer,[]}; +bif_return_type(map_size, [_], _) -> {integer,[]}; bif_return_type(node, [], _) -> {atom,[]}; bif_return_type(node, [_], _) -> {atom,[]}; bif_return_type(hd, [_], _) -> term; @@ -2393,14 +2570,24 @@ bif_arg_types('and', [_,_]) -> [bool, bool]; bif_arg_types('or', [_,_]) -> [bool, bool]; bif_arg_types('xor', [_,_]) -> [bool, bool]; %% Binary -bif_arg_types('byte_size', [_]) -> [binary]; +bif_arg_types('binary_part', [_,_]) -> + PosLen = {tuple, 2, #{ {integer,1} => {integer,[]}, + {integer,2} => {integer,[]} }}, + [binary, PosLen]; +bif_arg_types('binary_part', [_,_,_]) -> + [binary, {integer,[]}, {integer,[]}]; bif_arg_types('bit_size', [_]) -> [binary]; +bif_arg_types('byte_size', [_]) -> [binary]; %% Numerical bif_arg_types('-', [_]) -> [number]; +bif_arg_types('-', [_,_]) -> [number,number]; bif_arg_types('+', [_]) -> [number]; +bif_arg_types('+', [_,_]) -> [number,number]; bif_arg_types('*', [_,_]) -> [number, number]; bif_arg_types('/', [_,_]) -> [number, number]; +bif_arg_types(abs, [_]) -> [number]; bif_arg_types(ceil, [_]) -> [number]; +bif_arg_types(float, [_]) -> [number]; bif_arg_types(floor, [_]) -> [number]; bif_arg_types(trunc, [_]) -> [number]; bif_arg_types(round, [_]) -> [number]; @@ -2446,14 +2633,14 @@ is_bif_safe(_, _) -> false. arith_return_type([A], Vst) -> %% Unary '+' or '-'. - case get_durable_term_type(A, Vst) of + case get_term_type(A, Vst) of {integer,_} -> {integer,[]}; {float,_} -> {float,[]}; _ -> number end; arith_return_type([A,B], Vst) -> - TypeA = get_durable_term_type(A, Vst), - TypeB = get_durable_term_type(B, Vst), + TypeA = get_term_type(A, Vst), + TypeB = get_term_type(B, Vst), case {TypeA, TypeB} of {{integer,_},{integer,_}} -> {integer,[]}; {{float,_},_} -> {float,[]}; @@ -2482,7 +2669,7 @@ call_return_type_1(erlang, setelement, 3, Vst) -> case meet({tuple,[I],#{}}, TupleType) of {tuple, Sz, Es0} -> ValueType = get_term_type({x,2}, Vst), - Es = set_element_type(I, ValueType, Es0), + Es = set_element_type({integer,I}, ValueType, Es0), {tuple, Sz, Es}; none -> TupleType @@ -2542,19 +2729,33 @@ math_mod_return_type(fmod, 2) -> {float,[]}; math_mod_return_type(pi, 0) -> {float,[]}; math_mod_return_type(F, A) when is_atom(F), is_integer(A), A >= 0 -> term. +lists_mod_return_type(all, 2, _Vst) -> + bool; +lists_mod_return_type(any, 2, _Vst) -> + bool; +lists_mod_return_type(keymember, 3, _Vst) -> + bool; +lists_mod_return_type(member, 2, _Vst) -> + bool; +lists_mod_return_type(prefix, 2, _Vst) -> + bool; +lists_mod_return_type(suffix, 2, _Vst) -> + bool; lists_mod_return_type(dropwhile, 2, _Vst) -> list; lists_mod_return_type(duplicate, 2, _Vst) -> list; lists_mod_return_type(filter, 2, _Vst) -> list; +lists_mod_return_type(flatten, 1, _Vst) -> + list; lists_mod_return_type(flatten, 2, _Vst) -> list; lists_mod_return_type(map, 2, Vst) -> same_length_type({x,1}, Vst); lists_mod_return_type(MF, 3, Vst) when MF =:= mapfoldl; MF =:= mapfoldr -> ListType = same_length_type({x,2}, Vst), - {tuple,2,#{1=>ListType}}; + {tuple,2,#{ {integer,1} => ListType} }; lists_mod_return_type(partition, 2, _Vst) -> two_tuple(list, list); lists_mod_return_type(reverse, 1, Vst) -> @@ -2590,7 +2791,8 @@ lists_mod_return_type(_, _, _) -> term. two_tuple(Type1, Type2) -> - {tuple,2,#{1=>Type1,2=>Type2}}. + {tuple,2,#{ {integer,1} => Type1, + {integer,2} => Type2 }}. same_length_type(Reg, Vst) -> case get_term_type(Reg, Vst) of @@ -2600,15 +2802,25 @@ same_length_type(Reg, Vst) -> _ -> list end. -check_limit({x,X}) when is_integer(X), X < 1023 -> - %% Note: x(1023) is reserved for use by the BEAM loader. - ok; -check_limit({y,Y}) when is_integer(Y), Y < 1024 -> - ok; -check_limit({fr,Fr}) when is_integer(Fr), Fr < 1024 -> - ok; -check_limit(_) -> - error(limit). +check_limit({x,X}=Src) when is_integer(X) -> + if + %% Note: x(1023) is reserved for use by the BEAM loader. + 0 =< X, X < 1023 -> ok; + 1023 =< X -> error(limit); + X < 0 -> error({bad_register, Src}) + end; +check_limit({y,Y}=Src) when is_integer(Y) -> + if + 0 =< Y, Y < 1024 -> ok; + 1024 =< Y -> error(limit); + Y < 0 -> error({bad_register, Src}) + end; +check_limit({fr,Fr}=Src) when is_integer(Fr) -> + if + 0 =< Fr, Fr < 1023 -> ok; + 1023 =< Fr -> error(limit); + Fr < 0 -> error({bad_register, Src}) + end. min(A, B) when is_integer(A), is_integer(B), A < B -> A; min(A, B) when is_integer(A), is_integer(B) -> B. diff --git a/lib/compiler/src/cerl_sets.erl b/lib/compiler/src/cerl_sets.erl index 0361186713..f489baf238 100644 --- a/lib/compiler/src/cerl_sets.erl +++ b/lib/compiler/src/cerl_sets.erl @@ -204,4 +204,4 @@ fold(F, Init, D) -> Set2 :: set(Element). filter(F, D) -> - maps:from_list(lists:filter(fun({K,_}) -> F(K) end, maps:to_list(D))). + maps:filter(fun(K,_) -> F(K) end, D). diff --git a/lib/compiler/src/v3_kernel.erl b/lib/compiler/src/v3_kernel.erl index 86351bc0c5..e2b8787224 100644 --- a/lib/compiler/src/v3_kernel.erl +++ b/lib/compiler/src/v3_kernel.erl @@ -1590,19 +1590,12 @@ match_var([U|Us], Cs0, Def, St) -> %% constructor/constant as first argument. Group the constructors %% according to type, the order is really irrelevant but tries to be %% smart. - -match_con(Us, Cs0, Def, St) -> - %% Expand literals at the top level. - Cs = [expand_pat_lit_clause(C) || C <- Cs0], - match_con_1(Us, Cs, Def, St). - -match_con_1([U|_Us] = L, Cs, Def, St0) -> +match_con([U|_Us] = L, Cs, Def, St0) -> %% Extract clauses for different constructors (types). %%ok = io:format("match_con ~p~n", [Cs]), - Ttcs0 = select_types([k_binary], Cs) ++ select_bin_con(Cs) ++ - select_types([k_cons,k_tuple,k_map,k_atom,k_float, - k_int,k_nil], Cs), - Ttcs = opt_single_valued(Ttcs0), + Ttcs0 = select_types(Cs, [], [], [], [], [], [], [], [], []), + Ttcs1 = [{T, Types} || {T, [_ | _] = Types} <- Ttcs0], + Ttcs = opt_single_valued(Ttcs1), %%ok = io:format("ttcs = ~p~n", [Ttcs]), {Scs,St1} = mapfoldl(fun ({T,Tcs}, St) -> @@ -1613,8 +1606,41 @@ match_con_1([U|_Us] = L, Cs, Def, St0) -> St0, Ttcs), {build_alt_1st_no_fail(build_select(U, Scs), Def),St1}. -select_types(Types, Cs) -> - [{T,Tcs} || T <- Types, begin Tcs = select(T, Cs), Tcs =/= [] end]. +select_types([NoExpC | Cs], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) -> + C = expand_pat_lit_clause(NoExpC), + case clause_con(C) of + k_binary -> + select_types(Cs, [C |Bin], BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil); + k_bin_seg -> + select_types(Cs, Bin, [C | BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil); + k_bin_end -> + select_types(Cs, Bin, [C | BinCon], Cons, Tuple, Map, Atom, Float, Int, Nil); + k_cons -> + select_types(Cs, Bin, BinCon, [C | Cons], Tuple, Map, Atom, Float, Int, Nil); + k_tuple -> + select_types(Cs, Bin, BinCon, Cons, [C | Tuple], Map, Atom, Float, Int, Nil); + k_map -> + select_types(Cs, Bin, BinCon, Cons, Tuple, [C | Map], Atom, Float, Int, Nil); + k_atom -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, [C | Atom], Float, Int, Nil); + k_float -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, [C | Float], Int, Nil); + k_int -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, [C | Int], Nil); + k_nil -> + select_types(Cs, Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, [C | Nil]) + end; +select_types([], Bin, BinCon, Cons, Tuple, Map, Atom, Float, Int, Nil) -> + [{k_binary, reverse(Bin)}] ++ handle_bin_con(reverse(BinCon)) ++ + [ + {k_cons, reverse(Cons)}, + {k_tuple, reverse(Tuple)}, + {k_map, reverse(Map)}, + {k_atom, reverse(Atom)}, + {k_float, reverse(Float)}, + {k_int, reverse(Int)}, + {k_nil, reverse(Nil)} + ]. expand_pat_lit_clause(#iclause{pats=[#ialias{pat=#k_literal{anno=A,val=Val}}=Alias|Ps]}=C) -> P = expand_pat_lit(Val, A), @@ -1743,20 +1769,12 @@ combine_bin_segs(#k_bin_end{}) -> combine_bin_segs(_) -> throw(not_possible). -%% select_bin_con([Clause]) -> [{Type,[Clause]}]. -%% Extract clauses for the k_bin_seg constructor. As k_bin_seg +%% handle_bin_con([Clause]) -> [{Type,[Clause]}]. +%% Handle clauses for the k_bin_seg constructor. As k_bin_seg %% matching can overlap, the k_bin_seg constructors cannot be %% reordered, only grouped. -select_bin_con(Cs0) -> - Cs1 = lists:filter(fun (C) -> - Con = clause_con(C), - (Con =:= k_bin_seg) or (Con =:= k_bin_end) - end, Cs0), - select_bin_con_1(Cs1). - - -select_bin_con_1(Cs) -> +handle_bin_con(Cs) -> try %% The usual way to match literals is to first extract the %% value to a register, and then compare the register to the @@ -1795,14 +1813,14 @@ select_bin_con_1(Cs) -> end catch throw:not_possible -> - select_bin_con_2(Cs) + handle_bin_con_not_possible(Cs) end. -select_bin_con_2([C1|Cs]) -> +handle_bin_con_not_possible([C1|Cs]) -> Con = clause_con(C1), {More,Rest} = splitwith(fun (C) -> clause_con(C) =:= Con end, Cs), - [{Con,[C1|More]}|select_bin_con_2(Rest)]; -select_bin_con_2([]) -> []. + [{Con,[C1|More]}|handle_bin_con_not_possible(Rest)]; +handle_bin_con_not_possible([]) -> []. %% select_bin_int([Clause]) -> {k_bin_int,[Clause]} %% If the first pattern in each clause selects the same integer, @@ -1902,10 +1920,6 @@ select_utf8(Val0) -> throw(not_possible) end. -%% select(Con, [Clause]) -> [Clause]. - -select(T, Cs) -> [ C || C <- Cs, clause_con(C) =:= T ]. - %% match_value([Var], Con, [Clause], Default, State) -> {SelectExpr,State}. %% At this point all the clauses have the same constructor, we must %% now separate them according to value. @@ -2040,6 +2054,10 @@ get_match(#k_cons{}, St0) -> get_match(#k_binary{}, St0) -> {[V]=Mes,St1} = new_vars(1, St0), {#k_binary{segs=V},Mes,St1}; +get_match(#k_bin_seg{size=#k_atom{val=all},next={k_bin_end,[]}}=Seg, St0) -> + {[S,N0],St1} = new_vars(2, St0), + N = set_kanno(N0, [no_usage]), + {Seg#k_bin_seg{seg=S,next=N},[S],St1}; get_match(#k_bin_seg{}=Seg, St0) -> {[S,N0],St1} = new_vars(2, St0), N = set_kanno(N0, [no_usage]), @@ -2067,6 +2085,9 @@ new_clauses(Cs0, U, St) -> #k_cons{hd=H,tl=T} -> [H,T|As]; #k_tuple{es=Es} -> Es ++ As; #k_binary{segs=E} -> [E|As]; + #k_bin_seg{size=#k_atom{val=all}, + seg=S,next={k_bin_end,[]}} -> + [S|As]; #k_bin_seg{seg=S,next=N} -> [S,N|As]; #k_bin_int{next=N} -> diff --git a/lib/compiler/test/beam_jump_SUITE.erl b/lib/compiler/test/beam_jump_SUITE.erl index 759d884dc4..a456f31d79 100644 --- a/lib/compiler/test/beam_jump_SUITE.erl +++ b/lib/compiler/test/beam_jump_SUITE.erl @@ -79,12 +79,13 @@ checks(Wanted) -> {catch case river() of sheet -> begin +Wanted, if "da" -> Wanted end end end, catch case river() of sheet -> begin + Wanted, if "da" -> Wanted end end end}. unsafe_move_elimination(_Config) -> - {{left,right,false},false} = unsafe_move_elimination(left, right, false), - {{false,right,false},false} = unsafe_move_elimination(false, right, true), - {{true,right,right},right} = unsafe_move_elimination(true, right, true), + {{left,right,false},false} = unsafe_move_elimination_1(left, right, false), + {{false,right,false},false} = unsafe_move_elimination_1(false, right, true), + {{true,right,right},right} = unsafe_move_elimination_1(true, right, true), + [ok = unsafe_move_elimination_2(I) || I <- lists:seq(0,16)], ok. -unsafe_move_elimination(Left, Right, Simple0) -> +unsafe_move_elimination_1(Left, Right, Simple0) -> id(1), %% The move at label 29 would be removed by beam_jump, which is unsafe because @@ -115,6 +116,44 @@ unsafe_move_elimination(Left, Right, Simple0) -> end, {id({Left,Right,Simple}),Simple}. +unsafe_move_elimination_2(Int) -> + %% The type optimization pass would recognize that TagInt can only be + %% [0 .. 7], so the first 'case' would select_val over [0 .. 6] and swap + %% out the fail label with the block for 7. + %% + %% A later optimization would merge this block with 'expects_h' in the + %% second case, as the latter is only reachable from the former. + %% + %% ... but this broke down when the move elimination optimization didn't + %% take the fail label of the first select_val into account. This caused it + %% to believe that the only way to reach 'expects_h' was through the second + %% case when 'Tag' =:= 'h', which made it remove the move instruction + %% added in the first case, passing garbage to expects_h/2. + TagInt = Int band 2#111, + Tag = case TagInt of + 0 -> a; + 1 -> b; + 2 -> c; + 3 -> d; + 4 -> e; + 5 -> f; + 6 -> g; + 7 -> h + end, + case Tag of + g -> expects_g(TagInt, Tag); + h -> expects_h(TagInt, Tag); + _ -> Tag = id(Tag), ok + end. + +expects_g(6, Atom) -> + Atom = id(g), + ok. + +expects_h(7, Atom) -> + Atom = id(h), + ok. + -record(message2, {id, p1}). -record(message3, {id, p1, p2}). diff --git a/lib/compiler/test/beam_validator_SUITE.erl b/lib/compiler/test/beam_validator_SUITE.erl index 2660bf222c..265da43f9d 100644 --- a/lib/compiler/test/beam_validator_SUITE.erl +++ b/lib/compiler/test/beam_validator_SUITE.erl @@ -107,13 +107,12 @@ xrange(Config) when is_list(Config) -> Errors = do_val(xrange, Config), [{{t,sum_1,2}, {{bif,'+',{f,0},[{x,-1},{x,1}],{x,0}},4, - {uninitialized_reg,{x,-1}}}}, + {bad_register,{x,-1}}}}, {{t,sum_2,2}, - {{bif,'+',{f,0},[{x,0},{x,1023}],{x,0}},4, - {uninitialized_reg,{x,1023}}}}, + {{bif,'+',{f,0},[{x,0},{x,1023}],{x,0}},4,limit}}, {{t,sum_3,2}, {{bif,'+',{f,0},[{x,0},{x,1}],{x,-1}},4, - {invalid_store,{x,-1},number}}}, + {bad_register,{x,-1}}}}, {{t,sum_4,2}, {{bif,'+',{f,0},[{x,0},{x,1}],{x,1023}},4,limit}}] = Errors, ok. @@ -122,15 +121,15 @@ yrange(Config) when is_list(Config) -> Errors = do_val(yrange, Config), [{{t,sum_1,2}, {{move,{x,1},{y,-1}},5, - {invalid_store,{y,-1},term}}}, + {bad_register,{y,-1}}}}, {{t,sum_2,2}, {{bif,'+',{f,0},[{x,0},{y,1024}],{x,0}},7, - {uninitialized_reg,{y,1024}}}}, + limit}}, {{t,sum_3,2}, {{move,{x,1},{y,1024}},5,limit}}, {{t,sum_4,2}, {{move,{x,1},{y,-1}},5, - {invalid_store,{y,-1},term}}}] = Errors, + {bad_register,{y,-1}}}}] = Errors, ok. stack(Config) when is_list(Config) -> @@ -178,7 +177,7 @@ unsafe_catch(Config) when is_list(Config) -> Errors = do_val(unsafe_catch, Config), [{{t,small,2}, {{bs_put_integer,{f,0},{integer,16},1, - {field_flags,[unsigned,big]},{y,0}}, + {field_flags,[unsigned,big]},{y,0}}, 20, {unassigned,{y,0}}}}] = Errors, ok. @@ -223,7 +222,7 @@ bad_catch_try(Config) when is_list(Config) -> {{try_case,{y,1}},12,{invalid_tag,{y,1},term}}}, {{bad_catch_try,bad_6,1}, {{move,{integer,1},{y,1}},7, - {invalid_store,{y,1},{integer,1}}}}] = Errors, + {invalid_store,{y,1}}}}] = Errors, ok. cons_guard(Config) when is_list(Config) -> @@ -247,7 +246,7 @@ freg_range(Config) when is_list(Config) -> {{t,sum_3,2}, {{bif,fadd,{f,0},[{fr,0},{fr,1}],{fr,-1}}, 7, - {bad_target,{fr,-1}}}}, + {bad_register,{fr,-1}}}}, {{t,sum_4,2}, {{bif,fadd,{f,0},[{fr,0},{fr,1}],{fr,1024}}, 7, diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl index 12108445f0..0038eb1a4b 100644 --- a/lib/compiler/test/receive_SUITE.erl +++ b/lib/compiler/test/receive_SUITE.erl @@ -25,7 +25,8 @@ init_per_group/2,end_per_group/2, init_per_testcase/2,end_per_testcase/2, export/1,recv/1,coverage/1,otp_7980/1,ref_opt/1, - wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1]). + wait/1,recv_in_try/1,double_recv/1,receive_var_zero/1, + match_built_terms/1]). -include_lib("common_test/include/ct.hrl"). @@ -45,7 +46,8 @@ all() -> groups() -> [{p,test_lib:parallel(), [recv,coverage,otp_7980,ref_opt,export,wait, - recv_in_try,double_recv,receive_var_zero]}]. + recv_in_try,double_recv,receive_var_zero, + match_built_terms]}]. init_per_suite(Config) -> @@ -400,5 +402,29 @@ receive_var_zero(Config) when is_list(Config) -> zero() -> 0. +%% ERL-862; the validator would explode when a term was constructed in a +%% receive guard. + +-define(MATCH_BUILT_TERM(Ref, Expr), + (fun() -> + Ref = make_ref(), + A = id($a), + B = id($b), + Built = id(Expr), + self() ! {Ref, A, B}, + receive + {Ref, A, B} when Expr =:= Built -> + ok + after 5000 -> + ct:fail("Failed to match message with term built in " + "receive guard.") + end + end)()). + +match_built_terms(Config) when is_list(Config) -> + ?MATCH_BUILT_TERM(Ref, [A, B]), + ?MATCH_BUILT_TERM(Ref, {A, B}), + ?MATCH_BUILT_TERM(Ref, <<A, B>>), + ?MATCH_BUILT_TERM(Ref, #{ 1 => A, 2 => B}). id(I) -> I. diff --git a/lib/crypto/c_src/pkey.c b/lib/crypto/c_src/pkey.c index 567e8df08a..393358d173 100644 --- a/lib/crypto/c_src/pkey.c +++ b/lib/crypto/c_src/pkey.c @@ -70,10 +70,13 @@ static int get_pkey_digest_type(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_ if (type == atom_none && algorithm == atom_rsa) return PKEY_OK; + if (algorithm == atom_eddsa) { #ifdef HAVE_EDDSA - if (algorithm == atom_eddsa) - return PKEY_OK; + if (!FIPS_mode()) return PKEY_OK; +#else + return PKEY_NOTSUP; #endif + } if ((digp = get_digest_type(type)) == NULL) return PKEY_BADARG; if (digp->md.p == NULL) @@ -312,11 +315,16 @@ static int get_pkey_private_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_ return PKEY_NOTSUP; #endif } else if (algorithm == atom_eddsa) { -#if defined(HAVE_EDDSA) - if (!get_eddsa_key(env, 0, key, &result)) - goto err; +#ifdef HAVE_EDDSA + if (!FIPS_mode()) + { + if (!get_eddsa_key(env, 0, key, &result)) + goto err; + else + goto done; // Not nice.... + } #else - return PKEY_NOTSUP; + return PKEY_NOTSUP; #endif } else if (algorithm == atom_dss) { if ((dsa = DSA_new()) == NULL) @@ -432,10 +440,11 @@ static int get_pkey_public_key(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NIF_T return PKEY_NOTSUP; #endif } else if (algorithm == atom_eddsa) { -#if defined(HAVE_EDDSA) - if (!get_eddsa_key(env, 1, key, &result)) - goto err; - +#ifdef HAVE_EDDSA + if (!FIPS_mode()) { + if (!get_eddsa_key(env, 1, key, &result)) + goto err; + } #else return PKEY_NOTSUP; #endif @@ -516,7 +525,6 @@ ERL_NIF_TERM pkey_sign_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[]) /*char buf[1024]; enif_get_atom(env,argv[0],buf,1024,ERL_NIF_LATIN1); printf("algo=%s ",buf); enif_get_atom(env,argv[1],buf,1024,ERL_NIF_LATIN1); printf("hash=%s ",buf); -printf("\r\n"); */ #ifndef HAS_ENGINE_SUPPORT @@ -583,22 +591,24 @@ printf("\r\n"); if (argv[0] == atom_eddsa) { #ifdef HAVE_EDDSA - if ((mdctx = EVP_MD_CTX_new()) == NULL) - goto err; + if (!FIPS_mode()) { + if ((mdctx = EVP_MD_CTX_new()) == NULL) + goto err; - if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) - goto err; - if (EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen) != 1) - goto err; - if (!enif_alloc_binary(siglen, &sig_bin)) - goto err; - sig_bin_alloc = 1; + if (EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey) != 1) + goto err; + if (EVP_DigestSign(mdctx, NULL, &siglen, tbs, tbslen) != 1) + goto err; + if (!enif_alloc_binary(siglen, &sig_bin)) + goto err; + sig_bin_alloc = 1; - if (EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen) != 1) - goto bad_key; -#else - goto bad_arg; + if (EVP_DigestSign(mdctx, sig_bin.data, &siglen, tbs, tbslen) != 1) + goto bad_key; + } + else #endif + goto notsup; } else { if (EVP_PKEY_sign(ctx, NULL, &siglen, tbs, tbslen) != 1) goto err; @@ -805,16 +815,18 @@ ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM argv[] if (argv[0] == atom_eddsa) { #ifdef HAVE_EDDSA - if ((mdctx = EVP_MD_CTX_new()) == NULL) - goto err; - - if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) - goto err; + if (!FIPS_mode()) { + if ((mdctx = EVP_MD_CTX_new()) == NULL) + goto err; - result = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen); -#else - goto bad_arg; + if (EVP_DigestVerifyInit(mdctx, NULL, NULL, NULL, pkey) != 1) + goto err; + + result = EVP_DigestVerify(mdctx, sig_bin.data, sig_bin.size, tbs, tbslen); + } + else #endif + goto notsup; } else { if (md != NULL) { ERL_VALGRIND_ASSERT_MEM_DEFINED(tbs, EVP_MD_size(md)); diff --git a/lib/crypto/test/Makefile b/lib/crypto/test/Makefile index 8b320e01a9..988d95a8bc 100644 --- a/lib/crypto/test/Makefile +++ b/lib/crypto/test/Makefile @@ -7,7 +7,6 @@ include $(ERL_TOP)/make/$(TARGET)/otp.mk MODULES = \ crypto_bench_SUITE \ - blowfish_SUITE \ crypto_SUITE \ engine_SUITE diff --git a/lib/crypto/test/blowfish_SUITE.erl b/lib/crypto/test/blowfish_SUITE.erl deleted file mode 100644 index a931ebb47e..0000000000 --- a/lib/crypto/test/blowfish_SUITE.erl +++ /dev/null @@ -1,300 +0,0 @@ -%% -%% %CopyrightBegin% -%% -%% Copyright Ericsson AB 2009-2018. 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(blowfish_SUITE). - -%% Note: This directive should only be used in test suites. --compile(export_all). - --include_lib("common_test/include/ct.hrl"). - --define(TIMEOUT, 120000). % 2 min - --define(KEY, to_bin("0123456789ABCDEFF0E1D2C3B4A59687")). --define(IVEC, to_bin("FEDCBA9876543210")). -%% "7654321 Now is the time for " (includes trailing '\0') --define(DATA, to_bin("37363534333231204E6F77206973207468652074696D6520666F722000")). --define(DATA_PADDED, to_bin("37363534333231204E6F77206973207468652074696D6520666F722000000000")). - -%% Test server callback functions -%%-------------------------------------------------------------------- -%% Function: init_per_suite(Config) -> Config -%% Config - [tuple()] -%% A list of key/value pairs, holding the test case configuration. -%% Description: Initialization before the whole suite -%% -%% Note: This function is free to add any key/value pairs to the Config -%% variable, but should NOT alter/remove any existing entries. -%%-------------------------------------------------------------------- -init_per_suite(Config) -> - case catch crypto:start() of - ok -> - catch ct:comment("~s",[element(3,hd(crypto:info_lib()))]), - catch ct:log("crypto:info_lib() -> ~p~n" - "crypto:supports() -> ~p~n" - "crypto:version() -> ~p~n" - ,[crypto:info_lib(), crypto:supports(), crypto:version()]), - Config; - _Else -> - {skip,"Could not start crypto!"} - end. - -%%-------------------------------------------------------------------- -%% Function: end_per_suite(Config) -> _ -%% Config - [tuple()] -%% A list of key/value pairs, holding the test case configuration. -%% Description: Cleanup after the whole suite -%%-------------------------------------------------------------------- -end_per_suite(_Config) -> - crypto:stop(). - -%%-------------------------------------------------------------------- -%% Function: init_per_testcase(TestCase, Config) -> Config -%% Case - atom() -%% Name of the test case that is about to be run. -%% Config - [tuple()] -%% A list of key/value pairs, holding the test case configuration. -%% -%% Description: Initialization before each test case -%% -%% Note: This function is free to add any key/value pairs to the Config -%% variable, but should NOT alter/remove any existing entries. -%% Description: Initialization before each test case -%%-------------------------------------------------------------------- -init_per_testcase(_TestCase, Config0) -> - Config = lists:keydelete(watchdog, 1, Config0), - Dog = test_server:timetrap(?TIMEOUT), - [{watchdog, Dog} | Config]. - -%%-------------------------------------------------------------------- -%% Function: end_per_testcase(TestCase, Config) -> _ -%% Case - atom() -%% Name of the test case that is about to be run. -%% Config - [tuple()] -%% A list of key/value pairs, holding the test case configuration. -%% Description: Cleanup after each test case -%%-------------------------------------------------------------------- -end_per_testcase(_TestCase, Config) -> - Dog = ?config(watchdog, Config), - case Dog of - undefined -> - ok; - _ -> - test_server:timetrap_cancel(Dog) - end. - -%%-------------------------------------------------------------------- -%% Function: all(Clause) -> TestCases -%% Clause - atom() - suite | doc -%% TestCases - [Case] -%% Case - atom() -%% Name of a test case. -%% Description: Returns a list of all test cases in this test suite -%%-------------------------------------------------------------------- -suite() -> [{ct_hooks,[ts_install_cth]}]. - -all() -> -[{group, fips}, - {group, non_fips}]. - -groups() -> - [{fips, [], [no_ecb, no_cbc, no_cfb64, no_ofb64]}, - {non_fips, [], [ecb, cbc, cfb64, ofb64]}]. - -init_per_group(fips, Config) -> - case crypto:info_fips() of - enabled -> - Config; - not_enabled -> - case crypto:enable_fips_mode(true) of - true -> - enabled = crypto:info_fips(), - Config; - false -> - {skip, "Failed to enable FIPS mode"} - end; - not_supported -> - {skip, "FIPS mode not supported"} - end; -init_per_group(non_fips, Config) -> - case crypto:info_fips() of - enabled -> - true = crypto:enable_fips_mode(false), - not_enabled = crypto:info_fips(), - Config; - _NotEnabled -> - Config - end; -init_per_group(_GroupName, Config) -> - Config. - -end_per_group(_GroupName, Config) -> - Config. - - -%% Test cases start here. -%%-------------------------------------------------------------------- - -ecb_test(KeyBytes, ClearBytes, CipherBytes) -> - {Key, Clear, Cipher} = - {to_bin(KeyBytes), to_bin(ClearBytes), to_bin(CipherBytes)}, - ?line m(crypto:block_encrypt(blowfish_ecb, Key, Clear), Cipher), - true. - -ecb(doc) -> - "Test that ECB mode is OK"; -ecb(suite) -> - []; -ecb(Config) when is_list(Config) -> - true = ecb_test("0000000000000000", "0000000000000000", "4EF997456198DD78"), - true = ecb_test("FFFFFFFFFFFFFFFF", "FFFFFFFFFFFFFFFF", "51866FD5B85ECB8A"), - true = ecb_test("3000000000000000", "1000000000000001", "7D856F9A613063F2"), - true = ecb_test("1111111111111111", "1111111111111111", "2466DD878B963C9D"), - true = ecb_test("0123456789ABCDEF", "1111111111111111", "61F9C3802281B096"), - true = ecb_test("1111111111111111", "0123456789ABCDEF", "7D0CC630AFDA1EC7"), - true = ecb_test("0000000000000000", "0000000000000000", "4EF997456198DD78"), - true = ecb_test("FEDCBA9876543210", "0123456789ABCDEF", "0ACEAB0FC6A0A28D"), - true = ecb_test("7CA110454A1A6E57", "01A1D6D039776742", "59C68245EB05282B"), - true = ecb_test("0131D9619DC1376E", "5CD54CA83DEF57DA", "B1B8CC0B250F09A0"), - true = ecb_test("07A1133E4A0B2686", "0248D43806F67172", "1730E5778BEA1DA4"), - true = ecb_test("3849674C2602319E", "51454B582DDF440A", "A25E7856CF2651EB"), - true = ecb_test("04B915BA43FEB5B6", "42FD443059577FA2", "353882B109CE8F1A"), - true = ecb_test("0113B970FD34F2CE", "059B5E0851CF143A", "48F4D0884C379918"), - true = ecb_test("0170F175468FB5E6", "0756D8E0774761D2", "432193B78951FC98"), - true = ecb_test("43297FAD38E373FE", "762514B829BF486A", "13F04154D69D1AE5"), - true = ecb_test("07A7137045DA2A16", "3BDD119049372802", "2EEDDA93FFD39C79"), - true = ecb_test("04689104C2FD3B2F", "26955F6835AF609A", "D887E0393C2DA6E3"), - true = ecb_test("37D06BB516CB7546", "164D5E404F275232", "5F99D04F5B163969"), - true = ecb_test("1F08260D1AC2465E", "6B056E18759F5CCA", "4A057A3B24D3977B"), - true = ecb_test("584023641ABA6176", "004BD6EF09176062", "452031C1E4FADA8E"), - true = ecb_test("025816164629B007", "480D39006EE762F2", "7555AE39F59B87BD"), - true = ecb_test("49793EBC79B3258F", "437540C8698F3CFA", "53C55F9CB49FC019"), - true = ecb_test("4FB05E1515AB73A7", "072D43A077075292", "7A8E7BFA937E89A3"), - true = ecb_test("49E95D6D4CA229BF", "02FE55778117F12A", "CF9C5D7A4986ADB5"), - true = ecb_test("018310DC409B26D6", "1D9D5C5018F728C2", "D1ABB290658BC778"), - true = ecb_test("1C587F1C13924FEF", "305532286D6F295A", "55CB3774D13EF201"), - true = ecb_test("0101010101010101", "0123456789ABCDEF", "FA34EC4847B268B2"), - true = ecb_test("1F1F1F1F0E0E0E0E", "0123456789ABCDEF", "A790795108EA3CAE"), - true = ecb_test("E0FEE0FEF1FEF1FE", "0123456789ABCDEF", "C39E072D9FAC631D"), - true = ecb_test("0000000000000000", "FFFFFFFFFFFFFFFF", "014933E0CDAFF6E4"), - true = ecb_test("FFFFFFFFFFFFFFFF", "0000000000000000", "F21E9A77B71C49BC"), - true = ecb_test("0123456789ABCDEF", "0000000000000000", "245946885754369A"), - true = ecb_test("FEDCBA9876543210", "FFFFFFFFFFFFFFFF", "6B5C5A9C5D9E0A5A"), - ok. - -cbc(doc) -> - "Test that CBC mode is OK"; -cbc(suite) -> - []; -cbc(Config) when is_list(Config) -> - true = crypto:block_encrypt(blowfish_cbc, ?KEY, ?IVEC, ?DATA_PADDED) =:= - to_bin("6B77B4D63006DEE605B156E27403979358DEB9E7154616D959F1652BD5FF92CC"), - ok. - -cfb64(doc) -> - "Test that CFB64 mode is OK"; -cfb64(suite) -> - []; -cfb64(Config) when is_list(Config) -> - true = crypto:block_encrypt(blowfish_cfb64, ?KEY, ?IVEC, ?DATA) =:= - to_bin("E73214A2822139CAF26ECF6D2EB9E76E3DA3DE04D1517200519D57A6C3"), - ok. - -ofb64(doc) -> - "Test that OFB64 mode is OK"; -ofb64(suite) -> - []; -ofb64(Config) when is_list(Config) -> - true = crypto:block_encrypt(blowfish_ofb64, ?KEY, ?IVEC, ?DATA) =:= - to_bin("E73214A2822139CA62B343CC5B65587310DD908D0C241B2263C2CF80DA"), - ok. - -no_ecb(doc) -> - "Test that ECB mode is disabled"; -no_ecb(suite) -> - []; -no_ecb(Config) when is_list(Config) -> - notsup(fun crypto:block_encrypt/3, - [blowfish_ecb, - to_bin("0000000000000000"), - to_bin("FFFFFFFFFFFFFFFF")]). - -no_cbc(doc) -> - "Test that CBC mode is disabled"; -no_cbc(suite) -> - []; -no_cbc(Config) when is_list(Config) -> - notsup(fun crypto:block_encrypt/4, - [blowfish_cbc, ?KEY, ?IVEC, ?DATA_PADDED]). - -no_cfb64(doc) -> - "Test that CFB64 mode is disabled"; -no_cfb64(suite) -> - []; -no_cfb64(Config) when is_list(Config) -> - notsup(fun crypto:block_encrypt/4, - [blowfish_cfb64, ?KEY, ?IVEC, ?DATA]), - ok. - -no_ofb64(doc) -> - "Test that OFB64 mode is disabled"; -no_ofb64(suite) -> - []; -no_ofb64(Config) when is_list(Config) -> - notsup(fun crypto:block_encrypt/4, - [blowfish_ofb64, ?KEY, ?IVEC, ?DATA]). - -%% Helper functions - -%% Assert function fails with notsup error -notsup(Fun, Args) -> - ok = try - {error, {return, apply(Fun, Args)}} - catch - error:notsup -> - ok; - Class:Error -> - {error, {Class, Error}} - end. - - -%% Convert a hexadecimal string to a binary. --spec(to_bin(L::string()) -> binary()). -to_bin(L) -> - to_bin(L, []). - -%% @spec dehex(char()) -> integer() -%% @doc Convert a hex digit to its integer value. --spec(dehex(char()) -> integer()). -dehex(C) when C >= $0, C =< $9 -> - C - $0; -dehex(C) when C >= $a, C =< $f -> - C - $a + 10; -dehex(C) when C >= $A, C =< $F -> - C - $A + 10. - --spec(to_bin(L::string(), list()) -> binary()). -to_bin([], Acc) -> - iolist_to_binary(lists:reverse(Acc)); -to_bin([C1, C2 | Rest], Acc) -> - to_bin(Rest, [(dehex(C1) bsl 4) bor dehex(C2) | Acc]). - -m(X,X) -> ok. diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index c4323de83f..ab6d88deb2 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -101,6 +101,8 @@ groups() -> {group, rsa}, {group, dss}, {group, ecdsa}, + {group, no_ed25519}, + {group, no_ed448}, {group, dh}, {group, ecdh}, {group, no_srp}, @@ -115,8 +117,8 @@ groups() -> {group, no_blowfish_cfb64}, {group, no_blowfish_ofb64}, {group, aes_cbc128}, - {group, aes_cfb8}, - {group, aes_cfb128}, + {group, no_aes_cfb8}, + {group, no_aes_cfb128}, {group, aes_cbc256}, {group, no_aes_ige256}, {group, no_rc2_cbc}, @@ -187,8 +189,16 @@ groups() -> {chacha20, [], [stream]}, {poly1305, [], [poly1305]}, {aes_cbc, [], [block]}, + {no_aes_cfb8,[], [no_support, no_block]}, + {no_aes_cfb128,[], [no_support, no_block]}, {no_md4, [], [no_support, no_hash]}, {no_md5, [], [no_support, no_hash, no_hmac]}, + {no_ed25519, [], [no_support, no_sign_verify + %% Does not work yet: ,public_encrypt, private_encrypt + ]}, + {no_ed448, [], [no_support, no_sign_verify + %% Does not work yet: ,public_encrypt, private_encrypt + ]}, {no_ripemd160, [], [no_support, no_hash]}, {no_srp, [], [no_support, no_generate_compute]}, {no_des_cbc, [], [no_support, no_block]}, @@ -255,7 +265,7 @@ init_per_group(fips, Config) -> enabled = crypto:info_fips(), FIPSConfig; false -> - {skip, "Failed to enable FIPS mode"} + {fail, "Failed to enable FIPS mode"} end; not_supported -> {skip, "FIPS mode not supported"} @@ -405,17 +415,6 @@ block() -> block(Config) when is_list(Config) -> Fips = proplists:get_bool(fips, Config), Type = ?config(type, Config), - %% See comment about EVP_CIPHER_CTX_set_key_length in - %% block_crypt_nif in crypto.c. - case {Fips, Type} of - {true, aes_cfb8} -> - throw({skip, "Cannot test aes_cfb8 in FIPS mode because of key length issue"}); - {true, aes_cfb128} -> - throw({skip, "Cannot test aes_cfb128 in FIPS mode because of key length issue"}); - _ -> - ok - end, - Blocks = lazy_eval(proplists:get_value(block, Config)), lists:foreach(fun block_cipher/1, Blocks), lists:foreach(fun block_cipher/1, block_iolistify(Blocks)), @@ -504,6 +503,13 @@ sign_verify(Config) when is_list(Config) -> SignVerify = proplists:get_value(sign_verify, Config), lists:foreach(fun do_sign_verify/1, SignVerify). +%%-------------------------------------------------------------------- +no_sign_verify() -> + [{doc, "Test disabled sign/verify digital signatures"}]. +no_sign_verify(Config) when is_list(Config) -> + [SignVerifyHd|_] = proplists:get_value(sign_verify, Config), + notsup(fun do_sign_verify/1, [SignVerifyHd]). + %%-------------------------------------------------------------------- public_encrypt() -> [{doc, "Test public_encrypt/decrypt "}]. diff --git a/lib/edoc/src/edoc.erl b/lib/edoc/src/edoc.erl index b641118c5d..e9d62d3283 100644 --- a/lib/edoc/src/edoc.erl +++ b/lib/edoc/src/edoc.erl @@ -578,7 +578,7 @@ read_source(Name, Opts0) -> Opts = expand_opts(Opts0), case read_source_1(Name, Opts) of {ok, Forms} -> - check_forms(Forms, Name), + check_forms(Forms, Name, Opts), Forms; {error, R} -> edoc_report:error({"error reading file '~ts'.", @@ -692,13 +692,19 @@ fll([T | L], LastLine, Ts) -> fll(L, _LastLine, Ts) -> lists:reverse(L, Ts). -check_forms(Fs, Name) -> +check_forms(Fs, Name, Opts) -> Fun = fun (F) -> case erl_syntax:type(F) of error_marker -> case erl_syntax:error_marker_info(F) of {L, M, D} -> - edoc_report:error(L, Name, {format_error, M, D}); + edoc_report:error(L, Name, {format_error, M, D}), + case proplists:get_bool(preprocess, Opts) of + true -> + ok; + false -> + helpful_message(Name) + end; Other -> edoc_report:report(Name, "unknown error in " "source code: ~w.", [Other]) @@ -710,6 +716,11 @@ check_forms(Fs, Name) -> end, lists:foreach(Fun, Fs). +helpful_message(Name) -> + Ms = ["If the error is caused by too exotic macro", + "definitions or uses of macros, adding option", + "{preprocess, true} can help. See also edoc(3)."], + lists:foreach(fun(M) -> edoc_report:report(Name, M, []) end, Ms). %% @spec get_doc(File::filename()) -> {ModuleName, edoc_module()} %% @equiv get_doc(File, []) diff --git a/lib/erl_docgen/priv/css/otp_doc.css b/lib/erl_docgen/priv/css/otp_doc.css index 89b278215c..17d9f8dd56 100644 --- a/lib/erl_docgen/priv/css/otp_doc.css +++ b/lib/erl_docgen/priv/css/otp_doc.css @@ -74,7 +74,7 @@ a:visited { color: #1b6ec2; text-decoration: none } /* Invisible table for function specs, * just to get since-version out in right margin */ -.func-table, .func-tr, .func-td, .func-since-td { +.func-table, .func-tr, .func-td, .cfunc-td, .func-since-td { width: 200%; border: 0; padding: 0; @@ -86,7 +86,13 @@ a:visited { color: #1b6ec2; text-decoration: none } } .func-td { - width: 50% + width: 50%; +} + +.cfunc-td { + width: 50%; + padding-left: 100px; + text-indent: -100px; } .func-since-td { diff --git a/lib/erl_docgen/priv/xsl/db_html.xsl b/lib/erl_docgen/priv/xsl/db_html.xsl index c5150d447c..c9be926e1e 100644 --- a/lib/erl_docgen/priv/xsl/db_html.xsl +++ b/lib/erl_docgen/priv/xsl/db_html.xsl @@ -2119,17 +2119,10 @@ <xsl:when test="ancestor::cref"> <table class="func-table"> <tr class="func-tr"> - <td class="func-td"> + <td class="cfunc-td"> <span class="bold_code bc-7"> <xsl:call-template name="title_link"> <xsl:with-param name="link" select="substring-before(nametext, '(')"/> - <xsl:with-param name="title"> - <xsl:value-of select="ret"/> - <xsl:call-template name="maybe-space-after-ret"> - <xsl:with-param name="s" select="ret"/> - </xsl:call-template> - <xsl:value-of select="nametext"/> - </xsl:with-param> </xsl:call-template> </span> </td> @@ -2199,18 +2192,6 @@ </xsl:template> - <xsl:template name="maybe-space-after-ret"> - <xsl:param name="s"/> - <xsl:variable name="last_char" - select="substring($s, string-length($s), 1)"/> - <xsl:choose> - <xsl:when test="$last_char != '*'"> - <xsl:text> </xsl:text> - </xsl:when> - </xsl:choose> - </xsl:template> - - <!-- Type --> <xsl:template match="type"> <xsl:param name="partnum"/> @@ -2264,7 +2245,7 @@ </xsl:template> <xsl:template name="title_link"> - <xsl:param name="title"/> + <xsl:param name="title" select="'APPLY'"/> <xsl:param name="link" select="erl:to-link(title)"/> <xsl:param name="ghlink" select="ancestor-or-self::*[@ghlink][position() = 1]/@ghlink"/> <xsl:variable name="id" select="concat(concat($link,'-'), generate-id(.))"/> @@ -2274,7 +2255,16 @@ <xsl:with-param name="id" select="$id"/> <xsl:with-param name="ghlink" select="$ghlink"/> </xsl:call-template> - <a class="title_link" name="{$link}" href="#{$link}"><xsl:value-of select="$title"/></a> + <a class="title_link" name="{$link}" href="#{$link}"> + <xsl:choose> + <xsl:when test="$title = 'APPLY'"> + <xsl:apply-templates/> <!-- like <ret> and <nametext> --> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$title"/> + </xsl:otherwise> + </xsl:choose> + </a> </span> </xsl:template> @@ -2704,4 +2694,46 @@ <xsl:value-of select="normalize-space(.)"/> </xsl:template> + <xsl:template match="ret"> + <xsl:value-of select="."/> + <xsl:variable name="last_char" select="substring(., string-length(.), 1)"/> + <xsl:if test="$last_char != '*'"> + <xsl:text> </xsl:text> + </xsl:if> + </xsl:template> + + <xsl:template match="nametext"> + <xsl:value-of select="substring-before(.,'(')"/> + <xsl:text>(</xsl:text> + <xsl:variable name="arglist" select="substring-after(.,'(')"/> + <xsl:choose> + <xsl:when test="$arglist = ')' or $arglist = 'void)'"> + <xsl:value-of select="$arglist"/> + </xsl:when> + <xsl:otherwise> + <br/> + <xsl:call-template name="cfunc-arglist"> + <xsl:with-param name="text" select="$arglist"/> + </xsl:call-template> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + + <!-- Format C function argument list with <br> after comma --> + <xsl:template name="cfunc-arglist"> + <xsl:param name="text"/> + <xsl:variable name="line" select="normalize-space($text)"/> + <xsl:choose> + <xsl:when test="contains($line,',')"> + <xsl:value-of select="substring-before($line,',')"/>,<br/> + <xsl:call-template name="cfunc-arglist"> + <xsl:with-param name="text" select="substring-after($line,',')"/> + </xsl:call-template> + </xsl:when> + <xsl:otherwise> + <xsl:value-of select="$line"/> + </xsl:otherwise> + </xsl:choose> + </xsl:template> + </xsl:stylesheet> diff --git a/lib/kernel/doc/src/application.xml b/lib/kernel/doc/src/application.xml index 4e32c1a3a5..5170502581 100644 --- a/lib/kernel/doc/src/application.xml +++ b/lib/kernel/doc/src/application.xml @@ -238,6 +238,41 @@ Nodes = [cp1@cave, {cp2@cave, cp3@cave}]</code> </desc> </func> <func> + <name name="set_env" arity="1" since="OTP @OTP-15642@"/> + <name name="set_env" arity="2" since="OTP @OTP-15642@"/> + <fsummary>Sets the configuration parameters of multiple applications.</fsummary> + <desc> + <p>Sets the configuration <c><anno>Config</anno></c> for multiple + applications. It is equivalent to calling <c>set_env/4</c> on + each application individially, except it is more efficient. + The given <c><anno>Config</anno></c> is validated before the + configuration is set.</p> + <p><c>set_env/2</c> uses the standard <c>gen_server</c> time-out + value (5000 ms). Option <c>timeout</c> can be specified + if another time-out value is useful, for example, in situations + where the application controller is heavily loaded.</p> + <p>Option <c>persistent</c> can be set to <c>true</c> + to guarantee that parameters set with <c>set_env/2</c> + are not overridden by those defined in the application resource + file on load. This means that persistent values will stick after the application + is loaded and also on application reload.</p> + <p>If an application is given more than once or if an application + has the same key given more than once, the behaviour is undefined + and a warning message will be logged. In future releases, an error + will be raised.</p> + <p><c>set_env/1</c> is equivalent to <c>set_env(Config, [])</c>.</p> + <warning> + <p>Use this function only if you know what you are doing, + that is, on your own applications. It is very + application-dependent and + configuration parameter-dependent when and how often + the value is read by the application. Careless use + of this function can put the application in a + weird, inconsistent, and malfunctioning state.</p> + </warning> + </desc> + </func> + <func> <name name="permit" arity="2" since=""/> <fsummary>Change the permission for an application to run at a node.</fsummary> <desc> diff --git a/lib/kernel/doc/src/logger.xml b/lib/kernel/doc/src/logger.xml index df2d081d76..e6448e144e 100644 --- a/lib/kernel/doc/src/logger.xml +++ b/lib/kernel/doc/src/logger.xml @@ -190,7 +190,7 @@ logger:error("error happened because: ~p", [Reason]). % Without macro <list> <item><c>pid => self()</c></item> <item><c>gl => group_leader()</c></item> - <item><c>time => erlang:system_time(microsecond)</c></item> + <item><c>time => logger:timestamp()</c></item> </list> <p>When a log macro is used, Logger also inserts location information:</p> @@ -288,8 +288,8 @@ logger:error("error happened because: ~p", [Reason]). % Without macro <name name="timestamp"/> <desc> <p>A timestamp produced - with <seealso marker="erts:erlang#system_time-1"> - <c>erlang:system_time(microsecond)</c></seealso>.</p> + with <seealso marker="#timestamp-0"> + <c>logger:timestamp()</c></seealso>.</p> </desc> </datatype> </datatypes> @@ -1117,6 +1117,24 @@ logger:set_proxy_config(maps:merge(Old, Config)). a key-value list before formatting as such.</p> </desc> </func> + + <func> + <name name="timestamp" arity="0" since="OTP @OTP-15625@"/> + <fsummary>Return a timestamp to insert in meta data for a log + event.</fsummary> + <desc> + <p>Return a timestamp that can be inserted as the <c>time</c> + field in the meta data for a log event. It is produced with + <seealso marker="kernel:os#system_time-1"> + <c>os:system_time(microsecond)</c></seealso>.</p> + <p>Notice that Logger automatically inserts a timestamp in the + meta data unless it already exists. This function is + exported for the rare case when the timestamp must be taken + at a different point in time than when the log event is + issued.</p> + </desc> + </func> + </funcs> <section> diff --git a/lib/kernel/src/application.erl b/lib/kernel/src/application.erl index bc6be2f8f5..5c2e981e4b 100644 --- a/lib/kernel/src/application.erl +++ b/lib/kernel/src/application.erl @@ -25,7 +25,7 @@ which_applications/0, which_applications/1, loaded_applications/0, permit/2]). -export([ensure_started/1, ensure_started/2]). --export([set_env/3, set_env/4, unset_env/2, unset_env/3]). +-export([set_env/1, set_env/2, set_env/3, set_env/4, unset_env/2, unset_env/3]). -export([get_env/1, get_env/2, get_env/3, get_all_env/0, get_all_env/1]). -export([get_key/1, get_key/2, get_all_key/0, get_all_key/1]). -export([get_application/0, get_application/1, info/0]). @@ -279,6 +279,26 @@ loaded_applications() -> info() -> application_controller:info(). +-spec set_env(Config) -> 'ok' when + Config :: [{Application, Env}], + Application :: atom(), + Env :: [{Par :: atom(), Val :: term()}]. + +set_env(Config) when is_list(Config) -> + set_env(Config, []). + +-spec set_env(Config, Opts) -> 'ok' when + Config :: [{Application, Env}], + Application :: atom(), + Env :: [{Par :: atom(), Val :: term()}], + Opts :: [{timeout, timeout()} | {persistent, boolean()}]. + +set_env(Config, Opts) when is_list(Config), is_list(Opts) -> + case application_controller:set_env(Config, Opts) of + ok -> ok; + {error, Msg} -> erlang:error({badarg, Msg}, [Config, Opts]) + end. + -spec set_env(Application, Par, Val) -> 'ok' when Application :: atom(), Par :: atom(), diff --git a/lib/kernel/src/application_controller.erl b/lib/kernel/src/application_controller.erl index a074d2e74b..9a8091fb2e 100644 --- a/lib/kernel/src/application_controller.erl +++ b/lib/kernel/src/application_controller.erl @@ -26,7 +26,7 @@ control_application/1, change_application_data/2, prep_config_change/0, config_change/1, which_applications/0, which_applications/1, - loaded_applications/0, info/0, + loaded_applications/0, info/0, set_env/2, get_pid_env/2, get_env/2, get_pid_all_env/1, get_all_env/1, get_pid_key/2, get_key/2, get_pid_all_key/1, get_all_key/1, get_master/1, get_application/1, get_application_module/1, @@ -345,9 +345,6 @@ get_all_env(AppName) -> map(fun([Key, Val]) -> {Key, Val} end, ets:match(ac_tab, {{env, AppName, '$1'}, '$2'})). - - - get_pid_key(Master, Key) -> case ets:match(ac_tab, {{application_master, '$1'}, Master}) of [[AppName]] -> get_key(AppName, Key); @@ -461,6 +458,15 @@ permit_application(ApplName, Flag) -> {permit_application, ApplName, Flag}, infinity). +set_env(Config, Opts) -> + case check_conf_data(Config) of + ok -> + Timeout = proplists:get_value(timeout, Opts, 5000), + gen_server:call(?AC, {set_env, Config, Opts}, Timeout); + + {error, _} = Error -> + Error + end. set_env(AppName, Key, Val) -> gen_server:call(?AC, {set_env, AppName, Key, Val, []}). @@ -528,19 +534,17 @@ check_conf_data([]) -> check_conf_data(ConfData) when is_list(ConfData) -> [Application | ConfDataRem] = ConfData, case Application of - {kernel, List} when is_list(List) -> - case check_para_kernel(List) of - ok -> - check_conf_data(ConfDataRem); - Error1 -> - Error1 - end; {AppName, List} when is_atom(AppName), is_list(List) -> - case check_para(List, atom_to_list(AppName)) of - ok -> - check_conf_data(ConfDataRem); - Error2 -> - Error2 + case lists:keymember(AppName, 1, ConfDataRem) of + true -> + ?LOG_WARNING("duplicate application config: " ++ atom_to_list(AppName)); + false -> + ok + end, + + case check_para(List, AppName) of + ok -> check_conf_data(ConfDataRem); + Error -> Error end; {AppName, List} when is_list(List) -> ErrMsg = "application: " @@ -553,36 +557,40 @@ check_conf_data(ConfData) when is_list(ConfData) -> ++ "; parameters must be a list", {error, ErrMsg}; Else -> - ErrMsg = "invalid application name: " ++ - lists:flatten(io_lib:format(" ~tp",[Else])), + ErrMsg = "invalid application config: " + ++ lists:flatten(io_lib:format("~tp",[Else])), {error, ErrMsg} end; check_conf_data(_ConfData) -> - {error, 'configuration must be a list ended by <dot><whitespace>'}. - + {error, "configuration must be a list ended by <dot><whitespace>"}. -%% Special check of distributed parameter for kernel -check_para_kernel([]) -> + +check_para([], _AppName) -> ok; -check_para_kernel([{distributed, Apps} | ParaList]) when is_list(Apps) -> - case check_distributed(Apps) of - {error, _ErrorMsg} = Error -> - Error; - _ -> - check_para_kernel(ParaList) +check_para([{Para, Val} | ParaList], AppName) when is_atom(Para) -> + case lists:keymember(Para, 1, ParaList) of + true -> + ?LOG_WARNING("application: " ++ atom_to_list(AppName) ++ + "; duplicate parameter: " ++ atom_to_list(Para)); + false -> + ok + end, + + case check_para_value(Para, Val, AppName) of + ok -> check_para(ParaList, AppName); + {error, _} = Error -> Error end; -check_para_kernel([{distributed, _Apps} | _ParaList]) -> - {error, "application: kernel; erroneous parameter: distributed"}; -check_para_kernel([{Para, _Val} | ParaList]) when is_atom(Para) -> - check_para_kernel(ParaList); -check_para_kernel([{Para, _Val} | _ParaList]) -> - {error, "application: kernel; invalid parameter: " ++ +check_para([{Para, _Val} | _ParaList], AppName) -> + {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter name: " ++ lists:flatten(io_lib:format("~tp",[Para]))}; -check_para_kernel(Else) -> - {error, "application: kernel; invalid parameter list: " ++ +check_para([Else | _ParaList], AppName) -> + {error, "application: " ++ atom_to_list(AppName) ++ "; invalid parameter: " ++ lists:flatten(io_lib:format("~tp",[Else]))}. - +check_para_value(distributed, Apps, kernel) -> check_distributed(Apps); +check_para_value(_Para, _Val, _AppName) -> ok. + +%% Special check of distributed parameter for kernel check_distributed([]) -> ok; check_distributed([{App, List} | Apps]) when is_atom(App), is_list(List) -> @@ -595,18 +603,6 @@ check_distributed(_Else) -> {error, "application: kernel; erroneous parameter: distributed"}. -check_para([], _AppName) -> - ok; -check_para([{Para, _Val} | ParaList], AppName) when is_atom(Para) -> - check_para(ParaList, AppName); -check_para([{Para, _Val} | _ParaList], AppName) -> - {error, "application: " ++ AppName ++ "; invalid parameter: " ++ - lists:flatten(io_lib:format("~tp",[Para]))}; -check_para([Else | _ParaList], AppName) -> - {error, "application: " ++ AppName ++ "; invalid parameter: " ++ - lists:flatten(io_lib:format("~tp",[Else]))}. - - -type calls() :: 'info' | 'prep_config_change' | 'which_applications' | {'config_change' | 'control_application' | 'load_application' | 'start_type' | 'stop_application' | @@ -863,6 +859,16 @@ handle_call(which_applications, _From, S) -> end, S#state.running), {reply, Reply, S}; +handle_call({set_env, Config, Opts}, _From, S) -> + _ = [add_env(AppName, Env) || {AppName, Env} <- Config], + + case proplists:get_value(persistent, Opts, false) of + true -> + {reply, ok, S#state{conf_data = merge_env(S#state.conf_data, Config)}}; + false -> + {reply, ok, S} + end; + handle_call({set_env, AppName, Key, Val, Opts}, _From, S) -> ets:insert(ac_tab, {{env, AppName, Key}, Val}), case proplists:get_value(persistent, Opts, false) of diff --git a/lib/kernel/src/code_server.erl b/lib/kernel/src/code_server.erl index 1b4a67ecb7..68e1205301 100644 --- a/lib/kernel/src/code_server.erl +++ b/lib/kernel/src/code_server.erl @@ -1434,19 +1434,25 @@ all_loaded(Db) -> -spec error_msg(io:format(), [term()]) -> 'ok'. error_msg(Format, Args) -> + %% This is equal to calling logger:error/3 which we don't want to + %% do from code_server. We don't want to call logger:timestamp() + %% either. logger ! {log,error,Format,Args, #{pid=>self(), gl=>group_leader(), - time=>erlang:system_time(microsecond), + time=>os:system_time(microsecond), error_logger=>#{tag=>error}}}, ok. -spec info_msg(io:format(), [term()]) -> 'ok'. info_msg(Format, Args) -> + %% This is equal to calling logger:info/3 which we don't want to + %% do from code_server. We don't want to call logger:timestamp() + %% either. logger ! {log,info,Format,Args, #{pid=>self(), gl=>group_leader(), - time=>erlang:system_time(microsecond), + time=>os:system_time(microsecond), error_logger=>#{tag=>info_msg}}}, ok. diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl index 6cbb6ac2da..3f5a2ea5ee 100644 --- a/lib/kernel/src/inet_db.erl +++ b/lib/kernel/src/inet_db.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2016. All Rights Reserved. +%% Copyright Ericsson AB 1997-2019. 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. @@ -1223,7 +1223,10 @@ handle_set_file(Option, Fname, TagTm, TagInfo, ParseFun, From, {ok, B, _} -> B; _ -> <<>> end; - _ -> <<>> + _ -> + ets:insert(Db, {TagInfo, undefined}), + TimeZero = - (?RES_FILE_UPDATE_TM + 1), % Early enough + ets:insert(Db, {TagTm, TimeZero}) end, handle_set_file(ParseFun, Bin, From, State); false -> {reply,error,State} diff --git a/lib/kernel/src/logger.erl b/lib/kernel/src/logger.erl index 7d36640f52..38bd2f481c 100644 --- a/lib/kernel/src/logger.erl +++ b/lib/kernel/src/logger.erl @@ -61,6 +61,7 @@ -export([set_process_metadata/1, update_process_metadata/1, unset_process_metadata/0, get_process_metadata/0]). -export([i/0, i/1]). +-export([timestamp/0]). %% Basic report formatting -export([format_report/1, format_otp_report/1]). @@ -154,7 +155,8 @@ filter_return/0, config_handler/0, formatter_config/0, - olp_config/0]). + olp_config/0, + timestamp/0]). %%%----------------------------------------------------------------- %%% API @@ -354,6 +356,10 @@ internal_log(Level,Term) when is_atom(Level) -> erlang:display_string("Logger - "++ atom_to_list(Level) ++ ": "), erlang:display(Term). +-spec timestamp() -> timestamp(). +timestamp() -> + os:system_time(microsecond). + %%%----------------------------------------------------------------- %%% Configuration -spec add_primary_filter(FilterId,Filter) -> ok | {error,term()} when @@ -1129,7 +1135,7 @@ proc_meta() -> default(pid) -> self(); default(gl) -> group_leader(); -default(time) -> erlang:system_time(microsecond). +default(time) -> timestamp(). %% Remove everything upto and including this module from the stacktrace filter_stacktrace(Module,[{Module,_,_,_}|_]) -> diff --git a/lib/kernel/src/logger_h_common.erl b/lib/kernel/src/logger_h_common.erl index e69f6de38d..4b5e0a7dd0 100644 --- a/lib/kernel/src/logger_h_common.erl +++ b/lib/kernel/src/logger_h_common.erl @@ -351,7 +351,7 @@ log_handler_info(Name, Format, Args, #{module:=Module, {ok,Conf} -> Conf; _ -> #{formatter=>{?DEFAULT_FORMATTER,?DEFAULT_FORMAT_CONFIG}} end, - Meta = #{time=>erlang:system_time(microsecond)}, + Meta = #{time=>logger:timestamp()}, Bin = log_to_binary(#{level => notice, msg => {Format,Args}, meta => Meta}, Config), diff --git a/lib/kernel/src/logger_simple_h.erl b/lib/kernel/src/logger_simple_h.erl index fe181722f3..a0d51dba25 100644 --- a/lib/kernel/src/logger_simple_h.erl +++ b/lib/kernel/src/logger_simple_h.erl @@ -69,7 +69,7 @@ log(#{msg:=_,meta:=#{time:=_}}=Log,_Config) -> do_log( #{level=>error, msg=>{report,{error,simple_handler_process_dead}}, - meta=>#{time=>erlang:system_time(microsecond)}}), + meta=>#{time=>logger:timestamp()}}), do_log(Log); _ -> ?MODULE ! {log,Log} @@ -129,7 +129,7 @@ drop_msg(0) -> drop_msg(N) -> [#{level=>info, msg=>{"Simple handler buffer full, dropped ~w messages",[N]}, - meta=>#{time=>erlang:system_time(microsecond)}}]. + meta=>#{time=>logger:timestamp()}}]. %%%----------------------------------------------------------------- %%% Internal diff --git a/lib/kernel/test/application_SUITE.erl b/lib/kernel/test/application_SUITE.erl index 5c35b82207..94d7c17712 100644 --- a/lib/kernel/test/application_SUITE.erl +++ b/lib/kernel/test/application_SUITE.erl @@ -31,6 +31,7 @@ otp_3002/1, otp_3184/1, otp_4066/1, otp_4227/1, otp_5363/1, otp_5606/1, start_phases/1, get_key/1, get_env/1, + set_env/1, set_env_persistent/1, set_env_errors/1, permit_false_start_local/1, permit_false_start_dist/1, script_start/1, nodedown_start/1, init2973/0, loop2973/0, loop5606/1]). @@ -55,6 +56,7 @@ all() -> load_use_cache, ensure_started, {group, reported_bugs}, start_phases, script_start, nodedown_start, permit_false_start_local, permit_false_start_dist, get_key, get_env, ensure_all_started, + set_env, set_env_persistent, set_env_errors, {group, distr_changed}, config_change, shutdown_func, shutdown_timeout, shutdown_deadlock, config_relative_paths, persistent_env]. @@ -1944,6 +1946,101 @@ get_appls([_ | T], Res) -> get_appls([], Res) -> Res. +%% Test set_env/1. +set_env(Conf) when is_list(Conf) -> + ok = application:set_env([{appinc, [{own2, persist}, {not_in_app, persist}]}, + {unknown_app, [{key, persist}]}]), + + %% own_env1 and own2 are set in appinc + undefined = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, not_in_app), + {ok, persist} = application:get_env(unknown_app, key), + + ok = application:load(appinc()), + {ok, value1} = application:get_env(appinc, own_env1), + {ok, val2} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, not_in_app), + {ok, persist} = application:get_env(unknown_app, key), + + %% On reload, values are lost + ok = application:unload(appinc), + ok = application:load(appinc()), + {ok, value1} = application:get_env(appinc, own_env1), + {ok, val2} = application:get_env(appinc, own2), + undefined = application:get_env(appinc, not_in_app), + + %% Clean up + ok = application:unload(appinc). + +%% Test set_env/2 with persistent true. +set_env_persistent(Conf) when is_list(Conf) -> + Opts = [{persistent, true}], + ok = application:set_env([{appinc, [{own2, persist}, {not_in_app, persist}]}, + {unknown_app, [{key, persist}]}], Opts), + + %% own_env1 and own2 are set in appinc + undefined = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, not_in_app), + {ok, persist} = application:get_env(unknown_app, key), + + ok = application:load(appinc()), + {ok, value1} = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, not_in_app), + {ok, persist} = application:get_env(unknown_app, key), + + %% On reload, values are not lost + ok = application:unload(appinc), + ok = application:load(appinc()), + {ok, value1} = application:get_env(appinc, own_env1), + {ok, persist} = application:get_env(appinc, own2), + {ok, persist} = application:get_env(appinc, not_in_app), + + %% Clean up + ok = application:unload(appinc). + +set_env_errors(Conf) when is_list(Conf) -> + "application: 1; application name must be an atom" = + badarg_msg(fun() -> application:set_env([{1, []}]) end), + + "application: foo; parameters must be a list" = + badarg_msg(fun() -> application:set_env([{foo, bar}]) end), + + "invalid application config: foo_bar" = + badarg_msg(fun() -> application:set_env([foo_bar]) end), + + "application: foo; invalid parameter name: 1" = + badarg_msg(fun() -> application:set_env([{foo, [{1, 2}]}]) end), + + "application: foo; invalid parameter: config" = + badarg_msg(fun() -> application:set_env([{foo, [config]}]) end), + + "application: kernel; erroneous parameter: distributed" = + badarg_msg(fun() -> application:set_env([{kernel, [{distributed, config}]}]) end), + + %% This will raise in the future + ct:capture_start(), + _ = application:set_env([{foo, []}, {foo, []}]), + timer:sleep(100), + ct:capture_stop(), + [_ | _] = string:find(ct:capture_get(), "duplicate application config: foo"), + + ct:capture_start(), + _ = application:set_env([{foo, [{bar, baz}, {bar, bat}]}]), + timer:sleep(100), + ct:capture_stop(), + [_ | _] = string:find(ct:capture_get(), "application: foo; duplicate parameter: bar"), + + ok. + +badarg_msg(Fun) -> + try Fun() of + _ -> ct:fail(try_succeeded) + catch + error:{badarg, Msg} -> Msg + end. %% Test set_env/4 and unset_env/3 with persistent true. persistent_env(Conf) when is_list(Conf) -> diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl index 8b33f4a679..44ec7e7076 100644 --- a/lib/kernel/test/inet_SUITE.erl +++ b/lib/kernel/test/inet_SUITE.erl @@ -21,6 +21,7 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("kernel/include/inet.hrl"). +-include_lib("kernel/src/inet_res.hrl"). -include_lib("kernel/src/inet_dns.hrl"). -export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1, @@ -34,7 +35,7 @@ ipv4_to_ipv6/0, ipv4_to_ipv6/1, host_and_addr/0, host_and_addr/1, t_gethostnative/1, - gethostnative_parallell/1, cname_loop/1, + gethostnative_parallell/1, cname_loop/1, missing_hosts_reload/1, gethostnative_soft_restart/0, gethostnative_soft_restart/1, gethostnative_debug_level/0, gethostnative_debug_level/1, lookup_bad_search_option/1, @@ -56,7 +57,7 @@ all() -> [t_gethostbyaddr, t_gethostbyname, t_getaddr, t_gethostbyaddr_v6, t_gethostbyname_v6, t_getaddr_v6, ipv4_to_ipv6, host_and_addr, {group, parse}, - t_gethostnative, gethostnative_parallell, cname_loop, + t_gethostnative, gethostnative_parallell, cname_loop, missing_hosts_reload, gethostnative_debug_level, gethostnative_soft_restart, lookup_bad_search_option, getif, getif_ifr_name_overflow, getservbyname_overflow, @@ -840,6 +841,32 @@ cname_loop(Config) when is_list(Config) -> ok. +%% Test that hosts file gets reloaded correctly in case when it +% was missing during initial startup +missing_hosts_reload(Config) when is_list(Config) -> + RootDir = proplists:get_value(priv_dir,Config), + HostsFile = filename:join(RootDir, atom_to_list(?MODULE) ++ ".hosts"), + InetRc = filename:join(RootDir, "inetrc"), + ok = file:write_file(InetRc, "{hosts_file, \"" ++ HostsFile ++ "\"}.\n"), + {error, enoent} = file:read_file_info(HostsFile), + % start a node + Pa = filename:dirname(code:which(?MODULE)), + {ok, TestNode} = test_server:start_node(?MODULE, slave, + [{args, "-pa " ++ Pa ++ " -kernel inetrc '\"" ++ InetRc ++ "\"'"}]), + % ensure it has our RC + Rc = rpc:call(TestNode, inet_db, get_rc, []), + {hosts_file, HostsFile} = lists:keyfind(hosts_file, 1, Rc), + % ensure it does not resolve + {error, nxdomain} = rpc:call(TestNode, inet_hosts, gethostbyname, ["somehost"]), + % write hosts file + ok = file:write_file(HostsFile, "1.2.3.4 somehost"), + % wait for cached timestamp to expire + timer:sleep(?RES_FILE_UPDATE_TM * 1000 + 100), + % ensure it DOES resolve + {ok,{hostent,"somehost",[],inet,4,[{1,2,3,4}]}} = + rpc:call(TestNode, inet_hosts, gethostbyname, ["somehost"]), + % cleanup + true = test_server:stop_node(TestNode). %% These must be run in the whole suite since they need %% the host list and require inet_gethost_native to be started. diff --git a/lib/kernel/test/logger_SUITE.erl b/lib/kernel/test/logger_SUITE.erl index 2dad651f9c..70bb775db8 100644 --- a/lib/kernel/test/logger_SUITE.erl +++ b/lib/kernel/test/logger_SUITE.erl @@ -899,14 +899,14 @@ process_metadata(_Config) -> undefined = logger:get_process_metadata(), {error,badarg} = ?TRY(logger:set_process_metadata(bad)), ok = logger:add_handler(h1,?MODULE,#{level=>notice,filter_default=>log}), - Time = erlang:system_time(microsecond), + Time = logger:timestamp(), ProcMeta = #{time=>Time,line=>0,custom=>proc}, ok = logger:set_process_metadata(ProcMeta), S1 = ?str, ?LOG_NOTICE(S1,#{custom=>macro}), check_logged(notice,S1,#{time=>Time,line=>0,custom=>macro}), - Time2 = erlang:system_time(microsecond), + Time2 = logger:timestamp(), S2 = ?str, ?LOG_NOTICE(S2,#{time=>Time2,line=>1,custom=>macro}), check_logged(notice,S2,#{time=>Time2,line=>1,custom=>macro}), diff --git a/lib/kernel/test/logger_formatter_SUITE.erl b/lib/kernel/test/logger_formatter_SUITE.erl index 8c13f0f908..83e3e6c40a 100644 --- a/lib/kernel/test/logger_formatter_SUITE.erl +++ b/lib/kernel/test/logger_formatter_SUITE.erl @@ -867,7 +867,7 @@ my_try(Fun) -> try Fun() catch C:R:S -> {C,R,hd(S)} end. timestamp() -> - erlang:system_time(microsecond). + logger:timestamp(). %% necessary? add_time(#{time:=_}=Meta) -> diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml index a2dd1f29b7..b145aac6ab 100644 --- a/lib/ssl/doc/src/ssl.xml +++ b/lib/ssl/doc/src/ssl.xml @@ -89,9 +89,12 @@ <datatype> <name name="active_msgs"/> <desc> - <p>When an TLS/DTLS socket is in active mode (the default), data from the + <p>When a TLS/DTLS socket is in active mode (the default), data from the socket is delivered to the owner of the socket in the form of messages as described above.</p> + <p>The <c>ssl_passive</c> message is sent only when the socket is in + <c>{active, N}</c> mode and the counter dropped to 0. It indicates + that the socket has transitioned to passive (<c>{active, false}</c>) mode.</p> </desc> </datatype> @@ -1204,8 +1207,8 @@ fun(srp, Username :: string(), UserState :: term()) -> marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>. </p> - <p> If the option <c>active</c> is set to <c>once</c> or <c>true</c> the - process owning the sslsocket will receive messages of type + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type <seealso marker="#type-active_msgs"> active_msgs() </seealso> </p> </desc> @@ -1252,8 +1255,8 @@ fun(srp, Username :: string(), UserState :: term()) -> marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>. </p> - <p> If the option <c>active</c> is set to <c>once</c> or <c>true</c> the - process owning the sslsocket will receive messages of type + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type <seealso marker="#type-active_msgs"> active_msgs() </seealso> </p> </desc> @@ -1414,8 +1417,8 @@ fun(srp, Username :: string(), UserState :: term()) -> <p>Performs the SSL/TLS/DTLS server-side handshake.</p> <p>Returns a new TLS/DTLS socket if the handshake is successful.</p> - <p> If the option <c>active</c> is set to <c>once</c> or <c>true</c> the - process owning the sslsocket will receive messages of type + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type <seealso marker="#type-active_msgs"> active_msgs() </seealso> </p> </desc> @@ -1459,8 +1462,8 @@ fun(srp, Username :: string(), UserState :: term()) -> marker="#handshake_cancel-1"><c>handshake_cancel/1</c></seealso>. </p> - <p> If the option <c>active</c> is set to <c>once</c> or <c>true</c> the - process owning the sslsocket will receive messages of type + <p> If the option <c>active</c> is set to <c>once</c>, <c>true</c> or an integer value, + the process owning the sslsocket will receive messages of type <seealso marker="#type-active_msgs"> active_msgs() </seealso> </p> diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl index afcd4af000..e0423b07b4 100644 --- a/lib/ssl/src/dtls_packet_demux.erl +++ b/lib/ssl/src/dtls_packet_demux.erl @@ -298,6 +298,9 @@ do_set_emulated_opts([], Opts) -> Opts; do_set_emulated_opts([{mode, Value} | Rest], Opts) -> do_set_emulated_opts(Rest, Opts#socket_options{mode = Value}); +do_set_emulated_opts([{active, N0} | Rest], Opts=#socket_options{active = Active}) when is_integer(N0) -> + N = tls_socket:update_active_n(N0, Active), + do_set_emulated_opts(Rest, Opts#socket_options{active = N}); do_set_emulated_opts([{active, Value} | Rest], Opts) -> do_set_emulated_opts(Rest, Opts#socket_options{active = Value}). diff --git a/lib/ssl/src/dtls_socket.erl b/lib/ssl/src/dtls_socket.erl index 2001afd02f..4d07372e31 100644 --- a/lib/ssl/src/dtls_socket.erl +++ b/lib/ssl/src/dtls_socket.erl @@ -38,7 +38,9 @@ listen(Port, #config{transport_info = TransportInfo, case dtls_listener_sup:start_child([Port, TransportInfo, emulated_socket_options(EmOpts, #socket_options{}), Options ++ internal_inet_values(), SslOpts]) of {ok, Pid} -> - {ok, #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}}; + Socket = #sslsocket{pid = {dtls, Config#config{dtls_handler = {Pid, Port}}}}, + check_active_n(EmOpts, Socket), + {ok, Socket}; Err = {error, _} -> Err end. @@ -81,8 +83,9 @@ socket(Pids, Transport, Socket, ConnectionCb) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb}}. -setopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> - SplitOpts = tls_socket:split_options(Options), +setopts(_, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> + SplitOpts = {_, EmOpts} = tls_socket:split_options(Options), + check_active_n(EmOpts, Socket), dtls_packet_demux:set_sock_opts(ListenPid, SplitOpts); %%% Following clauses will not be called for emulated options, they are handled in the connection process setopts(gen_udp, Socket, Options) -> @@ -90,6 +93,32 @@ setopts(gen_udp, Socket, Options) -> setopts(Transport, Socket, Options) -> Transport:setopts(Socket, Options). +check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}) -> + %% We check the resulting options to send an ssl_passive message if necessary. + case proplists:lookup(active, EmulatedOpts) of + %% The provided value is out of bound. + {_, N} when is_integer(N), N < -32768 -> + throw(einval); + {_, N} when is_integer(N), N > 32767 -> + throw(einval); + {_, N} when is_integer(N) -> + {ok, #socket_options{active = Active}, _} = dtls_packet_demux:get_all_opts(ListenPid), + case Active of + Atom when is_atom(Atom), N =< 0 -> + self() ! {ssl_passive, Socket}; + %% The result of the addition is out of bound. + %% We do not need to check < -32768 because Active can't be below 1. + A when is_integer(A), A + N > 32767 -> + throw(einval); + A when is_integer(A), A + N =< 0 -> + self() ! {ssl_passive, Socket}; + _ -> + ok + end; + _ -> + ok + end. + getopts(_, #sslsocket{pid = {dtls, #config{dtls_handler = {ListenPid, _}}}}, Options) -> SplitOpts = tls_socket:split_options(Options), dtls_packet_demux:get_sock_opts(ListenPid, SplitOpts); @@ -161,9 +190,18 @@ emulated_socket_options(InetValues, #socket_options{ mode = proplists:get_value(mode, InetValues, Mode), packet = proplists:get_value(packet, InetValues, Packet), packet_size = proplists:get_value(packet_size, InetValues, PacketSize), - active = proplists:get_value(active, InetValues, Active) + active = emulated_active_option(InetValues, Active) }. +emulated_active_option([], Active) -> + Active; +emulated_active_option([{active, Active} | _], _) when Active =< 0 -> + false; +emulated_active_option([{active, Active} | _], _) -> + Active; +emulated_active_option([_|Tail], Active) -> + emulated_active_option(Tail, Active). + emulated_options([{mode, Value} = Opt |Opts], Inet, Emulated) -> validate_inet_option(mode, Value), emulated_options(Opts, Inet, [Opt | proplists:delete(mode, Emulated)]); @@ -185,6 +223,9 @@ validate_inet_option(mode, Value) when Value =/= list, Value =/= binary -> throw({error, {options, {mode,Value}}}); validate_inet_option(active, Value) + when Value >= -32768, Value =< 32767 -> + ok; +validate_inet_option(active, Value) when Value =/= true, Value =/= false, Value =/= once -> throw({error, {options, {active,Value}}}); validate_inet_option(_, _) -> diff --git a/lib/ssl/src/ssl.erl b/lib/ssl/src/ssl.erl index 3516bd6d49..5a2d31ffc2 100644 --- a/lib/ssl/src/ssl.erl +++ b/lib/ssl/src/ssl.erl @@ -94,7 +94,7 @@ -type tls_client_option() :: client_option() | common_option() | socket_option() | transport_option(). -type tls_server_option() :: server_option() | common_option() | socket_option() | transport_option(). -type active_msgs() :: {ssl, sslsocket(), Data::binary() | list()} | {ssl_closed, sslsocket()} | - {ssl_error, sslsocket(), Reason::term()}. + {ssl_error, sslsocket(), Reason::term()} | {ssl_passive, sslsocket()}. -type transport_option() :: {cb_info, {CallbackModule::atom(), DataTag::atom(), ClosedTag::atom(), ErrTag::atom()}}. -type host() :: hostname() | ip_address(). diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl index f194610d72..422c4c94ae 100644 --- a/lib/ssl/src/ssl_connection.erl +++ b/lib/ssl/src/ssl_connection.erl @@ -1383,10 +1383,22 @@ handle_call({get_opts, OptTags}, From, _, {keep_state_and_data, [{reply, From, OptsReply}]}; handle_call({set_opts, Opts0}, From, StateName, #state{static_env = #static_env{socket = Socket, - transport_cb = Transport}, + transport_cb = Transport, + tracker = Tracker}, + connection_env = + #connection_env{user_application = {_Mon, Pid}}, socket_options = Opts1 } = State0, Connection) -> {Reply, Opts} = set_socket_opts(Connection, Transport, Socket, Opts0, Opts1, []), + case {proplists:lookup(active, Opts0), Opts} of + {{_, N}, #socket_options{active=false}} when is_integer(N) -> + send_user( + Pid, + format_passive( + Connection:pids(State0), Transport, Socket, Tracker, Connection)); + _ -> + ok + end, State = State0#state{socket_options = Opts}, handle_active_option(Opts#socket_options.active, StateName, From, Reply, State); @@ -2516,6 +2528,30 @@ set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active}| Opts], SockO Active == false -> set_socket_opts(ConnectionCb, Transport, Socket, Opts, SockOpts#socket_options{active = Active}, Other); +set_socket_opts(ConnectionCb, Transport, Socket, [{active, Active1} = Opt| Opts], + SockOpts=#socket_options{active = Active0}, Other) + when Active1 >= -32768, Active1 =< 32767 -> + Active = if + is_integer(Active0), Active0 + Active1 < -32768 -> + error; + is_integer(Active0), Active0 + Active1 =< 0 -> + false; + is_integer(Active0), Active0 + Active1 > 32767 -> + error; + Active1 =< 0 -> + false; + is_integer(Active0) -> + Active0 + Active1; + true -> + Active1 + end, + case Active of + error -> + {{error, {options, {socket_options, Opt}} }, SockOpts}; + _ -> + set_socket_opts(ConnectionCb, Transport, Socket, Opts, + SockOpts#socket_options{active = Active}, Other) + end; set_socket_opts(_,_, _, [{active, _} = Opt| _], SockOpts, _) -> {{error, {options, {socket_options, Opt}} }, SockOpts}; set_socket_opts(ConnectionCb, Transport, Socket, [Opt | Opts], SockOpts, Other) -> @@ -2795,6 +2831,14 @@ deliver_app_data( case Active of once -> SO#socket_options{active=false}; + 1 -> + send_user( + Pid, + format_passive( + CPids, Transport, Socket, Tracker, Connection)), + SO#socket_options{active=false}; + N when is_integer(N) -> + SO#socket_options{active=N - 1}; _ -> SO end. @@ -2831,6 +2875,9 @@ do_format_reply(list, Packet, _, Data) do_format_reply(list, _,_, Data) -> binary_to_list(Data). +format_passive(CPids, Transport, Socket, Tracker, Connection) -> + {ssl_passive, Connection:socket(CPids, Transport, Socket, Tracker)}. + header(0, <<>>) -> <<>>; header(_, <<>>) -> diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl index 6b1e3b6e07..260f603e90 100644 --- a/lib/ssl/src/ssl_handshake.erl +++ b/lib/ssl/src/ssl_handshake.erl @@ -39,7 +39,7 @@ -type oid() :: tuple(). -type public_key_params() :: #'Dss-Parms'{} | {namedCurve, oid()} | #'ECParameters'{} | term(). -type public_key_info() :: {oid(), #'RSAPublicKey'{} | integer() | #'ECPoint'{}, public_key_params()}. --type ssl_handshake_history() :: {[binary()], [binary()]}. +-type ssl_handshake_history() :: {iodata(), iodata()}. -type ssl_handshake() :: #server_hello{} | #server_hello_done{} | #certificate{} | #certificate_request{} | #client_key_exchange{} | #finished{} | #certificate_verify{} | @@ -76,7 +76,7 @@ handle_client_hello_extensions/9, %% Returns server hello extensions handle_server_hello_extensions/9, select_curve/2, select_curve/3, select_hashsign/4, select_hashsign/5, - select_hashsign_algs/3, empty_extensions/2, add_server_share/2 + select_hashsign_algs/3, empty_extensions/2, add_server_share/3 ]). -export([get_cert_params/1]). @@ -1150,12 +1150,18 @@ maybe_add_key_share(HelloExtensions, KeyShare) -> HelloExtensions#{key_share => #key_share_client_hello{ client_shares = ClientShares}}. -add_server_share(Extensions, KeyShare) -> +add_server_share(server_hello, Extensions, KeyShare) -> #key_share_server_hello{server_share = ServerShare0} = KeyShare, %% Keep only public keys ServerShare = kse_remove_private_key(ServerShare0), Extensions#{key_share => #key_share_server_hello{ - server_share = ServerShare}}. + server_share = ServerShare}}; +add_server_share(hello_retry_request, Extensions, + #key_share_server_hello{ + server_share = #key_share_entry{group = Group}}) -> + Extensions#{key_share => #key_share_hello_retry_request{ + selected_group = Group}}. + kse_remove_private_key(#key_share_entry{ group = Group, diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl index de786d0875..71ac6a9310 100644 --- a/lib/ssl/src/tls_connection_1_3.erl +++ b/lib/ssl/src/tls_connection_1_3.erl @@ -113,27 +113,27 @@ wait_finished/4 ]). -start(internal, - #client_hello{} = Hello, - #state{connection_states = _ConnectionStates0, - ssl_options = #ssl_options{ciphers = _ServerCiphers, - signature_algs = _ServerSignAlgs, - signature_algs_cert = _SignatureSchemes, %% TODO: Check?? - supported_groups = _ServerGroups0, - versions = _Versions} = SslOpts, - session = #session{own_certificate = Cert}} = State0, - _Module) -> - Env = #{cert => Cert}, - case tls_handshake_1_3:handle_client_hello(Hello, SslOpts, Env) of +start(internal, + #change_cipher_spec{} = ChangeCipherSpec, State0, _Module) -> + case tls_handshake_1_3:do_start(ChangeCipherSpec, State0) of #alert{} = Alert -> ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); - M -> - %% update connection_states with cipher - State = update_state(State0, M), - {next_state, negotiated, State, [{next_event, internal, M}]} - - end. + State1 -> + {Record, State} = tls_connection:next_record(State1), + tls_connection:next_event(?FUNCTION_NAME, Record, State) + end; +start(internal, #client_hello{} = Hello, State0, _Module) -> + case tls_handshake_1_3:do_start(Hello, State0) of + #alert{} = Alert -> + ssl_connection:handle_own_alert(Alert, {3,4}, start, State0); + {State, _, start} -> + {next_state, start, State, []}; + {State, Context, negotiated} -> + {next_state, negotiated, State, [{next_event, internal, Context}]} + end; +start(Type, Msg, State, Connection) -> + ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). negotiated(internal, Map, State0, _Module) -> @@ -166,24 +166,3 @@ wait_finished(internal, end; wait_finished(Type, Msg, State, Connection) -> ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection). - - -update_state(#state{connection_states = ConnectionStates0, - connection_env = CEnv, - session = Session} = State, - #{cipher := Cipher, - key_share := KeyShare, - session_id := SessionId}) -> - #{security_parameters := SecParamsR0} = PendingRead = - maps:get(pending_read, ConnectionStates0), - #{security_parameters := SecParamsW0} = PendingWrite = - maps:get(pending_write, ConnectionStates0), - SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher), - SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher), - ConnectionStates = - ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR}, - pending_write => PendingWrite#{security_parameters => SecParamsW}}, - State#state{connection_states = ConnectionStates, - key_share = KeyShare, - session = Session#session{session_id = SessionId}, - connection_env = CEnv#connection_env{negotiated_version = {3,4}}}. diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl index 6a6de4b988..3bc1290361 100644 --- a/lib/ssl/src/tls_handshake_1_3.erl +++ b/lib/ssl/src/tls_handshake_1_3.erl @@ -36,38 +36,49 @@ %% Encode -export([encode_handshake/1, decode_handshake/2]). -%% Handshake --export([handle_client_hello/3]). - %% Create handshake messages -export([certificate/5, certificate_verify/4, encrypted_extensions/0, server_hello/4]). --export([do_negotiated/2, +-export([do_start/2, + do_negotiated/2, do_wait_finished/2]). %%==================================================================== %% Create handshake messages %%==================================================================== -server_hello(SessionId, KeyShare, ConnectionStates, _Map) -> +server_hello(MsgType, SessionId, KeyShare, ConnectionStates) -> #{security_parameters := SecParams} = ssl_record:pending_connection_state(ConnectionStates, read), - Extensions = server_hello_extensions(KeyShare), + Extensions = server_hello_extensions(MsgType, KeyShare), #server_hello{server_version = {3,3}, %% legacy_version cipher_suite = SecParams#security_parameters.cipher_suite, compression_method = 0, %% legacy attribute - random = SecParams#security_parameters.server_random, + random = server_hello_random(MsgType, SecParams), session_id = SessionId, extensions = Extensions }. -server_hello_extensions(KeyShare) -> +server_hello_extensions(MsgType, KeyShare) -> SupportedVersions = #server_hello_selected_version{selected_version = {3,4}}, Extensions = #{server_hello_selected_version => SupportedVersions}, - ssl_handshake:add_server_share(Extensions, KeyShare). + ssl_handshake:add_server_share(MsgType, Extensions, KeyShare). + +server_hello_random(server_hello, #security_parameters{server_random = Random}) -> + Random; +%% For reasons of backward compatibility with middleboxes (see +%% Appendix D.4), the HelloRetryRequest message uses the same structure +%% as the ServerHello, but with Random set to the special value of the +%% SHA-256 of "HelloRetryRequest": +%% +%% CF 21 AD 74 E5 9A 61 11 BE 1D 8C 02 1E 65 B8 91 +%% C2 A2 11 16 7A BB 8C 5E 07 9E 09 E2 C8 A8 33 9C +server_hello_random(hello_retry_request, _) -> + crypto:hash(sha256, "HelloRetryRequest"). + %% TODO: implement support for encrypted_extensions encrypted_extensions() -> @@ -75,6 +86,7 @@ encrypted_extensions() -> extensions = #{} }. + %% TODO: use maybe monad for error handling! %% enum { %% X509(0), @@ -361,20 +373,44 @@ build_content(Context, THash) -> Prefix = binary:copy(<<32>>, 64), <<Prefix/binary,Context/binary,?BYTE(0),THash/binary>>. + %%==================================================================== %% Handle handshake messages %%==================================================================== -handle_client_hello(#client_hello{cipher_suites = ClientCiphers, - session_id = SessionId, - extensions = Extensions} = _Hello, - #ssl_options{ciphers = ServerCiphers, - signature_algs = ServerSignAlgs, - signature_algs_cert = _SignatureSchemes, %% TODO: Check?? - supported_groups = ServerGroups0} = _SslOpts, - Env) -> - Cert = maps:get(cert, Env, undefined), +do_start(#change_cipher_spec{}, + #state{connection_states = _ConnectionStates0, + session = #session{session_id = _SessionId, + own_certificate = _OwnCert}, + ssl_options = #ssl_options{} = _SslOpts, + key_share = _KeyShare, + handshake_env = #handshake_env{tls_handshake_history = _HHistory0}, + static_env = #static_env{ + cert_db = _CertDbHandle, + cert_db_ref = _CertDbRef, + socket = _Socket, + transport_cb = _Transport} + } = State0) -> + %% {Ref,Maybe} = maybe(), + + try + + State0 + + catch + {_Ref, {state_not_implemented, State}} -> + ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, {state_not_implemented, State}) + end; +do_start(#client_hello{cipher_suites = ClientCiphers, + session_id = SessionId, + extensions = Extensions} = _Hello, + #state{connection_states = _ConnectionStates0, + ssl_options = #ssl_options{ciphers = ServerCiphers, + signature_algs = ServerSignAlgs, + signature_algs_cert = _SignatureSchemes, %% TODO: check! + supported_groups = ServerGroups0}, + session = #session{own_certificate = Cert}} = State0) -> ClientGroups0 = maps:get(elliptic_curves, Extensions, undefined), ClientGroups = get_supported_groups(ClientGroups0), @@ -398,11 +434,9 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, %% and a signature algorithm/certificate pair to authenticate itself to %% the client. Cipher = Maybe(select_cipher_suite(ClientCiphers, ServerCiphers)), - Group = Maybe(select_server_group(ServerGroups, ClientGroups)), + Groups = Maybe(select_common_groups(ServerGroups, ClientGroups)), Maybe(validate_key_share(ClientGroups, ClientShares)), - ClientPubKey = Maybe(get_client_public_key(Group, ClientShares)), - {PublicKeyAlgo, SignAlgo, SignHash} = get_certificate_params(Cert), %% Check if client supports signature algorithm of server certificate @@ -411,14 +445,31 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, %% Select signature algorithm (used in CertificateVerify message). SelectedSignAlg = Maybe(select_sign_algo(PublicKeyAlgo, ClientSignAlgs, ServerSignAlgs)), + %% Select client public key. If no public key found in ClientShares or + %% ClientShares is empty, trigger HelloRetryRequest as we were able + %% to find an acceptable set of parameters but the ClientHello does not + %% contain sufficient information. + {Group, ClientPubKey} = get_client_public_key(Groups, ClientShares), + %% Generate server_share KeyShare = ssl_cipher:generate_server_share(Group), - _Ret = #{cipher => Cipher, - group => Group, - sign_alg => SelectedSignAlg, - client_share => ClientPubKey, - key_share => KeyShare, - session_id => SessionId} + + State1 = update_start_state(State0, Cipher, KeyShare, SessionId), + + %% 4.1.4. Hello Retry Request + %% + %% The server will send this message in response to a ClientHello + %% message if it is able to find an acceptable set of parameters but the + %% ClientHello does not contain sufficient information to proceed with + %% the handshake. + {State2, NextState} = + Maybe(send_hello_retry_request(State1, ClientPubKey, KeyShare, SessionId)), + + %% TODO: Add Context to state? + Context = #{group => Group, + sign_alg => SelectedSignAlg, + client_share => ClientPubKey}, + {State2, Context, NextState} %% TODO: %% - session handling @@ -430,9 +481,6 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_groups); {Ref, illegal_parameter} -> ?ALERT_REC(?FATAL, ?ILLEGAL_PARAMETER); - {Ref, {hello_retry_request, _Group0}} -> - %% TODO - ?ALERT_REC(?FATAL, ?INTERNAL_ERROR, "hello_retry_request not implemented"); {Ref, no_suitable_cipher} -> ?ALERT_REC(?FATAL, ?INSUFFICIENT_SECURITY, no_suitable_cipher); {Ref, {insufficient_security, no_suitable_signature_algorithm}} -> @@ -445,7 +493,7 @@ handle_client_hello(#client_hello{cipher_suites = ClientCiphers, do_negotiated(#{client_share := ClientKey, group := SelectedGroup, sign_alg := SignatureScheme - } = Map, + }, #state{connection_states = ConnectionStates0, session = #session{session_id = SessionId, own_certificate = OwnCert}, @@ -464,7 +512,7 @@ do_negotiated(#{client_share := ClientKey, try %% Create server_hello %% Extensions: supported_versions, key_share, (pre_shared_key) - ServerHello = server_hello(SessionId, KeyShare, ConnectionStates0, Map), + ServerHello = server_hello(server_hello, SessionId, KeyShare, ConnectionStates0), {State1, _} = tls_connection:send_handshake(ServerHello, State0), @@ -597,6 +645,56 @@ compare_verify_data(_, _) -> {error, decrypt_error}. +send_hello_retry_request(#state{connection_states = ConnectionStates0} = State0, + no_suitable_key, KeyShare, SessionId) -> + ServerHello = server_hello(hello_retry_request, SessionId, KeyShare, ConnectionStates0), + {State1, _} = tls_connection:send_handshake(ServerHello, State0), + + %% TODO: Fix handshake history! + State2 = replace_ch1_with_message_hash(State1), + + {ok, {State2, start}}; +send_hello_retry_request(State0, _, _, _) -> + %% Suitable key found. + {ok, {State0, negotiated}}. + + +%% 4.4.1. The Transcript Hash +%% +%% As an exception to this general rule, when the server responds to a +%% ClientHello with a HelloRetryRequest, the value of ClientHello1 is +%% replaced with a special synthetic handshake message of handshake type +%% "message_hash" containing Hash(ClientHello1). I.e., +%% +%% Transcript-Hash(ClientHello1, HelloRetryRequest, ... Mn) = +%% Hash(message_hash || /* Handshake type */ +%% 00 00 Hash.length || /* Handshake message length (bytes) */ +%% Hash(ClientHello1) || /* Hash of ClientHello1 */ +%% HelloRetryRequest || ... || Mn) +%% +%% NOTE: Hash.length is used in practice (openssl) and not message length! +%% It is most probably a fault in the RFC. +replace_ch1_with_message_hash(#state{connection_states = ConnectionStates, + handshake_env = + #handshake_env{ + tls_handshake_history = + {[HRR,CH1|HHistory], LM}} = HSEnv} = State0) -> + #{security_parameters := SecParamsR} = + ssl_record:pending_connection_state(ConnectionStates, read), + #security_parameters{prf_algorithm = HKDFAlgo} = SecParamsR, + MessageHash = message_hash(CH1, HKDFAlgo), + State0#state{handshake_env = + HSEnv#handshake_env{ + tls_handshake_history = + {[HRR,MessageHash|HHistory], LM}}}. + + +message_hash(ClientHello1, HKDFAlgo) -> + [?MESSAGE_HASH, + 0,0,ssl_cipher:hash_size(HKDFAlgo), + crypto:hash(HKDFAlgo, ClientHello1)]. + + calculate_handshake_secrets(ClientKey, SelectedGroup, KeyShare, #state{connection_states = ConnectionStates, handshake_env = @@ -721,6 +819,24 @@ update_connection_state(ConnectionState = #{security_parameters := SecurityParam cipher_state => cipher_init(Key, IV, FinishedKey)}. +update_start_state(#state{connection_states = ConnectionStates0, + connection_env = CEnv, + session = Session} = State, + Cipher, KeyShare, SessionId) -> + #{security_parameters := SecParamsR0} = PendingRead = + maps:get(pending_read, ConnectionStates0), + #{security_parameters := SecParamsW0} = PendingWrite = + maps:get(pending_write, ConnectionStates0), + SecParamsR = ssl_cipher:security_parameters_1_3(SecParamsR0, Cipher), + SecParamsW = ssl_cipher:security_parameters_1_3(SecParamsW0, Cipher), + ConnectionStates = + ConnectionStates0#{pending_read => PendingRead#{security_parameters => SecParamsR}, + pending_write => PendingWrite#{security_parameters => SecParamsW}}, + State#state{connection_states = ConnectionStates, + key_share = KeyShare, + session = Session#session{session_id = SessionId}, + connection_env = CEnv#connection_env{negotiated_version = {3,4}}}. + cipher_init(Key, IV, FinishedKey) -> #cipher_state{key = Key, @@ -733,17 +849,19 @@ cipher_init(Key, IV, FinishedKey) -> %% "supported_groups" and the groups supported by the server, then the %% server MUST abort the handshake with a "handshake_failure" or an %% "insufficient_security" alert. -select_server_group(_, []) -> +select_common_groups(_, []) -> {error, {insufficient_security, no_suitable_groups}}; -select_server_group(ServerGroups, [C|ClientGroups]) -> - case lists:member(C, ServerGroups) of - true -> - {ok, C}; - false -> - select_server_group(ServerGroups, ClientGroups) +select_common_groups(ServerGroups, ClientGroups) -> + Fun = fun(E) -> lists:member(E, ClientGroups) end, + case lists:filter(Fun, ServerGroups) of + [] -> + {error, {insufficient_security, no_suitable_groups}}; + L -> + {ok, L} end. + %% RFC 8446 - 4.2.8. Key Share %% This vector MAY be empty if the client is requesting a %% HelloRetryRequest. Each KeyShareEntry value MUST correspond to a @@ -771,20 +889,36 @@ validate_key_share([_|ClientGroups], [_|_] = ClientShares) -> validate_key_share(ClientGroups, ClientShares). -get_client_public_key(Group, ClientShares) -> +get_client_public_key([Group|_] = Groups, ClientShares) -> + get_client_public_key(Groups, ClientShares, Group). +%% +get_client_public_key(_, [], PreferredGroup) -> + {PreferredGroup, no_suitable_key}; +get_client_public_key([], _, PreferredGroup) -> + {PreferredGroup, no_suitable_key}; +get_client_public_key([Group|Groups], ClientShares, PreferredGroup) -> case lists:keysearch(Group, 2, ClientShares) of {value, {_, _, ClientPublicKey}} -> - {ok, ClientPublicKey}; + {Group, ClientPublicKey}; false -> - %% 4.1.4. Hello Retry Request - %% - %% The server will send this message in response to a ClientHello - %% message if it is able to find an acceptable set of parameters but the - %% ClientHello does not contain sufficient information to proceed with - %% the handshake. - {error, {hello_retry_request, Group}} + get_client_public_key(Groups, ClientShares, PreferredGroup) end. + +%% get_client_public_key(Group, ClientShares) -> +%% case lists:keysearch(Group, 2, ClientShares) of +%% {value, {_, _, ClientPublicKey}} -> +%% ClientPublicKey; +%% false -> +%% %% 4.1.4. Hello Retry Request +%% %% +%% %% The server will send this message in response to a ClientHello +%% %% message if it is able to find an acceptable set of parameters but the +%% %% ClientHello does not contain sufficient information to proceed with +%% %% the handshake. +%% no_suitable_key +%% end. + select_cipher_suite([], _) -> {error, no_suitable_cipher}; select_cipher_suite([Cipher|ClientCiphers], ServerCiphers) -> diff --git a/lib/ssl/src/tls_socket.erl b/lib/ssl/src/tls_socket.erl index a391bc53de..c3c41d3e12 100644 --- a/lib/ssl/src/tls_socket.erl +++ b/lib/ssl/src/tls_socket.erl @@ -32,6 +32,7 @@ emulated_socket_options/2, get_emulated_opts/1, set_emulated_opts/2, get_all_opts/1, handle_call/3, handle_cast/2, handle_info/2, code_change/3]). +-export([update_active_n/2]). -record(state, { emulated_opts, @@ -51,7 +52,9 @@ listen(Transport, Port, #config{transport_info = {Transport, _, _, _}, case Transport:listen(Port, Options ++ internal_inet_values()) of {ok, ListenSocket} -> {ok, Tracker} = inherit_tracker(ListenSocket, EmOpts, SslOpts), - {ok, #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}}; + Socket = #sslsocket{pid = {ListenSocket, Config#config{emulated = Tracker}}}, + check_active_n(EmOpts, Socket), + {ok, Socket}; Err = {error, _} -> Err end. @@ -117,14 +120,16 @@ socket(Pids, Transport, Socket, ConnectionCb, Tracker) -> #sslsocket{pid = Pids, %% "The name "fd" is keept for backwards compatibility fd = {Transport, Socket, ConnectionCb, Tracker}}. -setopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> +setopts(gen_tcp, Socket = #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> {SockOpts, EmulatedOpts} = split_options(Options), ok = set_emulated_opts(Tracker, EmulatedOpts), + check_active_n(EmulatedOpts, Socket), inet:setopts(ListenSocket, SockOpts); -setopts(_, #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}, +setopts(_, Socket = #sslsocket{pid = {ListenSocket, #config{transport_info = {Transport,_,_,_}, emulated = Tracker}}}, Options) -> {SockOpts, EmulatedOpts} = split_options(Options), ok = set_emulated_opts(Tracker, EmulatedOpts), + check_active_n(EmulatedOpts, Socket), Transport:setopts(ListenSocket, SockOpts); %%% Following clauses will not be called for emulated options, they are handled in the connection process setopts(gen_tcp, Socket, Options) -> @@ -132,6 +137,31 @@ setopts(gen_tcp, Socket, Options) -> setopts(Transport, Socket, Options) -> Transport:setopts(Socket, Options). +check_active_n(EmulatedOpts, Socket = #sslsocket{pid = {_, #config{emulated = Tracker}}}) -> + %% We check the resulting options to send an ssl_passive message if necessary. + case proplists:lookup(active, EmulatedOpts) of + %% The provided value is out of bound. + {_, N} when is_integer(N), N < -32768 -> + throw(einval); + {_, N} when is_integer(N), N > 32767 -> + throw(einval); + {_, N} when is_integer(N) -> + case get_emulated_opts(Tracker, [active]) of + [{_, false}] -> + self() ! {ssl_passive, Socket}, + ok; + %% The result of the addition is out of bound. + [{_, A}] when is_integer(A), A < -32768 -> + throw(einval); + [{_, A}] when is_integer(A), A > 32767 -> + throw(einval); + _ -> + ok + end; + _ -> + ok + end. + getopts(gen_tcp, #sslsocket{pid = {ListenSocket, #config{emulated = Tracker}}}, Options) -> {SockOptNames, EmulatedOptNames} = split_options(Options), EmulatedOpts = get_emulated_opts(Tracker, EmulatedOptNames), @@ -209,7 +239,7 @@ start_link(Port, SockOpts, SslOpts) -> init([Port, Opts, SslOpts]) -> process_flag(trap_exit, true), true = link(Port), - {ok, #state{emulated_opts = Opts, port = Port, ssl_opts = SslOpts}}. + {ok, #state{emulated_opts = do_set_emulated_opts(Opts, []), port = Port, ssl_opts = SslOpts}}. %%-------------------------------------------------------------------- -spec handle_call(msg(), from(), #state{}) -> {reply, reply(), #state{}}. @@ -304,9 +334,24 @@ split_options([Name | Opts], Emu, SocketOptNames, EmuOptNames) -> do_set_emulated_opts([], Opts) -> Opts; +do_set_emulated_opts([{active, N0} | Rest], Opts) when is_integer(N0) -> + N = update_active_n(N0, proplists:get_value(active, Opts, false)), + do_set_emulated_opts(Rest, [{active, N} | proplists:delete(active, Opts)]); do_set_emulated_opts([{Name,_} = Opt | Rest], Opts) -> do_set_emulated_opts(Rest, [Opt | proplists:delete(Name, Opts)]). +update_active_n(New, Current) -> + if + is_integer(Current), New + Current =< 0 -> + false; + is_integer(Current) -> + New + Current; + New =< 0 -> + false; + true -> + New + end. + get_socket_opts(_, [], _) -> []; get_socket_opts(ListenSocket, SockOptNames, Cb) -> @@ -366,6 +411,9 @@ validate_inet_option(header, Value) when not is_integer(Value) -> throw({error, {options, {header,Value}}}); validate_inet_option(active, Value) + when Value >= -32768, Value =< 32767 -> + ok; +validate_inet_option(active, Value) when Value =/= true, Value =/= false, Value =/= once -> throw({error, {options, {active,Value}}}); validate_inet_option(_, _) -> diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl index f109d85e49..292916692d 100644 --- a/lib/ssl/test/ssl_basic_SUITE.erl +++ b/lib/ssl/test/ssl_basic_SUITE.erl @@ -167,6 +167,7 @@ api_tests() -> accept_pool, prf, socket_options, + active_n, cipher_suites, handshake_continue, handshake_continue_timeout, @@ -246,6 +247,7 @@ error_handling_tests()-> [close_transport_accept, recv_active, recv_active_once, + recv_active_n, recv_error_handling, call_in_error_state, close_in_error_state, @@ -277,7 +279,9 @@ tls13_test_group() -> tls_record_1_3_encode_decode, tls13_finished_verify_data, tls13_1_RTT_handshake, - tls13_basic_ssl_s_client]. + tls13_basic_ssl_server_openssl_client, + tls13_custom_groups_ssl_server_openssl_client, + tls13_hello_retry_request_ssl_server_openssl_client]. %%-------------------------------------------------------------------- init_per_suite(Config0) -> @@ -1991,7 +1995,7 @@ recv_active(Config) when is_list(Config) -> %%-------------------------------------------------------------------- recv_active_once() -> - [{doc,"Test recv on active socket"}]. + [{doc,"Test recv on active (once) socket"}]. recv_active_once(Config) when is_list(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), @@ -2016,6 +2020,178 @@ recv_active_once(Config) when is_list(Config) -> ssl_test_lib:close(Client). %%-------------------------------------------------------------------- +recv_active_n() -> + [{doc,"Test recv on active (n) socket"}]. + +recv_active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config), + Server = + ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ServerOpts]}]), + Port = ssl_test_lib:inet_port(Server), + Client = + ssl_test_lib:start_client([{node, ClientNode}, {port, Port}, + {host, Hostname}, + {from, self()}, + {mfa, {?MODULE, try_recv_active_once, []}}, + {options, [{active, 1} | ClientOpts]}]), + + ssl_test_lib:check_result(Server, ok, Client, ok), + + ssl_test_lib:close(Server), + ssl_test_lib:close(Client). + +%%-------------------------------------------------------------------- +%% Test case adapted from gen_tcp_misc_SUITE. +active_n() -> + [{doc,"Test {active,N} option"}]. + +active_n(Config) when is_list(Config) -> + ClientOpts = ssl_test_lib:ssl_options(client_opts, Config), + ServerOpts = ssl_test_lib:ssl_options(server_opts, Config), + Port = ssl_test_lib:inet_port(node()), + N = 3, + LS = ok(ssl:listen(Port, [{active,N}|ServerOpts])), + [{active,N}] = ok(ssl:getopts(LS, [active])), + active_n_common(LS, N), + Self = self(), + spawn_link(fun() -> + S0 = ok(ssl:transport_accept(LS)), + {ok, S} = ssl:handshake(S0), + ok = ssl:setopts(S, [{active,N}]), + [{active,N}] = ok(ssl:getopts(S, [active])), + ssl:controlling_process(S, Self), + Self ! {server, S} + end), + C = ok(ssl:connect("localhost", Port, [{active,N}|ClientOpts])), + [{active,N}] = ok(ssl:getopts(C, [active])), + S = receive + {server, S0} -> S0 + after + 1000 -> + exit({error, connect}) + end, + active_n_common(C, N), + active_n_common(S, N), + ok = ssl:setopts(C, [{active,N}]), + ok = ssl:setopts(S, [{active,N}]), + ReceiveMsg = fun(Socket, Msg) -> + receive + {ssl,Socket,Msg} -> + ok; + {ssl,Socket,Begin} -> + receive + {ssl,Socket,End} -> + Msg = Begin ++ End, + ok + after 1000 -> + exit(timeout) + end + after 1000 -> + exit(timeout) + end + end, + repeat(3, fun(I) -> + Msg = "message "++integer_to_list(I), + ok = ssl:send(C, Msg), + ReceiveMsg(S, Msg), + ok = ssl:send(S, Msg), + ReceiveMsg(C, Msg) + end), + receive + {ssl_passive,S} -> + [{active,false}] = ok(ssl:getopts(S, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + receive + {ssl_passive,C} -> + [{active,false}] = ok(ssl:getopts(C, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + LS2 = ok(ssl:listen(0, [{active,0}])), + receive + {ssl_passive,LS2} -> + [{active,false}] = ok(ssl:getopts(LS2, [active])) + after + 1000 -> + exit({error,ssl_passive}) + end, + ok = ssl:close(LS2), + ok = ssl:close(C), + ok = ssl:close(S), + ok = ssl:close(LS), + ok. + +active_n_common(S, N) -> + ok = ssl:setopts(S, [{active,-N}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,0}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + ok = ssl:setopts(S, [{active,32767}]), + {error,{options,_}} = ssl:setopts(S, [{active,1}]), + {error,{options,_}} = ssl:setopts(S, [{active,-32769}]), + ok = ssl:setopts(S, [{active,-32768}]), + receive + {ssl_passive, S} -> ok + after + 1000 -> + error({error,ssl_passive_failure}) + end, + [{active,false}] = ok(ssl:getopts(S, [active])), + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,true}]), + [{active,true}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + ok = ssl:setopts(S, [{active,N}]), + ok = ssl:setopts(S, [{active,once}]), + [{active,once}] = ok(ssl:getopts(S, [active])), + receive + _ -> error({error,active_n}) + after + 0 -> + ok + end, + {error,{options,_}} = ssl:setopts(S, [{active,32768}]), + ok = ssl:setopts(S, [{active,false}]), + [{active,false}] = ok(ssl:getopts(S, [active])), + ok. + +ok({ok,V}) -> V. + +repeat(N, Fun) -> + repeat(N, N, Fun). + +repeat(N, T, Fun) when is_integer(N), N > 0 -> + Fun(T-N), + repeat(N-1, T, Fun); +repeat(_, _, _) -> + ok. + +%%-------------------------------------------------------------------- dh_params() -> [{doc,"Test to specify DH-params file in server."}]. @@ -5341,10 +5517,10 @@ tls13_finished_verify_data(_Config) -> FinishedKey = tls_v1:finished_key(BaseKey, sha256), VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages). -tls13_basic_ssl_s_client() -> +tls13_basic_ssl_server_openssl_client() -> [{doc,"Test TLS 1.3 basic connection between ssl server and openssl s_client"}]. -tls13_basic_ssl_s_client(Config) -> +tls13_basic_ssl_server_openssl_client(Config) -> ClientOpts = ssl_test_lib:ssl_options(client_rsa_opts, Config), ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), %% Set versions @@ -5363,6 +5539,55 @@ tls13_basic_ssl_s_client(Config) -> ssl_test_lib:close(Server), ssl_test_lib:close_port(Client). +tls13_custom_groups_ssl_server_openssl_client() -> + [{doc,"Test that ssl server can select a common group for key-exchange"}]. + +tls13_custom_groups_ssl_server_openssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, secp256r1, secp384r1]}|ServerOpts0], + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + ClientOpts = [{groups,"P-384:P-256:X25519"}|ClientOpts0], + Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + +tls13_hello_retry_request_ssl_server_openssl_client() -> + [{doc,"Test that ssl server can request a new group when the client's first key share" + "is not supported"}]. + +tls13_hello_retry_request_ssl_server_openssl_client(Config) -> + ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config), + ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config), + %% Set versions + ServerOpts = [{versions, ['tlsv1.2','tlsv1.3']}, + {supported_groups, [x448, x25519]}|ServerOpts0], + {_ClientNode, ServerNode, _Hostname} = ssl_test_lib:run_where(Config), + + Server = ssl_test_lib:start_server([{node, ServerNode}, {port, 0}, + {from, self()}, + {mfa, {ssl_test_lib, send_recv_result_active, []}}, + {options, ServerOpts}]), + Port = ssl_test_lib:inet_port(Server), + + ClientOpts = [{groups,"P-256:X25519"}|ClientOpts0], + Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts), + + ssl_test_lib:check_result(Server, ok), + ssl_test_lib:close(Server), + ssl_test_lib:close_port(Client). + %%-------------------------------------------------------------------- %% Internal functions ------------------------------------------------ diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl index c921dcae4c..f628b4e6d4 100644 --- a/lib/ssl/test/ssl_test_lib.erl +++ b/lib/ssl/test/ssl_test_lib.erl @@ -1110,12 +1110,19 @@ start_basic_client(openssl, Version, Port, ClientOpts) -> Cert = proplists:get_value(certfile, ClientOpts), Key = proplists:get_value(keyfile, ClientOpts), CA = proplists:get_value(cacertfile, ClientOpts), + Groups0 = proplists:get_value(groups, ClientOpts), Exe = "openssl", - Args = ["s_client", "-verify", "2", "-port", integer_to_list(Port), + Args0 = ["s_client", "-verify", "2", "-port", integer_to_list(Port), ssl_test_lib:version_flag(Version), "-cert", Cert, "-CAfile", CA, "-key", Key, "-host","localhost", "-msg", "-debug"], - + Args = + case Groups0 of + undefined -> + Args0; + G -> + Args0 ++ ["-groups", G] + end, OpenSslPort = ssl_test_lib:portable_open_port(Exe, Args), true = port_command(OpenSslPort, "Hello world"), OpenSslPort. diff --git a/lib/stdlib/src/calendar.erl b/lib/stdlib/src/calendar.erl index 3a083d9fda..3a8fe2211b 100644 --- a/lib/stdlib/src/calendar.erl +++ b/lib/stdlib/src/calendar.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1996-2018. All Rights Reserved. +%% Copyright Ericsson AB 1996-2019. 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. @@ -357,13 +357,17 @@ rfc3339_to_system_time(DateTimeString) -> rfc3339_to_system_time(DateTimeString, Options) -> Unit = proplists:get_value(unit, Options, second), %% _T is the character separating the date and the time: - {DateStr, [_T|TimeStr]} = lists:split(10, DateTimeString), - {TimeStr2, TimeStr3} = lists:split(8, TimeStr), - {ok, [Hour, Min, Sec], []} = io_lib:fread("~d:~d:~d", TimeStr2), - {ok, [Year, Month, Day], []} = io_lib:fread("~d-~d-~d", DateStr), + [Y1, Y2, Y3, Y4, $-, Mon1, Mon2, $-, D1, D2, _T, + H1, H2, $:, Min1, Min2, $:, S1, S2 | TimeStr] = DateTimeString, + Hour = list_to_integer([H1, H2]), + Min = list_to_integer([Min1, Min2]), + Sec = list_to_integer([S1, S2]), + Year = list_to_integer([Y1, Y2, Y3, Y4]), + Month = list_to_integer([Mon1, Mon2]), + Day = list_to_integer([D1, D2]), DateTime = {{Year, Month, Day}, {Hour, Min, Sec}}, IsFractionChar = fun(C) -> C >= $0 andalso C =< $9 orelse C =:= $. end, - {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr3), + {FractionStr, UtcOffset} = lists:splitwith(IsFractionChar, TimeStr), Time = datetime_to_system_time(DateTime), Secs = Time - offset_adjustment(Time, second, UtcOffset), check(DateTimeString, Options, Secs), @@ -451,8 +455,9 @@ system_time_to_rfc3339(Time, Options) -> DateTime = system_time_to_datetime(Secs), {{Year, Month, Day}, {Hour, Min, Sec}} = DateTime, FractionStr = fraction_str(Factor, AdjustedTime), - flat_fwrite("~4.10.0B-~2.10.0B-~2.10.0B~c~2.10.0B:~2.10.0B:~2.10.0B~s~s", - [Year, Month, Day, T, Hour, Min, Sec, FractionStr, Offset]). + L = [pad4(Year), "-", pad2(Month), "-", pad2(Day), [T], + pad2(Hour), ":", pad2(Min), ":", pad2(Sec), FractionStr, Offset], + lists:append(L). %% time_difference(T1, T2) = Tdiff %% @@ -680,7 +685,7 @@ offset(OffsetOption, Secs0) when OffsetOption =:= ""; Secs = abs(Secs0), Hour = Secs div 3600, Min = (Secs rem 3600) div 60, - io_lib:fwrite("~c~2.10.0B:~2.10.0B", [Sign, Hour, Min]); + [Sign | lists:append([pad2(Hour), ":", pad2(Min)])]; offset(OffsetOption, _Secs) -> OffsetOption. @@ -695,8 +700,10 @@ offset_string_adjustment(_Time, _Unit, "Z") -> 0; offset_string_adjustment(_Time, _Unit, "z") -> 0; -offset_string_adjustment(_Time, _Unit, [Sign|Tz]) -> - {ok, [Hour, Min], []} = io_lib:fread("~d:~d", Tz), +offset_string_adjustment(_Time, _Unit, Tz) -> + [Sign, H1, H2, $:, M1, M2] = Tz, + Hour = list_to_integer([H1, H2]), + Min = list_to_integer([M1, M2]), Adjustment = 3600 * Hour + 60 * Min, case Sign of $- -> -Adjustment; @@ -704,8 +711,9 @@ offset_string_adjustment(_Time, _Unit, [Sign|Tz]) -> end. local_offset(SystemTime, Unit) -> - LocalTime = system_time_to_local_time(SystemTime, Unit), + %% Not optimized for special cases. UniversalTime = system_time_to_universal_time(SystemTime, Unit), + LocalTime = erlang:universaltime_to_localtime(UniversalTime), LocalSecs = datetime_to_gregorian_seconds(LocalTime), UniversalSecs = datetime_to_gregorian_seconds(UniversalTime), LocalSecs - UniversalSecs. @@ -714,7 +722,8 @@ fraction_str(1, _Time) -> ""; fraction_str(Factor, Time) -> Fraction = Time rem Factor, - io_lib:fwrite(".~*..0B", [log10(Factor), abs(Fraction)]). + S = integer_to_list(abs(Fraction)), + [$. | pad(log10(Factor) - length(S), S)]. fraction(second, _) -> 0; @@ -735,5 +744,21 @@ log10(1000) -> 3; log10(1000000) -> 6; log10(1000000000) -> 9. -flat_fwrite(F, S) -> - lists:flatten(io_lib:fwrite(F, S)). +pad(0, S) -> + S; +pad(I, S) -> + [$0 | pad(I - 1, S)]. + +pad2(N) when N < 10 -> + [$0 | integer_to_list(N)]; +pad2(N) -> + integer_to_list(N). + +pad4(N) when N < 10 -> + [$0, $0, $0 | integer_to_list(N)]; +pad4(N) when N < 100 -> + [$0, $0 | integer_to_list(N)]; +pad4(N) when N < 1000 -> + [$0 | integer_to_list(N)]; +pad4(N) -> + integer_to_list(N). diff --git a/lib/stdlib/test/binary_module_SUITE.erl b/lib/stdlib/test/binary_module_SUITE.erl index e0811f19cf..9b2033ec4a 100644 --- a/lib/stdlib/test/binary_module_SUITE.erl +++ b/lib/stdlib/test/binary_module_SUITE.erl @@ -755,8 +755,9 @@ list_to_bin(Config) when is_list(Config) -> copy(Config) when is_list(Config) -> <<1,2,3>> = binary:copy(<<1,2,3>>), RS = random_string({1,10000}), - RS = RS2 = binary:copy(RS), - false = erts_debug:same(RS,RS2), + RS2 = binary:copy(RS), + true = RS =:= RS2, + false = erts_debug:same(RS, RS2), <<>> = ?MASK_ERROR(binary:copy(<<1,2,3>>,0)), badarg = ?MASK_ERROR(binary:copy(<<1,2,3:3>>,2)), badarg = ?MASK_ERROR(binary:copy([],0)), diff --git a/lib/stdlib/test/calendar_SUITE.erl b/lib/stdlib/test/calendar_SUITE.erl index c6d9dbca4a..224c0d5625 100644 --- a/lib/stdlib/test/calendar_SUITE.erl +++ b/lib/stdlib/test/calendar_SUITE.erl @@ -1,7 +1,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 1997-2018. All Rights Reserved. +%% Copyright Ericsson AB 1997-2019. 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. diff --git a/lib/stdlib/test/qlc_SUITE.erl b/lib/stdlib/test/qlc_SUITE.erl index d7354438f9..2354a08f78 100644 --- a/lib/stdlib/test/qlc_SUITE.erl +++ b/lib/stdlib/test/qlc_SUITE.erl @@ -3163,19 +3163,13 @@ lookup2(Config) when is_list(Config) -> [a] = lookup_keys(Q) end, [{a},{b},{c}])">>, - {cres, - <<"etsc(fun(E) -> + <<"etsc(fun(E) -> Q = qlc:q([X || {X}=Y <- ets:table(E), element(2, Y) == b, X =:= 1]), [] = qlc:e(Q), false = lookup_keys(Q) - end, [{1,b},{2,3}])">>, - %% {warnings,[{2,sys_core_fold,nomatch_guard}, - %% {3,qlc,nomatch_filter}, - %% {3,sys_core_fold,{eval_failure,badarg}}]}}, - {warnings,[{2,sys_core_fold,nomatch_guard}, - {3,sys_core_fold,{eval_failure,badarg}}]}}, + end, [{1,b},{2,3}])">>, <<"etsc(fun(E) -> Q = qlc:q([X || {X} <- ets:table(E), element(1,{X}) =:= 1]), |
