aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--erts/emulator/beam/erl_nif.c12
-rw-r--r--erts/emulator/beam/utils.c2
-rw-r--r--erts/emulator/test/ref_SUITE.erl28
-rw-r--r--lib/common_test/doc/src/ct.xml2
-rw-r--r--lib/compiler/src/beam_validator.erl26
-rw-r--r--lib/compiler/src/compile.erl29
-rw-r--r--lib/compiler/src/sys_core_fold.erl94
-rw-r--r--lib/compiler/test/compile_SUITE.erl21
-rw-r--r--lib/compiler/test/core_SUITE.erl5
-rw-r--r--lib/compiler/test/core_SUITE_data/fun_letrec_effect.core25
-rw-r--r--lib/compiler/test/core_fold_SUITE.erl24
-rw-r--r--lib/compiler/test/receive_SUITE.erl49
-rw-r--r--lib/kernel/doc/src/kernel_app.xml2
-rw-r--r--lib/ssl/doc/src/ssl.xml10
-rw-r--r--lib/ssl/src/ssl_cipher.erl4
-rw-r--r--lib/ssl/src/ssl_connection.erl2
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl30
-rw-r--r--lib/ssl/test/ssl_test_lib.erl5
-rw-r--r--system/doc/reference_manual/expressions.xml4
19 files changed, 322 insertions, 52 deletions
diff --git a/erts/emulator/beam/erl_nif.c b/erts/emulator/beam/erl_nif.c
index 26498eda7b..ee6e6085b6 100644
--- a/erts/emulator/beam/erl_nif.c
+++ b/erts/emulator/beam/erl_nif.c
@@ -453,8 +453,18 @@ static void full_flush_env(ErlNifEnv* env)
static void full_cache_env(ErlNifEnv* env)
{
- if (env->proc->static_flags & ERTS_STC_FLG_SHADOW_PROC)
+ if (env->proc->static_flags & ERTS_STC_FLG_SHADOW_PROC) {
erts_cache_dirty_shadow_proc(env->proc);
+ /*
+ * If shadow proc had heap fragments when flushed
+ * those have now been moved to the real proc.
+ * Ensure heap pointers do not point into a heap
+ * fragment on real proc...
+ */
+ ASSERT(!env->proc->mbuf);
+ env->hp_end = HEAP_LIMIT(env->proc);
+ env->hp = HEAP_TOP(env->proc);
+ }
cache_env(env);
}
diff --git a/erts/emulator/beam/utils.c b/erts/emulator/beam/utils.c
index 19b1312ee3..08f8ca9788 100644
--- a/erts/emulator/beam/utils.c
+++ b/erts/emulator/beam/utils.c
@@ -3122,7 +3122,7 @@ tailrecur_ne:
ASSERT(alen == blen);
for (i = (Sint) alen - 1; i >= 0; i--)
if (anum[i] != bnum[i])
- RETURN_NEQ((Sint32) (anum[i] - bnum[i]));
+ RETURN_NEQ(anum[i] < bnum[i] ? -1 : 1);
goto pop_next;
case (_TAG_HEADER_EXTERNAL_REF >> _TAG_PRIMARY_SIZE):
if (is_internal_ref(b)) {
diff --git a/erts/emulator/test/ref_SUITE.erl b/erts/emulator/test/ref_SUITE.erl
index 5f519d522e..74df857c65 100644
--- a/erts/emulator/test/ref_SUITE.erl
+++ b/erts/emulator/test/ref_SUITE.erl
@@ -22,6 +22,7 @@
-export([all/0, suite/0]).
-export([wrap_1/1]).
+-export([compare_list/1, compare_ets/1]).
-export([loop_ref/1]).
@@ -32,7 +33,7 @@ suite() ->
{timetrap, {minutes, 2}}].
all() ->
- [wrap_1].
+ [wrap_1, compare_list, compare_ets].
%% Check that refs don't wrap around easily.
wrap_1(Config) when is_list(Config) ->
@@ -53,3 +54,28 @@ loop_ref(Parent) ->
loop_ref(R, R, _) -> ok;
loop_ref(R0, _, N) ->
loop_ref(R0, make_ref(), N+1).
+
+%% Check that ref ordering works
+compare_list(Config) when is_list(Config) ->
+ %% Although this test uses external refs, it would apply the same to plain refs
+ ExtRef1 = <<131,114,0,3,100,0,3,110,64,98,3, 0,0,173,156, 0,216,0,4, 0,0,0,0>>,
+ ExtRef2 = <<131,114,0,3,100,0,3,110,64,98,3, 0,1,31,27, 129,4,0,1, 0,0,0,0>>,
+
+ Ref1 = binary_to_term(ExtRef1), %% #Ref<[email protected]>
+ Ref2 = binary_to_term(ExtRef2), %% #Ref<[email protected]>
+ OrderedList = [Ref1, Ref2],
+ OrderedList = lists:sort(OrderedList),
+ ok.
+
+%% This is the scarier case since it makes terms "invisible" in ets or Mnesia
+%% (the underlying fault cause is the same as compare_list/1)
+compare_ets(Config) when is_list(Config) ->
+ W2s = [610350147,899574699,2994196869,686384822,2397690439, 923302211],
+ ExtRefBase = <<131,114,0,3,100,0,3,110,64,98,3>>,
+ ExtRefs = [<<ExtRefBase/binary, 1:32, W2:32, 0:32>> || W2 <- W2s],
+ Refs = [binary_to_term(Bin) || Bin <- ExtRefs],
+
+ Ets = ets:new(refbug, [ordered_set]),
+ ets:insert(Ets, [{Ref,Ref} || Ref <- Refs]),
+ 0 = length([R || R <- ets:tab2list(Ets), ets:lookup(Ets, element(1,R)) == []]),
+ ok.
diff --git a/lib/common_test/doc/src/ct.xml b/lib/common_test/doc/src/ct.xml
index b51dee266b..c0380c4142 100644
--- a/lib/common_test/doc/src/ct.xml
+++ b/lib/common_test/doc/src/ct.xml
@@ -1522,7 +1522,7 @@
<v>Hours = integer()</v>
<v>Mins = integer()</v>
<v>Secs = integer()</v>
- <v>Millisecs = integer() | float()</v>
+ <v>Millisecs = integer()</v>
<v>Func = {M, F, A} | function()</v>
<v>M = atom()</v>
<v>F = atom()</v>
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index a1b71251b7..a0eef826ce 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -451,6 +451,19 @@ valfun_1({try_case,Reg}, #vst{current=#st{ct=[Fail|Fails]}}=Vst0) ->
Type ->
error({bad_type,Type})
end;
+valfun_1({get_list,Src,D1,D2}, Vst0) ->
+ assert_type(cons, Src, Vst0),
+ Vst = set_type_reg(term, Src, D1, Vst0),
+ set_type_reg(term, Src, D2, Vst);
+valfun_1({get_hd,Src,Dst}, Vst) ->
+ assert_type(cons, Src, Vst),
+ set_type_reg(term, Src, Dst, Vst);
+valfun_1({get_tl,Src,Dst}, Vst) ->
+ assert_type(cons, Src, Vst),
+ set_type_reg(term, Src, Dst, Vst);
+valfun_1({get_tuple_element,Src,I,Dst}, Vst) ->
+ assert_type({tuple_element,I+1}, Src, Vst),
+ set_type_reg(term, Src, Dst, Vst);
valfun_1(I, Vst) ->
valfun_2(I, Vst).
@@ -609,19 +622,6 @@ valfun_4({select_val,Src,{f,Fail},{list,Choices}}, Vst) ->
valfun_4({select_tuple_arity,Tuple,{f,Fail},{list,Choices}}, Vst) ->
assert_type(tuple, Tuple, Vst),
kill_state(branch_arities(Choices, Tuple, branch_state(Fail, Vst)));
-valfun_4({get_list,Src,D1,D2}, Vst0) ->
- assert_type(cons, Src, Vst0),
- Vst = set_type_reg(term, Src, D1, Vst0),
- set_type_reg(term, Src, D2, Vst);
-valfun_4({get_hd,Src,Dst}, Vst) ->
- assert_type(cons, Src, Vst),
- set_type_reg(term, Src, Dst, Vst);
-valfun_4({get_tl,Src,Dst}, Vst) ->
- assert_type(cons, Src, Vst),
- set_type_reg(term, Src, Dst, Vst);
-valfun_4({get_tuple_element,Src,I,Dst}, Vst) ->
- assert_type({tuple_element,I+1}, Src, Vst),
- set_type_reg(term, Src, Dst, Vst);
%% New bit syntax matching instructions.
valfun_4({test,bs_start_match2,{f,Fail},Live,[Ctx,NeedSlots],Ctx}, Vst0) ->
diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl
index 5ef9611504..e1c1f7338e 100644
--- a/lib/compiler/src/compile.erl
+++ b/lib/compiler/src/compile.erl
@@ -1432,16 +1432,30 @@ encrypt_debug_info(DebugInfo, Key, Opts) ->
end.
cleanup_compile_options(Opts) ->
- lists:filter(fun keep_compile_option/1, Opts).
-
+ IsDeterministic = lists:member(deterministic, Opts),
+ lists:filter(fun(Opt) ->
+ keep_compile_option(Opt, IsDeterministic)
+ end, Opts).
+
+%% Include paths and current directory don't affect compilation, but they might
+%% be helpful so we include them unless we're doing a deterministic build.
+keep_compile_option({i, _}, Deterministic) ->
+ not Deterministic;
+keep_compile_option({cwd, _}, Deterministic) ->
+ not Deterministic;
%% We are storing abstract, not asm or core.
-keep_compile_option(from_asm) -> false;
-keep_compile_option(from_core) -> false;
+keep_compile_option(from_asm, _Deterministic) ->
+ false;
+keep_compile_option(from_core, _Deterministic) ->
+ false;
%% Parse transform and macros have already been applied.
-keep_compile_option({parse_transform, _}) -> false;
-keep_compile_option({d, _, _}) -> false;
+keep_compile_option({parse_transform, _}, _Deterministic) ->
+ false;
+keep_compile_option({d, _, _}, _Deterministic) ->
+ false;
%% Do not affect compilation result on future calls.
-keep_compile_option(Option) -> effects_code_generation(Option).
+keep_compile_option(Option, _Deterministic) ->
+ effects_code_generation(Option).
start_crypto() ->
try crypto:start() of
@@ -1589,6 +1603,7 @@ effects_code_generation(Option) ->
binary -> false;
verbose -> false;
{cwd,_} -> false;
+ {i,_} -> false;
{outdir, _} -> false;
_ -> true
end.
diff --git a/lib/compiler/src/sys_core_fold.erl b/lib/compiler/src/sys_core_fold.erl
index ceb7d56221..3a65b40fa5 100644
--- a/lib/compiler/src/sys_core_fold.erl
+++ b/lib/compiler/src/sys_core_fold.erl
@@ -115,13 +115,6 @@ module(#c_module{defs=Ds0}=Mod, Opts) ->
{ok,Mod#c_module{defs=Ds1},get_warnings()}.
function_1({#c_var{name={F,Arity}}=Name,B0}) ->
- %% Find a suitable starting value for the variable counter. Note
- %% that this pass assumes that new_var_name/1 returns a variable
- %% name distinct from any variable used in the entire body of
- %% the function. We use integers as variable names to avoid
- %% filling up the atom table when compiling huge functions.
- Count = cerl_trees:next_free_variable_name(B0),
- put(new_var_num, Count),
try
%% Find a suitable starting value for the variable
%% counter. Note that this pass assumes that new_var_name/1
@@ -352,7 +345,12 @@ expr(#c_letrec{body=#c_var{}}=Letrec, effect, _Sub) ->
void();
expr(#c_letrec{defs=Fs0,body=B0}=Letrec, Ctxt, Sub) ->
Fs1 = map(fun ({Name,Fb}) ->
- {Name,expr(Fb, {letrec,Ctxt}, Sub)}
+ case Ctxt =:= effect andalso is_fun_effect_safe(Name, B0) of
+ true ->
+ {Name,expr(Fb, {letrec, effect}, Sub)};
+ false ->
+ {Name,expr(Fb, {letrec, value}, Sub)}
+ end
end, Fs0),
B1 = body(B0, Ctxt, Sub),
Letrec#c_letrec{defs=Fs1,body=B1};
@@ -483,6 +481,86 @@ expr(#c_try{anno=A,arg=E0,vars=Vs0,body=B0,evars=Evs0,handler=H0}=Try, _, Sub0)
Try#c_try{arg=E1,vars=Vs1,body=B1,evars=Evs1,handler=H1}
end.
+
+%% If a fun or its application is used as an argument, then it's unsafe to
+%% handle it in effect context as the side-effects may rely on its return
+%% value. The following is a minimal example of where it can go wrong:
+%%
+%% do letrec 'f'/0 = fun () -> ... whatever ...
+%% in call 'side':'effect'(apply 'f'/0())
+%% 'ok'
+%%
+%% This function returns 'true' if Body definitely does not rely on a
+%% value produced by FVar, or 'false' if Body depends on or might depend on
+%% a value produced by FVar.
+
+is_fun_effect_safe(#c_var{}=FVar, Body) ->
+ ifes_1(FVar, Body, true).
+
+ifes_1(FVar, #c_alias{pat=Pat}, _Safe) ->
+ ifes_1(FVar, Pat, false);
+ifes_1(FVar, #c_apply{op=Op,args=Args}, Safe) ->
+ %% FVar(...) is safe as long its return value is ignored, but it's never
+ %% okay to pass FVar as an argument.
+ ifes_list(FVar, Args, false) andalso ifes_1(FVar, Op, Safe);
+ifes_1(FVar, #c_binary{segments=Segments}, _Safe) ->
+ ifes_list(FVar, Segments, false);
+ifes_1(FVar, #c_bitstr{val=Val,size=Size,unit=Unit}, _Safe) ->
+ ifes_list(FVar, [Val, Size, Unit], false);
+ifes_1(FVar, #c_call{args=Args}, _Safe) ->
+ ifes_list(FVar, Args, false);
+ifes_1(FVar, #c_case{arg=Arg,clauses=Clauses}, Safe) ->
+ ifes_1(FVar, Arg, false) andalso ifes_list(FVar, Clauses, Safe);
+ifes_1(FVar, #c_catch{body=Body}, _Safe) ->
+ ifes_1(FVar, Body, false);
+ifes_1(FVar, #c_clause{pats=Pats,guard=Guard,body=Body}, Safe) ->
+ ifes_list(FVar, Pats, false) andalso
+ ifes_1(FVar, Guard, false) andalso
+ ifes_1(FVar, Body, Safe);
+ifes_1(FVar, #c_cons{hd=Hd,tl=Tl}, _Safe) ->
+ ifes_1(FVar, Hd, false) andalso ifes_1(FVar, Tl, false);
+ifes_1(FVar, #c_fun{body=Body}, _Safe) ->
+ ifes_1(FVar, Body, false);
+ifes_1(FVar, #c_let{arg=Arg,body=Body}, Safe) ->
+ ifes_1(FVar, Arg, false) andalso ifes_1(FVar, Body, Safe);
+ifes_1(FVar, #c_letrec{defs=Defs,body=Body}, Safe) ->
+ Funs = [Fun || {_,Fun} <- Defs],
+ ifes_list(FVar, Funs, false) andalso ifes_1(FVar, Body, Safe);
+ifes_1(_FVar, #c_literal{}, _Safe) ->
+ true;
+ifes_1(FVar, #c_map{arg=Arg,es=Elements}, _Safe) ->
+ ifes_1(FVar, Arg, false) andalso ifes_list(FVar, Elements, false);
+ifes_1(FVar, #c_map_pair{key=Key,val=Val}, _Safe) ->
+ ifes_1(FVar, Key, false) andalso ifes_1(FVar, Val, false);
+ifes_1(FVar, #c_primop{args=Args}, _Safe) ->
+ ifes_list(FVar, Args, false);
+ifes_1(FVar, #c_receive{timeout=Timeout,action=Action,clauses=Clauses}, Safe) ->
+ ifes_1(FVar, Timeout, false) andalso
+ ifes_1(FVar, Action, Safe) andalso
+ ifes_list(FVar, Clauses, Safe);
+ifes_1(FVar, #c_seq{arg=Arg,body=Body}, Safe) ->
+ %% Arg of a #c_seq{} has no effect so it's okay to use FVar there even if
+ %% Safe=false.
+ ifes_1(FVar, Arg, true) andalso ifes_1(FVar, Body, Safe);
+ifes_1(FVar, #c_try{arg=Arg,handler=Handler,body=Body}, Safe) ->
+ ifes_1(FVar, Arg, false) andalso
+ ifes_1(FVar, Handler, Safe) andalso
+ ifes_1(FVar, Body, Safe);
+ifes_1(FVar, #c_tuple{es=Elements}, _Safe) ->
+ ifes_list(FVar, Elements, false);
+ifes_1(FVar, #c_values{es=Elements}, _Safe) ->
+ ifes_list(FVar, Elements, false);
+ifes_1(#c_var{name=Name}, #c_var{name=Name}, Safe) ->
+ %% It's safe to return FVar if it's unused.
+ Safe;
+ifes_1(_FVar, #c_var{}, _Safe) ->
+ true.
+
+ifes_list(FVar, [E|Es], Safe) ->
+ ifes_1(FVar, E, Safe) andalso ifes_list(FVar, Es, Safe);
+ifes_list(_FVar, [], _Safe) ->
+ true.
+
expr_list(Es, Ctxt, Sub) ->
[expr(E, Ctxt, Sub) || E <- Es].
diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl
index 2206927ed1..1ecae06128 100644
--- a/lib/compiler/test/compile_SUITE.erl
+++ b/lib/compiler/test/compile_SUITE.erl
@@ -36,7 +36,7 @@
core_roundtrip/1, asm/1, optimized_guards/1,
sys_pre_attributes/1, dialyzer/1,
warnings/1, pre_load_check/1, env_compiler_options/1,
- bc_options/1
+ bc_options/1, deterministic_include/1
]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -53,7 +53,7 @@ all() ->
cover, env, core_pp, core_roundtrip, asm, optimized_guards,
sys_pre_attributes, dialyzer, warnings, pre_load_check,
env_compiler_options, custom_debug_info, bc_options,
- custom_compile_info].
+ custom_compile_info, deterministic_include].
groups() ->
[].
@@ -1514,6 +1514,23 @@ highest_opcode(DataDir, Mod, Opt) ->
<<16:32,0:32,HighestOpcode:32,_/binary>> = Code,
HighestOpcode.
+deterministic_include(Config) when is_list(Config) ->
+ DataDir = proplists:get_value(data_dir, Config),
+ Simple = filename:join(DataDir, "simple"),
+
+ %% Files without +deterministic should differ if their include paths do,
+ %% as their debug info will be different.
+ {ok,_,NonDetA} = compile:file(Simple, [binary, {i,"gurka"}]),
+ {ok,_,NonDetB} = compile:file(Simple, [binary, {i,"gaffel"}]),
+ true = NonDetA =/= NonDetB,
+
+ %% ... but files with +deterministic shouldn't.
+ {ok,_,DetC} = compile:file(Simple, [binary, deterministic, {i,"gurka"}]),
+ {ok,_,DetD} = compile:file(Simple, [binary, deterministic, {i,"gaffel"}]),
+ true = DetC =:= DetD,
+
+ ok.
+
%%%
%%% Utilities.
%%%
diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl
index 23f420f5f2..e5611e99d1 100644
--- a/lib/compiler/test/core_SUITE.erl
+++ b/lib/compiler/test/core_SUITE.erl
@@ -29,7 +29,7 @@
bs_shadowed_size_var/1,
cover_v3_kernel_1/1,cover_v3_kernel_2/1,cover_v3_kernel_3/1,
cover_v3_kernel_4/1,cover_v3_kernel_5/1,
- non_variable_apply/1,name_capture/1]).
+ non_variable_apply/1,name_capture/1,fun_letrec_effect/1]).
-include_lib("common_test/include/ct.hrl").
@@ -57,7 +57,7 @@ groups() ->
bs_shadowed_size_var,
cover_v3_kernel_1,cover_v3_kernel_2,cover_v3_kernel_3,
cover_v3_kernel_4,cover_v3_kernel_5,
- non_variable_apply,name_capture
+ non_variable_apply,name_capture,fun_letrec_effect
]}].
@@ -94,6 +94,7 @@ end_per_group(_GroupName, Config) ->
?comp(cover_v3_kernel_5).
?comp(non_variable_apply).
?comp(name_capture).
+?comp(fun_letrec_effect).
try_it(Mod, Conf) ->
Src = filename:join(proplists:get_value(data_dir, Conf),
diff --git a/lib/compiler/test/core_SUITE_data/fun_letrec_effect.core b/lib/compiler/test/core_SUITE_data/fun_letrec_effect.core
new file mode 100644
index 0000000000..ab6f5b7940
--- /dev/null
+++ b/lib/compiler/test/core_SUITE_data/fun_letrec_effect.core
@@ -0,0 +1,25 @@
+module 'fun_letrec_effect' ['fun_letrec_effect'/0, 'ok'/0, 'wat'/0]
+attributes []
+
+'fun_letrec_effect'/0 =
+ fun () ->
+ do apply 'wat'/0()
+ receive
+ <'bar'> when 'true' -> 'ok'
+ <_0> when 'true' -> 'failed'
+ after 'infinity' ->
+ 'true'
+
+%% The return value (bar) of the fun was optimized away because the result of
+%% the `letrec ... in` was unused, despite the fun's return value being
+%% relevant for the side-effect of the expression.
+'wat'/0 =
+ fun () ->
+ let <Self> = call 'erlang':'self'() in
+ do letrec 'f'/0 = fun () ->
+ do call 'maps':'put'('foo', 'bar', ~{}~)
+ 'bar'
+ in call 'erlang':'send'(Self, apply 'f'/0())
+ 'undefined'
+
+end
diff --git a/lib/compiler/test/core_fold_SUITE.erl b/lib/compiler/test/core_fold_SUITE.erl
index 2a2369fff9..47606014c3 100644
--- a/lib/compiler/test/core_fold_SUITE.erl
+++ b/lib/compiler/test/core_fold_SUITE.erl
@@ -28,7 +28,7 @@
mixed_matching_clauses/1,unnecessary_building/1,
no_no_file/1,configuration/1,supplies/1,
redundant_stack_frame/1,export_from_case/1,
- empty_values/1]).
+ empty_values/1,cover_letrec_effect/1]).
-export([foo/0,foo/1,foo/2,foo/3]).
@@ -48,7 +48,7 @@ groups() ->
mixed_matching_clauses,unnecessary_building,
no_no_file,configuration,supplies,
redundant_stack_frame,export_from_case,
- empty_values]}].
+ empty_values,cover_letrec_effect]}].
init_per_suite(Config) ->
@@ -598,5 +598,25 @@ empty_values(_Config) ->
do_empty_values() when (#{})#{} ->
c.
+cover_letrec_effect(_Config) ->
+ self() ! {tag,42},
+ _ = try
+ try
+ ignore
+ after
+ receive
+ {tag,Int}=Term ->
+ Res = #{k => {Term,<<Int:16>>}},
+ self() ! Res
+ end
+ end
+ after
+ ok
+ end,
+ receive
+ Any ->
+ #{k := {{tag,42},<<42:16>>}} = Any
+ end,
+ ok.
id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 81ef0d33f2..37e737084a 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -25,7 +25,7 @@
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]).
+ wait/1,recv_in_try/1]).
-include_lib("common_test/include/ct.hrl").
@@ -44,7 +44,8 @@ all() ->
groups() ->
[{p,test_lib:parallel(),
- [recv,coverage,otp_7980,ref_opt,export,wait]}].
+ [recv,coverage,otp_7980,ref_opt,export,wait,
+ recv_in_try]}].
init_per_suite(Config) ->
@@ -305,4 +306,48 @@ wait_1(r, _, _) ->
wait_1(A, B, C) ->
{A,B,C}.
+recv_in_try(_Config) ->
+ self() ! {ok,fh}, {ok,fh} = recv_in_try(infinity, native),
+ self() ! {ok,ignored}, {ok,42} = recv_in_try(infinity, plain),
+ self() ! {error,ignored}, nok = recv_in_try(infinity, plain),
+ timeout = recv_in_try(1, plain),
+ ok.
+
+recv_in_try(Timeout, Format) ->
+ try
+ receive
+ {Status,History} ->
+ %% {test,is_tuple,{f,148},[{x,0}]}.
+ %% {test,test_arity,{f,148},[{x,0},2]}.
+ %% {get_tuple_element,{x,0},0,{y,1}}. %y1 is fragile.
+ %%
+ %% %% Here the fragility of y1 would be be progated to
+ %% %% the 'catch' below. Incorrect, since get_tuple_element
+ %% %% can't fail.
+ %% {get_tuple_element,{x,0},1,{x,2}}.
+ %%
+ %% remove_message. %y1 fragility cleared.
+ FH = case Format of
+ native ->
+ id(History);
+ plain ->
+ id(42)
+ end,
+ case Status of
+ ok ->
+ {ok,FH};
+ error ->
+ nok
+ end
+ after Timeout ->
+ timeout
+ end
+ catch
+ %% The fragility of y1 incorrectly propagated to here.
+ %% beam_validator would complain.
+ throw:{error,Reason} ->
+ {nok,Reason}
+ end.
+
+
id(I) -> I.
diff --git a/lib/kernel/doc/src/kernel_app.xml b/lib/kernel/doc/src/kernel_app.xml
index ccb7d17d25..15dbdb47dc 100644
--- a/lib/kernel/doc/src/kernel_app.xml
+++ b/lib/kernel/doc/src/kernel_app.xml
@@ -192,7 +192,7 @@
<p>To change the primary log level at runtime, use
<seealso marker="logger#set_primary_config/2">
<c>logger:set_primary_config(level, Level)</c></seealso>.</p>
- <p>Defaults to <c>info</c>.</p>
+ <p>Defaults to <c>notice</c>.</p>
</item>
<tag><marker id="logger_sasl_compatible"/>
<c>logger_sasl_compatible = true | false</c></tag>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index 437510b54d..3029977745 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -199,14 +199,14 @@
| sect163r1 | sect163r2 | secp160k1 | secp160r1 | secp160r2</c></p></item>
<tag><c>hello_extensions() =</c></tag>
- <item><p><c>#{renegotiation_info =>
+ <item><p><c>#{renegotiation_info => binary() | undefined,
signature_algs => [{hash(), ecsda| rsa| dsa}] | undefined
alpn => binary() | undefined,
- next_protocol_negotiation,
+ next_protocol_negotiation => binary() | undefined,
srp => string() | undefined,
- ec_point_formats ,
- elliptic_curves = [oid] | undefined
- sni = string()}
+ ec_point_formats => list() | undefined,
+ elliptic_curves => [oid] | undefined,
+ sni => string() | undefined}
}</c></p></item>
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 50dadd0903..1aeb415bd9 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -2777,6 +2777,8 @@ ecdsa_signed_suites(Ciphers, Version) ->
rsa_keyed(dhe_rsa) ->
true;
+rsa_keyed(ecdhe_rsa) ->
+ true;
rsa_keyed(rsa) ->
true;
rsa_keyed(rsa_psk) ->
@@ -2840,6 +2842,8 @@ ec_keyed(ecdh_ecdsa) ->
true;
ec_keyed(ecdh_rsa) ->
true;
+ec_keyed(ecdhe_ecdsa) ->
+ true;
ec_keyed(_) ->
false.
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index 064350e6de..4b3a6cde01 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -634,6 +634,8 @@ init(_Type, _Event, _State, _Connection) ->
tls_connection | dtls_connection) ->
gen_statem:state_function_result().
%%--------------------------------------------------------------------
+error({call, From}, {close, _}, State, _Connection) ->
+ stop_and_reply(normal, {reply, From, ok}, State);
error({call, From}, _Msg, State, _Connection) ->
{next_state, ?FUNCTION_NAME, State, [{reply, From, {error, closed}}]}.
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 0381d0d87d..013786d863 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -243,7 +243,8 @@ error_handling_tests()->
recv_active,
recv_active_once,
recv_error_handling,
- call_in_error_state
+ call_in_error_state,
+ close_in_error_state
].
error_handling_tests_tls()->
@@ -4036,6 +4037,33 @@ run_error_server([ Pid | Opts]) ->
%%--------------------------------------------------------------------
+close_in_error_state() ->
+ [{doc,"Special case of closing socket in error state"}].
+close_in_error_state(Config) when is_list(Config) ->
+ ServerOpts0 = ssl_test_lib:ssl_options(server_opts, Config),
+ ServerOpts = [{cacertfile, "foo.pem"} | proplists:delete(cacertfile, ServerOpts0)],
+ ClientOpts = ssl_test_lib:ssl_options(client_opts, Config),
+ _ = spawn_link(?MODULE, run_error_server_close, [[self() | ServerOpts]]),
+ receive
+ {_Pid, Port} ->
+ spawn_link(?MODULE, run_client_error, [[Port, ClientOpts]])
+ end,
+ receive
+ ok ->
+ ok;
+ Other ->
+ ct:fail(Other)
+ end.
+
+run_error_server_close([Pid | Opts]) ->
+ {ok, Listen} = ssl:listen(0, Opts),
+ {ok,{_, Port}} = ssl:sockname(Listen),
+ Pid ! {self(), Port},
+ {ok, Socket} = ssl:transport_accept(Listen),
+ Pid ! ssl:close(Socket).
+
+%%--------------------------------------------------------------------
+
rizzo() ->
[{doc, "Test that there is a 1/n-1-split for non RC4 in 'TLS < 1.1' as it is
vunrable to Rizzo/Dungon attack"}].
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 91a9c774a6..7202e3662c 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -1524,11 +1524,6 @@ v_1_2_check(ecdh_ecdsa, ecdh_rsa) ->
true;
v_1_2_check(ecdh_rsa, ecdh_ecdsa) ->
true;
-v_1_2_check(ecdhe_ecdsa, ecdhe_rsa) ->
- true;
-v_1_2_check(ecdhe_rsa, ecdhe_ecdsa) ->
- true;
-
v_1_2_check(_, _) ->
false.
diff --git a/system/doc/reference_manual/expressions.xml b/system/doc/reference_manual/expressions.xml
index 94e40dd077..f6daf50c8b 100644
--- a/system/doc/reference_manual/expressions.xml
+++ b/system/doc/reference_manual/expressions.xml
@@ -567,6 +567,10 @@ Expr1 <input>op</input> Expr2</pre>
order is defined:</p>
<pre>
number &lt; atom &lt; reference &lt; fun &lt; port &lt; pid &lt; tuple &lt; map &lt; nil &lt; list &lt; bit string</pre>
+ <p><c>nil</c> in the previous expression represents the empty list
+ (<c>[]</c>), which is regarded as a separate type from
+ <c>list/0</c>. That is why <c>nil &lt; list</c>.
+ </p>
<p>Lists are compared element by element. Tuples are ordered by
size, two tuples with the same size are compared element by
element.</p>