aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--OTP_VERSION2
-rw-r--r--bootstrap/bin/no_dot_erlang.bootbin6546 -> 6544 bytes
-rw-r--r--bootstrap/bin/start.bootbin6546 -> 6544 bytes
-rw-r--r--bootstrap/bin/start_clean.bootbin6546 -> 6544 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_asm.beambin11052 -> 11052 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_except.beambin4196 -> 4204 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_codegen.beambin37572 -> 37688 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_dead.beambin12072 -> 12004 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beambin45600 -> 45604 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_type.beambin28400 -> 28636 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_validator.beambin50212 -> 50216 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/hipe_unified_loader.beambin12340 -> 12336 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/inet_db.beambin25360 -> 25360 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_lint.beambin86228 -> 86340 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib.beambin13532 -> 13636 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_format.beambin13892 -> 14868 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/io_lib_pretty.beambin21280 -> 21116 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/queue.beambin5900 -> 5908 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/string.beambin35788 -> 35872 bytes
-rw-r--r--erts/doc/src/notes.xml126
-rw-r--r--erts/emulator/beam/atom.c77
-rw-r--r--erts/emulator/beam/atom.h8
-rw-r--r--erts/emulator/beam/beam_emu.c20
-rw-r--r--erts/emulator/beam/beam_load.c10
-rw-r--r--erts/emulator/beam/break.c5
-rw-r--r--erts/emulator/beam/dist.c11
-rw-r--r--erts/emulator/beam/erl_bif_info.c2
-rw-r--r--erts/emulator/beam/erl_bif_lists.c15
-rw-r--r--erts/emulator/beam/erl_proc_sig_queue.c1
-rw-r--r--erts/emulator/beam/erl_process.h2
-rw-r--r--erts/emulator/beam/instrs.tab17
-rw-r--r--erts/emulator/beam/ops.tab19
-rw-r--r--erts/emulator/nifs/common/socket_nif.c100
-rw-r--r--erts/emulator/test/bif_SUITE.erl10
-rw-r--r--erts/emulator/test/exception_SUITE.erl34
-rw-r--r--erts/emulator/test/process_SUITE.erl84
-rw-r--r--erts/emulator/test/socket_SUITE.erl481
-rw-r--r--erts/emulator/test/system_info_SUITE.erl53
-rw-r--r--erts/vsn.mk2
-rw-r--r--lib/compiler/doc/src/notes.xml77
-rw-r--r--lib/compiler/src/beam_except.erl7
-rw-r--r--lib/compiler/src/beam_ssa_codegen.erl15
-rw-r--r--lib/compiler/src/beam_ssa_dead.erl30
-rw-r--r--lib/compiler/src/beam_ssa_pre_codegen.erl73
-rw-r--r--lib/compiler/src/beam_ssa_type.erl89
-rw-r--r--lib/compiler/src/beam_validator.erl12
-rw-r--r--lib/compiler/test/beam_except_SUITE.erl20
-rw-r--r--lib/compiler/test/beam_ssa_SUITE.erl86
-rw-r--r--lib/compiler/test/beam_type_SUITE.erl35
-rw-r--r--lib/compiler/test/core_SUITE.erl7
-rw-r--r--lib/compiler/test/core_SUITE_data/get_map_element.core18
-rw-r--r--lib/compiler/test/guard_SUITE.erl25
-rw-r--r--lib/compiler/test/match_SUITE.erl26
-rw-r--r--lib/compiler/test/receive_SUITE.erl26
-rw-r--r--lib/compiler/vsn.mk2
-rw-r--r--lib/crypto/c_src/cipher.c1
-rw-r--r--lib/crypto/doc/src/notes.xml17
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/dialyzer/doc/src/notes.xml32
-rw-r--r--lib/dialyzer/src/dialyzer.erl54
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/union_paren30
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/union_paren.erl74
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/inets/doc/src/notes.xml18
-rw-r--r--lib/inets/src/http_client/httpc.erl8
-rw-r--r--lib/inets/test/httpc_SUITE.erl13
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/kernel/doc/src/notes.xml18
-rw-r--r--lib/kernel/src/kernel.appup.src8
-rw-r--r--lib/kernel/src/user.erl3
-rw-r--r--lib/kernel/src/user_drv.erl37
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml108
-rw-r--r--lib/ssl/src/dtls_connection.erl8
-rw-r--r--lib/ssl/src/dtls_packet_demux.erl6
-rw-r--r--lib/ssl/src/ssl_alert.erl31
-rw-r--r--lib/ssl/src/ssl_cipher.erl10
-rw-r--r--lib/ssl/src/ssl_connection.erl74
-rw-r--r--lib/ssl/src/ssl_handshake.erl22
-rw-r--r--lib/ssl/src/tls_connection.erl108
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl35
-rw-r--r--lib/ssl/src/tls_handshake_1_3.erl4
-rw-r--r--lib/ssl/src/tls_v1.erl22
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl154
-rw-r--r--lib/ssl/test/ssl_certificate_verify_SUITE.erl41
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl157
-rw-r--r--lib/ssl/test/ssl_test_lib.erl44
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/notes.xml34
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl6
-rw-r--r--lib/stdlib/src/stdlib.appup.src10
-rw-r--r--lib/stdlib/test/io_SUITE.erl14
-rw-r--r--lib/stdlib/test/lists_SUITE.erl11
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--make/otp_version_tickets157
-rw-r--r--otp_versions.table5
96 files changed, 2434 insertions, 579 deletions
diff --git a/OTP_VERSION b/OTP_VERSION
index b090fe57f6..8869803b66 100644
--- a/OTP_VERSION
+++ b/OTP_VERSION
@@ -1 +1 @@
-22.0
+22.0.5
diff --git a/bootstrap/bin/no_dot_erlang.boot b/bootstrap/bin/no_dot_erlang.boot
index 9a2412e1f0..1f7f088a77 100644
--- a/bootstrap/bin/no_dot_erlang.boot
+++ b/bootstrap/bin/no_dot_erlang.boot
Binary files differ
diff --git a/bootstrap/bin/start.boot b/bootstrap/bin/start.boot
index 9a2412e1f0..1f7f088a77 100644
--- a/bootstrap/bin/start.boot
+++ b/bootstrap/bin/start.boot
Binary files differ
diff --git a/bootstrap/bin/start_clean.boot b/bootstrap/bin/start_clean.boot
index 9a2412e1f0..1f7f088a77 100644
--- a/bootstrap/bin/start_clean.boot
+++ b/bootstrap/bin/start_clean.boot
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_asm.beam b/bootstrap/lib/compiler/ebin/beam_asm.beam
index f6e7f0d68e..07acbb1da7 100644
--- a/bootstrap/lib/compiler/ebin/beam_asm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_asm.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_except.beam b/bootstrap/lib/compiler/ebin/beam_except.beam
index 5daead0cab..1894483f71 100644
--- a/bootstrap/lib/compiler/ebin/beam_except.beam
+++ b/bootstrap/lib/compiler/ebin/beam_except.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
index afba2693b4..e59340de4f 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
index f3dbfc42ff..f6bd1b7a69 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_dead.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
index f7c9c34c9a..3de363dbb6 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_pre_codegen.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
index 029de3d4a5..c13b25bac3 100644
--- a/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
+++ b/bootstrap/lib/compiler/ebin/beam_ssa_type.beam
Binary files differ
diff --git a/bootstrap/lib/compiler/ebin/beam_validator.beam b/bootstrap/lib/compiler/ebin/beam_validator.beam
index 79d64b800b..9d0c34a94a 100644
--- a/bootstrap/lib/compiler/ebin/beam_validator.beam
+++ b/bootstrap/lib/compiler/ebin/beam_validator.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
index 023d0bda6d..7ff507242b 100644
--- a/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
+++ b/bootstrap/lib/kernel/ebin/hipe_unified_loader.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/inet_db.beam b/bootstrap/lib/kernel/ebin/inet_db.beam
index e53e183312..20d795e75f 100644
--- a/bootstrap/lib/kernel/ebin/inet_db.beam
+++ b/bootstrap/lib/kernel/ebin/inet_db.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_lint.beam b/bootstrap/lib/stdlib/ebin/erl_lint.beam
index c5ea3ee70f..62838ed771 100644
--- a/bootstrap/lib/stdlib/ebin/erl_lint.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_lint.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib.beam b/bootstrap/lib/stdlib/ebin/io_lib.beam
index c52efe1b44..45692978e4 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_format.beam b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
index 5556dc733b..66047b5070 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_format.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_format.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
index ba644430da..57c700cb31 100644
--- a/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
+++ b/bootstrap/lib/stdlib/ebin/io_lib_pretty.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/queue.beam b/bootstrap/lib/stdlib/ebin/queue.beam
index 24b8582188..94728e30cc 100644
--- a/bootstrap/lib/stdlib/ebin/queue.beam
+++ b/bootstrap/lib/stdlib/ebin/queue.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/string.beam b/bootstrap/lib/stdlib/ebin/string.beam
index e6d68ea84e..5d1d66892b 100644
--- a/bootstrap/lib/stdlib/ebin/string.beam
+++ b/bootstrap/lib/stdlib/ebin/string.beam
Binary files differ
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index aad7e27f80..13208fb7f6 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -31,6 +31,132 @@
</header>
<p>This document describes the changes made to the ERTS application.</p>
+<section><title>Erts 10.4.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ An invalid value test caused the socket:setopt(Socket,
+ ip, add_membership, ip_mreq()) to fail with badarg. The
+ same for drop_membership.</p>
+ <p>
+ Own Id: OTP-15908 Aux Id: ERL-980 </p>
+ </item>
+ <item>
+ <p>
+ Fixed bug causing VM crash when doing textual dump of a
+ process containing an unhandled monitor down signal.
+ Textual process dumps can be done with
+ <c>erlang:system_info(procs)</c>, trace feature
+ <c>process_dump</c>, Erlang shell break menu and a
+ crashdump. Bug exist since OTP 21.0.</p>
+ <p>
+ Own Id: OTP-15909 Aux Id: ERL-979 </p>
+ </item>
+ <item>
+ <p><c>lists:subtract/2</c> would produce incorrect
+ results for some inputs on 64-bit platforms.</p>
+ <p>
+ Own Id: OTP-15938 Aux Id: ERL-986 </p>
+ </item>
+ <item>
+ <p>Fixed a bug in the loader that was similar to
+ <c>OTP-15938</c>, yielding incorrect code for some inputs
+ on 64-bit platforms.</p>
+ <p>
+ Own Id: OTP-15939</p>
+ </item>
+ <item>
+ <p>
+ Fixed bug causing scheduler threads in rare cases to
+ block spinnning indefinitely. Bug exists since OTP 21.0.</p>
+ <p>
+ Own Id: OTP-15941 Aux Id: PR-2313 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.4.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed a buffer overflow when
+ <c>binary_to_existing_atom/2</c> and
+ <c>list_to_existing_atom/2</c> was used with the
+ <c>latin1</c> encoding.</p>
+ <p>
+ Own Id: OTP-15819 Aux Id: ERL-944 </p>
+ </item>
+ <item>
+ <p>
+ The runtime system disconnected a connection if it
+ received an <c>exit/2</c> signal where the recipient was
+ a process on an old incarnation of the current node. That
+ is, the receiving node had the same node name, but
+ another "creation" number. The signal will now just be
+ dropped since the receiving process no longer exists.</p>
+ <p>
+ Own Id: OTP-15867 Aux Id: ERIERL-373 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed <c>process_info(Pid,reductions)</c> to not
+ categorically increase reduction count of the measured
+ process <c>Pid</c>. Repeated reduction measure of an idle
+ process will most often (but not guaranteed) return the
+ same value, like it behaved before OTP 21.3.8.</p>
+ <p>
+ Own Id: OTP-15865 Aux Id: ERL-964 </p>
+ </item>
+ <item>
+ <p>Fixed an incorrect load-time optimization that could
+ cause a crash when extracting deeply nested tuple
+ elements.</p>
+ <p>
+ Own Id: OTP-15871 Aux Id: ERIERL-374 </p>
+ </item>
+ <item>
+ <p>
+ Fix bug causing VM crash when pressing P for "proc info"
+ in Erlang shell break menu. Bug exists since OTP 22.0.</p>
+ <p>
+ Own Id: OTP-15873 Aux Id: ERL-965 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>In nested use of <c>try</c>/<c>catch</c>, rethrowing
+ an exception using <c>erlang:raise/3</c> with a different
+ class would not always be able to change the class of the
+ exception.</p>
+ <p>
+ Own Id: OTP-15834 Aux Id: ERIERL-367 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/erts/emulator/beam/atom.c b/erts/emulator/beam/atom.c
index 59b51fd15e..5a70509ffd 100644
--- a/erts/emulator/beam/atom.c
+++ b/erts/emulator/beam/atom.c
@@ -200,11 +200,15 @@ atom_free(Atom* obj)
ASSERT(obj->slot.index == atom_val(am_ErtsSecretAtom));
}
-static void latin1_to_utf8(byte* conv_buf, const byte** srcp, int* lenp)
+static void latin1_to_utf8(byte* conv_buf, Uint buf_sz,
+ const byte** srcp, Uint* lenp)
{
byte* dst;
const byte* src = *srcp;
- int i, len = *lenp;
+ Uint i, len = *lenp;
+
+ ASSERT(len <= MAX_ATOM_CHARACTERS);
+ ASSERT(buf_sz >= MAX_ATOM_SZ_FROM_LATIN1);
for (i=0 ; i < len; ++i) {
if (src[i] & 0x80) {
@@ -234,11 +238,11 @@ need_convertion:
* erts_atom_put_index() may fail. Returns negative indexes for errors.
*/
int
-erts_atom_put_index(const byte *name, int len, ErtsAtomEncoding enc, int trunc)
+erts_atom_put_index(const byte *name, Sint len, ErtsAtomEncoding enc, int trunc)
{
byte utf8_copy[MAX_ATOM_SZ_FROM_LATIN1];
const byte *text = name;
- int tlen = len;
+ Uint tlen;
Sint no_latin1_chars;
Atom a;
int aix;
@@ -247,13 +251,16 @@ erts_atom_put_index(const byte *name, int len, ErtsAtomEncoding enc, int trunc)
erts_atomic_inc_nob(&atom_put_ops);
#endif
- if (tlen < 0) {
- if (trunc)
- tlen = 0;
- else
- return ATOM_MAX_CHARS_ERROR;
+ if (len < 0) {
+ if (trunc) {
+ len = 0;
+ } else {
+ return ATOM_MAX_CHARS_ERROR;
+ }
}
+ tlen = len;
+
switch (enc) {
case ERTS_ATOM_ENC_7BIT_ASCII:
if (tlen > MAX_ATOM_CHARACTERS) {
@@ -277,7 +284,7 @@ erts_atom_put_index(const byte *name, int len, ErtsAtomEncoding enc, int trunc)
return ATOM_MAX_CHARS_ERROR;
}
no_latin1_chars = tlen;
- latin1_to_utf8(utf8_copy, &text, &tlen);
+ latin1_to_utf8(utf8_copy, sizeof(utf8_copy), &text, &tlen);
break;
case ERTS_ATOM_ENC_UTF8:
/* First sanity check; need to verify later */
@@ -338,7 +345,7 @@ erts_atom_put_index(const byte *name, int len, ErtsAtomEncoding enc, int trunc)
* erts_atom_put() may fail. If it fails THE_NON_VALUE is returned!
*/
Eterm
-erts_atom_put(const byte *name, int len, ErtsAtomEncoding enc, int trunc)
+erts_atom_put(const byte *name, Sint len, ErtsAtomEncoding enc, int trunc)
{
int aix = erts_atom_put_index(name, len, enc, trunc);
if (aix >= 0)
@@ -348,7 +355,7 @@ erts_atom_put(const byte *name, int len, ErtsAtomEncoding enc, int trunc)
}
Eterm
-am_atom_put(const char* name, int len)
+am_atom_put(const char* name, Sint len)
{
/* Assumes 7-bit ascii; use erts_atom_put() for other encodings... */
return erts_atom_put((byte *) name, len, ERTS_ATOM_ENC_7BIT_ASCII, 1);
@@ -379,23 +386,57 @@ int atom_table_sz(void)
}
int
-erts_atom_get(const char *name, int len, Eterm* ap, ErtsAtomEncoding enc)
+erts_atom_get(const char *name, Uint len, Eterm* ap, ErtsAtomEncoding enc)
{
byte utf8_copy[MAX_ATOM_SZ_FROM_LATIN1];
Atom a;
int i;
int res;
- a.len = (Sint16) len;
- a.name = (byte *)name;
- if (enc == ERTS_ATOM_ENC_LATIN1) {
- latin1_to_utf8(utf8_copy, (const byte**)&a.name, &len);
- a.len = (Sint16) len;
+ switch (enc) {
+ case ERTS_ATOM_ENC_LATIN1:
+ if (len > MAX_ATOM_CHARACTERS) {
+ return 0;
+ }
+
+ latin1_to_utf8(utf8_copy, sizeof(utf8_copy), (const byte**)&name, &len);
+
+ a.name = (byte*)name;
+ a.len = (Sint16)len;
+ break;
+ case ERTS_ATOM_ENC_7BIT_ASCII:
+ if (len > MAX_ATOM_CHARACTERS) {
+ return 0;
+ }
+
+ for (i = 0; i < len; i++) {
+ if (name[i] & 0x80) {
+ return 0;
+ }
+ }
+
+ a.len = (Sint16)len;
+ a.name = (byte*)name;
+ break;
+ case ERTS_ATOM_ENC_UTF8:
+ if (len > MAX_ATOM_SZ_LIMIT) {
+ return 0;
+ }
+
+ /* We don't need to check whether the encoding is legal as all atom
+ * names are stored as UTF-8 and we know a lookup with a badly encoded
+ * name will fail. */
+
+ a.len = (Sint16)len;
+ a.name = (byte*)name;
+ break;
}
+
atom_read_lock();
i = index_get(&erts_atom_table, (void*) &a);
res = i < 0 ? 0 : (*ap = make_atom(i), 1);
atom_read_unlock();
+
return res;
}
diff --git a/erts/emulator/beam/atom.h b/erts/emulator/beam/atom.h
index ca920679c6..f51c5a8c62 100644
--- a/erts/emulator/beam/atom.h
+++ b/erts/emulator/beam/atom.h
@@ -133,14 +133,14 @@ typedef enum {
int atom_table_size(void); /* number of elements */
int atom_table_sz(void); /* table size in bytes, excluding stored objects */
-Eterm am_atom_put(const char*, int); /* ONLY 7-bit ascii! */
-Eterm erts_atom_put(const byte *name, int len, ErtsAtomEncoding enc, int trunc);
-int erts_atom_put_index(const byte *name, int len, ErtsAtomEncoding enc, int trunc);
+Eterm am_atom_put(const char*, Sint); /* ONLY 7-bit ascii! */
+Eterm erts_atom_put(const byte *name, Sint len, ErtsAtomEncoding enc, int trunc);
+int erts_atom_put_index(const byte *name, Sint len, ErtsAtomEncoding enc, int trunc);
void init_atom_table(void);
void atom_info(fmtfn_t, void *);
void dump_atoms(fmtfn_t, void *);
Uint erts_get_atom_limit(void);
-int erts_atom_get(const char* name, int len, Eterm* ap, ErtsAtomEncoding enc);
+int erts_atom_get(const char* name, Uint len, Eterm* ap, ErtsAtomEncoding enc);
void erts_atom_get_text_space_sizes(Uint *reserved, Uint *used);
#endif
diff --git a/erts/emulator/beam/beam_emu.c b/erts/emulator/beam/beam_emu.c
index bae64afb97..07c16e3415 100644
--- a/erts/emulator/beam/beam_emu.c
+++ b/erts/emulator/beam/beam_emu.c
@@ -414,6 +414,7 @@ static Eterm add_stacktrace(Process* c_p, Eterm Value, Eterm exc);
static void save_stacktrace(Process* c_p, BeamInstr* pc, Eterm* reg,
ErtsCodeMFA *bif_mfa, Eterm args);
static struct StackTrace * get_trace_from_exc(Eterm exc);
+static Eterm *get_freason_ptr_from_exc(Eterm exc);
static Eterm make_arglist(Process* c_p, Eterm* reg, int a);
void
@@ -1902,6 +1903,25 @@ static int is_raised_exc(Eterm exc) {
}
}
+static Eterm *get_freason_ptr_from_exc(Eterm exc) {
+ static Eterm dummy_freason;
+ struct StackTrace* s;
+
+ if (exc == NIL) {
+ /*
+ * Is is not exactly clear when exc can be NIL. Probably only
+ * when the exception has been generated from native code.
+ * Return a pointer to an Eterm that can be safely written and
+ * ignored.
+ */
+ return &dummy_freason;
+ } else {
+ ASSERT(is_list(exc));
+ s = (struct StackTrace *) big_val(CDR(list_val(exc)));
+ return &s->freason;
+ }
+}
+
/*
* Creating a list with the argument registers
*/
diff --git a/erts/emulator/beam/beam_load.c b/erts/emulator/beam/beam_load.c
index 941c3ebbbe..35f2ea6688 100644
--- a/erts/emulator/beam/beam_load.c
+++ b/erts/emulator/beam/beam_load.c
@@ -4612,7 +4612,15 @@ typedef struct SortGenOpArg {
static int
genopargtermcompare(SortGenOpArg* a, SortGenOpArg* b)
{
- return CMP_TERM(a->term, b->term);
+ Sint res = CMP_TERM(a->term, b->term);
+
+ if (res < 0) {
+ return -1;
+ } else if (res > 0) {
+ return 1;
+ }
+
+ return 0;
}
static int
diff --git a/erts/emulator/beam/break.c b/erts/emulator/beam/break.c
index 80e871aaf6..6379e4e04d 100644
--- a/erts/emulator/beam/break.c
+++ b/erts/emulator/beam/break.c
@@ -84,8 +84,9 @@ process_info(fmtfn_t to, void *to_arg)
* they are most likely just created and has invalid data
*/
if (p->heap != NULL) {
- ErtsProcLocks locks = (p == esdp->current_process ||
- p == esdp->free_process) ? ERTS_PROC_LOCK_MAIN : 0;
+ ErtsProcLocks locks = ((esdp && (p == esdp->current_process ||
+ p == esdp->free_process))
+ ? ERTS_PROC_LOCK_MAIN : 0);
print_process_info(to, to_arg, p, locks);
}
}
diff --git a/erts/emulator/beam/dist.c b/erts/emulator/beam/dist.c
index ff19ef018e..5e48a553af 100644
--- a/erts/emulator/beam/dist.c
+++ b/erts/emulator/beam/dist.c
@@ -2055,10 +2055,17 @@ int erts_net_message(Port *prt,
token = tuple[4];
}
if (is_not_pid(from)
- || dep != external_pid_dist_entry(from)
- || is_not_internal_pid(to)) {
+ || dep != external_pid_dist_entry(from)) {
goto invalid_message;
}
+ if (is_not_internal_pid(to)) {
+ if (is_external_pid(to)) {
+ DistEntry *dep = external_pid_dist_entry(to);
+ if (dep == erts_this_dist_entry)
+ break; /* Old incarnation of this node... */
+ }
+ goto invalid_message;
+ }
if (!erts_proc_lookup(to)) {
if (ede_hfrag != NULL) {
diff --git a/erts/emulator/beam/erl_bif_info.c b/erts/emulator/beam/erl_bif_info.c
index 2704b99aa4..0339589b79 100644
--- a/erts/emulator/beam/erl_bif_info.c
+++ b/erts/emulator/beam/erl_bif_info.c
@@ -768,7 +768,7 @@ static ErtsProcessInfoArgs pi_args[] = {
{am_memory, 0, ERTS_PI_FLAG_NEED_MSGQ_LEN|ERTS_PI_FLAG_FORCE_SIG_SEND, ERTS_PROC_LOCK_MAIN},
{am_garbage_collection, 3+2 + 3+2 + 3+2 + 3+2 + 3+2 + ERTS_MAX_HEAP_SIZE_MAP_SZ, 0, ERTS_PROC_LOCK_MAIN},
{am_group_leader, 0, 0, ERTS_PROC_LOCK_MAIN},
- {am_reductions, 0, ERTS_PI_FLAG_FORCE_SIG_SEND, ERTS_PROC_LOCK_MAIN},
+ {am_reductions, 0, 0, ERTS_PROC_LOCK_MAIN},
{am_priority, 0, 0, 0},
{am_trace, 0, 0, ERTS_PROC_LOCK_MAIN},
{am_binary, 0, ERTS_PI_FLAG_FORCE_SIG_SEND, ERTS_PROC_LOCK_MAIN},
diff --git a/erts/emulator/beam/erl_bif_lists.c b/erts/emulator/beam/erl_bif_lists.c
index b23fa77f5f..fa2edfef1e 100644
--- a/erts/emulator/beam/erl_bif_lists.c
+++ b/erts/emulator/beam/erl_bif_lists.c
@@ -413,12 +413,25 @@ typedef struct {
#define ERTS_RBT_GET_LEFT(T) ((T)->left)
#define ERTS_RBT_SET_LEFT(T, L) ((T)->left = (L))
#define ERTS_RBT_GET_KEY(T) ((T)->key)
-#define ERTS_RBT_CMP_KEYS(KX, KY) CMP_TERM(KX, KY)
+#define ERTS_RBT_CMP_KEYS(KX, KY) subtract_term_cmp((KX), (KY))
#define ERTS_RBT_WANT_LOOKUP_INSERT
#define ERTS_RBT_WANT_LOOKUP
#define ERTS_RBT_WANT_DELETE
#define ERTS_RBT_UNDEF
+/* erl_rbtree expects comparisons to return an int */
+static int subtract_term_cmp(Eterm a, Eterm b) {
+ Sint res = CMP_TERM(a, b);
+
+ if (res < 0) {
+ return -1;
+ } else if (res > 0) {
+ return 1;
+ }
+
+ return 0;
+}
+
#include "erl_rbtree.h"
static int subtract_continue(Process *p, ErtsSubtractContext *context);
diff --git a/erts/emulator/beam/erl_proc_sig_queue.c b/erts/emulator/beam/erl_proc_sig_queue.c
index f58a606d57..55e469b553 100644
--- a/erts/emulator/beam/erl_proc_sig_queue.c
+++ b/erts/emulator/beam/erl_proc_sig_queue.c
@@ -4051,6 +4051,7 @@ erts_proc_sig_signal_size(ErtsSignal *sig)
case ERTS_MON_TYPE_DIST_PROC:
case ERTS_MON_TYPE_NODE:
size = erts_monitor_size((ErtsMonitor *) sig);
+ break;
default:
ERTS_INTERNAL_ERROR("Unexpected sig type");
break;
diff --git a/erts/emulator/beam/erl_process.h b/erts/emulator/beam/erl_process.h
index 6118c671ee..cdad72443f 100644
--- a/erts/emulator/beam/erl_process.h
+++ b/erts/emulator/beam/erl_process.h
@@ -2352,6 +2352,8 @@ erts_try_change_runq_proc(Process *p, ErtsRunQueue *rq)
old_rqint);
if (act_rqint == old_rqint)
return !0;
+
+ old_rqint = act_rqint;
}
}
diff --git a/erts/emulator/beam/instrs.tab b/erts/emulator/beam/instrs.tab
index 462ee77e6f..7cffe7fb5c 100644
--- a/erts/emulator/beam/instrs.tab
+++ b/erts/emulator/beam/instrs.tab
@@ -1064,19 +1064,30 @@ raw_raise() {
Eterm class = x(0);
Eterm value = x(1);
Eterm stacktrace = x(2);
+ Eterm* freason_ptr;
+
+ /*
+ * Note that the i_raise instruction will override c_p->freason
+ * with the freason field stored inside the StackTrace struct in
+ * ftrace. Therefore, we must take care to store the class both
+ * inside the StackTrace struct and in c_p->freason (important if
+ * the class is different from the class of the original
+ * exception).
+ */
+ freason_ptr = get_freason_ptr_from_exc(stacktrace);
if (class == am_error) {
- c_p->freason = EXC_ERROR & ~EXF_SAVETRACE;
+ *freason_ptr = c_p->freason = EXC_ERROR & ~EXF_SAVETRACE;
c_p->fvalue = value;
c_p->ftrace = stacktrace;
goto find_func_info;
} else if (class == am_exit) {
- c_p->freason = EXC_EXIT & ~EXF_SAVETRACE;
+ *freason_ptr = c_p->freason = EXC_EXIT & ~EXF_SAVETRACE;
c_p->fvalue = value;
c_p->ftrace = stacktrace;
goto find_func_info;
} else if (class == am_throw) {
- c_p->freason = EXC_THROWN & ~EXF_SAVETRACE;
+ *freason_ptr = c_p->freason = EXC_THROWN & ~EXF_SAVETRACE;
c_p->fvalue = value;
c_p->ftrace = stacktrace;
goto find_func_info;
diff --git a/erts/emulator/beam/ops.tab b/erts/emulator/beam/ops.tab
index 10ca74cd60..e9107933f9 100644
--- a/erts/emulator/beam/ops.tab
+++ b/erts/emulator/beam/ops.tab
@@ -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.
@@ -699,13 +699,18 @@ is_tuple NotTupleFail Tuple=x | is_tagged_tuple WrongRecordFail Tuple Arity Atom
is_tagged_tuple_ff f? f? rx A a
-get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \
+get_tuple_element Reg=x P1 D1=x | \
+ get_tuple_element Reg=x P2 D2=x | \
get_tuple_element Reg=x P3 D3=x | \
- succ(P1, P2) | succ(P2, P3) | \
- succ(D1, D2) | succ(D2, D3) => i_get_tuple_element3 Reg P1 D1
-
-get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \
- succ(P1, P2) | succ(D1, D2) => i_get_tuple_element2 Reg P1 D1
+ succ(P1, P2) | succ(P2, P3) | succ(D1, D2) | succ(D2, D3) | \
+ distinct(D1, Reg) | distinct(D2, Reg) | distinct(D3, Reg) => \
+ i_get_tuple_element3 Reg P1 D1
+
+get_tuple_element Reg=x P1 D1=x | \
+ get_tuple_element Reg=x P2 D2=x | \
+ succ(P1, P2) | succ(D1, D2) | \
+ distinct(D1, Reg) => \
+ i_get_tuple_element2 Reg P1 D1
get_tuple_element Reg=x P1 D1=x | get_tuple_element Reg=x P2 D2=x | \
succ(P1, P2) | distinct(D1, Reg) => i_get_tuple_element2_dst Reg P1 D1 D2
diff --git a/erts/emulator/nifs/common/socket_nif.c b/erts/emulator/nifs/common/socket_nif.c
index ee3b9f2a98..dc0847fa39 100644
--- a/erts/emulator/nifs/common/socket_nif.c
+++ b/erts/emulator/nifs/common/socket_nif.c
@@ -8860,35 +8860,67 @@ ERL_NIF_TERM nsetopt_lvl_ip_update_membership(ErlNifEnv* env,
#endif
// It must be a map
- if (!IS_MAP(env, eVal))
+ if (!IS_MAP(env, eVal)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "value *not* a map\r\n") );
return enif_make_badarg(env);
+ }
// It must have atleast two attributes
- if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2))
+ if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "invalid map value: %T\r\n", eVal) );
return enif_make_badarg(env);
+ }
- if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr))
+ if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "failed get multiaddr (map) attribute\r\n") );
return enif_make_badarg(env);
+ }
- if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface))
+ if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "failed get interface (map) attribute\r\n") );
return enif_make_badarg(env);
+ }
if ((xres = esock_decode_ip4_address(env,
eMultiAddr,
- &mreq.imr_multiaddr)) != NULL)
+ &mreq.imr_multiaddr)) != NULL) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "failed decode multiaddr %T: %s\r\n", eMultiAddr, xres) );
return esock_make_error_str(env, xres);
+ }
if ((xres = esock_decode_ip4_address(env,
eInterface,
- &mreq.imr_interface)) != NULL)
+ &mreq.imr_interface)) != NULL) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "failed decode interface %T: %s\r\n", eInterface, xres) );
return esock_make_error_str(env, xres);
+ }
res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq));
- if (res != 0)
- result = esock_make_error_errno(env, sock_errno());
- else
+ if (res != 0) {
+ int save_errno = sock_errno();
+
+ result = esock_make_error_errno(env, save_errno);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "failed setopt: %T (%d)\r\n", result, save_errno) );
+
+ } else {
result = esock_atom_ok;
+ }
return result;
}
@@ -9492,33 +9524,65 @@ ERL_NIF_TERM nsetopt_lvl_ipv6_update_membership(ErlNifEnv* env,
#endif
// It must be a map
- if (!IS_MAP(env, eVal))
+ if (!IS_MAP(env, eVal)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> "
+ "value *not* a map\r\n") );
return enif_make_badarg(env);
+ }
// It must have atleast two attributes
- if (!enif_get_map_size(env, eVal, &sz) || (sz >= 2))
+ if (!enif_get_map_size(env, eVal, &sz) || (sz < 2)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> "
+ "invalid map value: %T\r\n", eVal) );
return enif_make_badarg(env);
+ }
- if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr))
+ if (!GET_MAP_VAL(env, eVal, atom_multiaddr, &eMultiAddr)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> "
+ "failed get multiaddr (map) attribute\r\n") );
return enif_make_badarg(env);
+ }
- if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface))
+ if (!GET_MAP_VAL(env, eVal, atom_interface, &eInterface)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> "
+ "failed get interface (map) attribute\r\n") );
return enif_make_badarg(env);
+ }
if ((xres = esock_decode_ip6_address(env,
eMultiAddr,
- &mreq.ipv6mr_multiaddr)) != NULL)
+ &mreq.ipv6mr_multiaddr)) != NULL) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> "
+ "failed decode multiaddr %T: %s\r\n", eMultiAddr, xres) );
return esock_make_error_str(env, xres);
+ }
- if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface))
+ if (!GET_UINT(env, eInterface, &mreq.ipv6mr_interface)) {
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ip_update_membership -> "
+ "failed decode interface %T: %s\r\n", eInterface, xres) );
return esock_make_error(env, esock_atom_einval);
+ }
res = socket_setopt(descP->sock, level, opt, &mreq, sizeof(mreq));
- if (res != 0)
- result = esock_make_error_errno(env, sock_errno());
- else
+ if (res != 0) {
+ int save_errno = sock_errno();
+
+ result = esock_make_error_errno(env, save_errno);
+
+ SSDBG( descP,
+ ("SOCKET", "nsetopt_lvl_ipv6_update_membership -> "
+ "failed setopt: %T (%d)\r\n", result, save_errno) );
+
+ } else {
result = esock_atom_ok;
+ }
return result;
}
diff --git a/erts/emulator/test/bif_SUITE.erl b/erts/emulator/test/bif_SUITE.erl
index 43975d1800..c5abd04e07 100644
--- a/erts/emulator/test/bif_SUITE.erl
+++ b/erts/emulator/test/bif_SUITE.erl
@@ -612,6 +612,16 @@ binary_to_existing_atom(Config) when is_list(Config) ->
UnlikelyAtom = binary_to_atom(id(UnlikelyBin), latin1),
UnlikelyAtom = binary_to_existing_atom(UnlikelyBin, latin1),
+
+ %% ERL-944; a binary that was too large would overflow the latin1-to-utf8
+ %% conversion buffer.
+ OverflowAtom = <<0:511/unit:8,
+ 196, 133, 196, 133, 196, 133, 196, 133, 196, 133,
+ 196, 133, 196, 133, 196, 133, 196, 133, 196, 133,
+ 196, 133, 196, 133, 196, 133, 196, 133, 196, 133,
+ 196, 133, 196, 133, 196, 133, 196, 133, 196, 133>>,
+ {'EXIT', _} = (catch binary_to_existing_atom(OverflowAtom, latin1)),
+
ok.
diff --git a/erts/emulator/test/exception_SUITE.erl b/erts/emulator/test/exception_SUITE.erl
index c4d9ea515a..154bce3c35 100644
--- a/erts/emulator/test/exception_SUITE.erl
+++ b/erts/emulator/test/exception_SUITE.erl
@@ -23,6 +23,7 @@
-export([all/0, suite/0,
badmatch/1, pending_errors/1, nil_arith/1, top_of_stacktrace/1,
stacktrace/1, nested_stacktrace/1, raise/1, gunilla/1, per/1,
+ change_exception_class/1,
exception_with_heap_frag/1, backtrace_depth/1,
line_numbers/1]).
@@ -48,6 +49,7 @@ suite() ->
all() ->
[badmatch, pending_errors, nil_arith, top_of_stacktrace,
stacktrace, nested_stacktrace, raise, gunilla, per,
+ change_exception_class,
exception_with_heap_frag, backtrace_depth, line_numbers].
-define(try_match(E),
@@ -512,6 +514,38 @@ t1(_,X,_) ->
t2(_,X,_) ->
(X bsl 1) + 1.
+change_exception_class(_Config) ->
+ try
+ change_exception_class_1(fun() -> throw(arne) end)
+ catch
+ error:arne ->
+ ok;
+ Class:arne ->
+ ct:fail({wrong_exception_class,Class})
+ end.
+
+change_exception_class_1(F) ->
+ try
+ change_exception_class_2(F)
+ after
+ %% The exception would be caught and rethrown using
+ %% an i_raise instruction. Before the correction
+ %% of the raw_raise instruction, the change of class
+ %% would not stick.
+ io:put_chars("Exception automatically rethrown here\n")
+ end.
+
+change_exception_class_2(F) ->
+ try
+ F()
+ catch
+ throw:Reason:Stack ->
+ %% Translated to a raw_raise instruction.
+ %% The change of exception class would not stick
+ %% if the i_raise instruction was later executed.
+ erlang:raise(error, Reason, Stack)
+ end.
+
%%
%% Make sure that even if a BIF builds an heap fragment, then causes an exception,
%% the stacktrace term will still be OK (specifically, that it does not contain
diff --git a/erts/emulator/test/process_SUITE.erl b/erts/emulator/test/process_SUITE.erl
index b530ced566..3684cde8d4 100644
--- a/erts/emulator/test/process_SUITE.erl
+++ b/erts/emulator/test/process_SUITE.erl
@@ -1098,42 +1098,86 @@ process_info_status_handled_signal(Config) when is_list(Config) ->
%% OTP-15709
%% Provoke a bug where process_info(reductions) returned wrong result
%% because REDS_IN (def_arg_reg[5]) is read when the process in not running.
+%%
+%% And a bug where process_info(reductions) on a process which was releasing its
+%% main lock during execution could result in negative reduction diffs.
process_info_reductions(Config) when is_list(Config) ->
- pi_reductions_tester(spawn_link(fun() -> pi_reductions_spinnloop() end)),
- pi_reductions_tester(spawn_link(fun() -> pi_reductions_recvloop() end)),
+ {S1, S2} = case erlang:system_info(schedulers) of
+ 1 -> {1,1};
+ _ -> {1,2}
+ end,
+ io:format("Run on schedulers ~p and ~p\n", [S1,S2]),
+ Boss = self(),
+ Doer = spawn_opt(fun () ->
+ pi_reductions_tester(true, 10, fun pi_reductions_spinnloop/0, S2),
+ pi_reductions_tester(true, 10, fun pi_reductions_recvloop/0, S2),
+ pi_reductions_tester(false, 100, fun pi_reductions_main_unlocker/0, S2),
+ Boss ! {self(), done}
+ end,
+ [link, {scheduler, S1}]),
+
+ {Doer, done} = receive M -> M end,
ok.
-pi_reductions_tester(Pid) ->
- {_, DiffList} =
- lists:foldl(fun(_, {Prev, Acc}) ->
- %% Add another item that force sending the request
- %% as a signal, like 'current_function'.
- PI = process_info(Pid, [reductions, current_function]),
- [{reductions,Reds}, {current_function,_}] = PI,
- Diff = Reds - Prev,
- {Diff, true} = {Diff, (Diff >= 0)},
- {Diff, true} = {Diff, (Diff =< 1000*1000)},
- {Reds, [Diff | Acc]}
- end,
- {0, []},
- lists:seq(1,10)),
+pi_reductions_tester(ForceSignal, MaxCalls, Fun, S2) ->
+ Pid = spawn_opt(Fun, [link, {scheduler,S2}]),
+ Extra = case ForceSignal of
+ true ->
+ %% Add another item that force sending the request
+ %% as a signal, like 'current_function'.
+ [current_function];
+ false ->
+ []
+ end,
+ LoopFun = fun Me(Calls, Prev, Acc0) ->
+ PI = process_info(Pid, [reductions | Extra]),
+ [{reductions,Reds} | _] = PI,
+ Diff = Reds - Prev,
+ %% Verify we get sane non-negative reduction diffs
+ {Diff, true} = {Diff, (Diff >= 0)},
+ {Diff, true} = {Diff, (Diff =< 1000*1000)},
+ Acc1 = [Diff | Acc0],
+ case Calls >= MaxCalls of
+ true -> Acc1;
+ false -> Me(Calls+1, Reds, Acc1)
+ end
+ end,
+ DiffList = LoopFun(0, 0, []),
unlink(Pid),
exit(Pid,kill),
- io:format("Reduction diffs: ~p\n", [DiffList]),
+ io:format("Reduction diffs: ~p\n", [lists:reverse(DiffList)]),
ok.
pi_reductions_spinnloop() ->
%% 6 args to make use of def_arg_reg[5] which is also used as REDS_IN
- pi_reductions_spinnloop(1, atom, "hej", self(), make_ref(), 3.14).
+ pi_reductions_spinnloop(999*1000, atom, "hej", self(), make_ref(), 3.14).
-pi_reductions_spinnloop(A,B,C,D,E,F) ->
- pi_reductions_spinnloop(B,C,D,E,F,A).
+pi_reductions_spinnloop(N,A,B,C,D,E) when N > 0 ->
+ pi_reductions_spinnloop(N-1,B,C,D,E,A);
+pi_reductions_spinnloop(0,_,_,_,_,_) ->
+ %% Stop to limit max number of reductions consumed
+ pi_reductions_recvloop().
pi_reductions_recvloop() ->
receive
"a free lunch" -> false
end.
+pi_reductions_main_unlocker() ->
+ Other = spawn_link(fun() -> receive die -> ok end end),
+ pi_reductions_main_unlocker_loop(Other).
+
+pi_reductions_main_unlocker_loop(Other) ->
+ %% Assumption: register(OtherPid, Name) will unlock main lock of calling
+ %% process during execution.
+ register(pi_reductions_main_unlocker, Other),
+ unregister(pi_reductions_main_unlocker),
+
+ %% Yield in order to increase probability of process_info sometimes probing
+ %% this process when it's not RUNNING.
+ erlang:yield(),
+ pi_reductions_main_unlocker_loop(Other).
+
%% Tests erlang:bump_reductions/1.
bump_reductions(Config) when is_list(Config) ->
diff --git a/erts/emulator/test/socket_SUITE.erl b/erts/emulator/test/socket_SUITE.erl
index e3545ccbf9..c6b5c31503 100644
--- a/erts/emulator/test/socket_SUITE.erl
+++ b/erts/emulator/test/socket_SUITE.erl
@@ -79,6 +79,7 @@
api_opt_simple_otp_options/1,
api_opt_simple_otp_rcvbuf_option/1,
api_opt_simple_otp_controlling_process/1,
+ api_opt_ip_add_drop_membership/1,
%% *** API Operation Timeout ***
api_to_connect_tcp4/1,
@@ -511,6 +512,8 @@ groups() ->
[{api, [], api_cases()},
{api_basic, [], api_basic_cases()},
{api_options, [], api_options_cases()},
+ {api_options_otp, [], api_options_otp_cases()},
+ {api_options_ip, [], api_options_ip_cases()},
{api_op_with_timeout, [], api_op_with_timeout_cases()},
{socket_closure, [], socket_closure_cases()},
{sc_ctrl_proc_exit, [], sc_cp_exit_cases()},
@@ -596,11 +599,22 @@ api_basic_cases() ->
api_options_cases() ->
[
+ {group, api_options_otp},
+ {group, api_options_ip}
+ ].
+
+api_options_otp_cases() ->
+ [
api_opt_simple_otp_options,
api_opt_simple_otp_rcvbuf_option,
api_opt_simple_otp_controlling_process
].
+api_options_ip_cases() ->
+ [
+ api_opt_ip_add_drop_membership
+ ].
+
api_op_with_timeout_cases() ->
[
api_to_connect_tcp4,
@@ -3173,7 +3187,7 @@ api_opt_simple_otp_controlling_process(suite) ->
api_opt_simple_otp_controlling_process(doc) ->
[];
api_opt_simple_otp_controlling_process(_Config) when is_list(_Config) ->
- ?TT(?SECS(5)),
+ ?TT(?SECS(30)),
tc_try(api_opt_simple_otp_controlling_process,
fun() -> api_opt_simple_otp_controlling_process() end).
@@ -3418,6 +3432,321 @@ api_opt_simple_otp_controlling_process() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Tests that the add_mambership and drop_membership ip options work.
+%% We create one server and two clients. The server only send messages,
+%% the clients only receives messages.
+%% An UDP datagram is forbidden (RFC 1122) from having a source address
+%% that is a multicast address (or a broadcast address).
+%% So, the server create a socket "for sending" and the clients sockets
+%% "for receiving".
+%% Sending socket: Bound to the local address (and any port).
+%% When sending, the dest will be the multicast address
+%% and port of the receiving socket.
+%% Receiving socket: Bound to the multicast address and port.
+api_opt_ip_add_drop_membership(suite) ->
+ [];
+api_opt_ip_add_drop_membership(doc) ->
+ ["OTP-15908 (ERL-980)"];
+api_opt_ip_add_drop_membership(_Config) when is_list(_Config) ->
+ ?TT(?SECS(30)),
+ tc_try(api_opt_ip_add_drop_membership,
+ fun() ->
+ has_ip_add_membership_support(),
+ has_ip_drop_membership_support(),
+ has_ip_multicast_support()
+ end,
+ fun() -> api_opt_ip_add_drop_membership() end).
+
+
+api_opt_ip_add_drop_membership() ->
+ Set = fun(S, Key, Val) ->
+ socket:setopt(S, ip, Key, Val)
+ end,
+ AddMembership = fun(S, Val) -> Set(S, add_membership, Val) end,
+ DropMembership = fun(S, Val) -> Set(S, drop_membership, Val) end,
+
+ ServerSeq =
+ [
+ %% *** Wait for start order part ***
+ #{desc => "await start",
+ cmd => fun(State) ->
+ {Tester, MSA} = ?SEV_AWAIT_START(),
+ {ok, State#{tester => Tester, msa => MSA}}
+ end},
+ #{desc => "monitor tester",
+ cmd => fun(#{tester := Tester} = _State) ->
+ _MRef = erlang:monitor(process, Tester),
+ ok
+ end},
+
+ %% *** Init part ***
+ #{desc => "which local address",
+ cmd => fun(#{domain := Domain} = State) ->
+ LAddr = which_local_addr(Domain),
+ LSA = #{family => Domain, addr => LAddr},
+ {ok, State#{local_sa => LSA}}
+ end},
+ #{desc => "create socket",
+ cmd => fun(#{domain := Domain} = State) ->
+ case socket:open(Domain, dgram, udp) of
+ {ok, Sock} ->
+ {ok, State#{sock => Sock}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "make recv socket reuse addr",
+ cmd => fun(#{sock := Sock} = _State) ->
+ case socket:setopt(Sock, socket, reuseaddr, true) of
+ ok ->
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed set reuseaddr: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "bind recv socket to multicast address",
+ cmd => fun(#{sock := Sock, msa := MSA} = State) ->
+ case socket:bind(Sock, MSA) of
+ {ok, Port} ->
+ ?SEV_IPRINT("bound to:"
+ "~n ~p", [Port]),
+ {ok, State#{msa => MSA#{port => Port}}};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (init)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, init),
+ ok
+ end},
+
+ %% The actual test
+ #{desc => "await continue (add_membership)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, add_membership)
+ end},
+ #{desc => "add membership",
+ cmd => fun(#{sock := Sock,
+ msa := #{addr := MAddr},
+ local_sa := #{addr := Addr}} = State) ->
+ MReq = #{multiaddr => MAddr,
+ interface => Addr},
+ ?SEV_IPRINT("try add membership to:"
+ "~n ~p", [MReq]),
+ case AddMembership(Sock, MReq) of
+ ok ->
+ ?SEV_IPRINT("membership added"),
+ {ok, State#{mreq => MReq}};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed adding membership to: "
+ "~n ~p"
+ "~n Reason: ~p",
+ [MReq, Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (add-membership)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, add_membership),
+ ok
+ end},
+
+ #{desc => "await continue (drop_membership)",
+ cmd => fun(#{tester := Tester} = _State) ->
+ ?SEV_AWAIT_CONTINUE(Tester, tester, drop_membership)
+ end},
+ #{desc => "drop membership",
+ cmd => fun(#{sock := Sock,
+ mreq := MReq} = State) ->
+ ?SEV_IPRINT("try drop membership from:"
+ "~n ~p", [MReq]),
+ case DropMembership(Sock, MReq) of
+ ok ->
+ ?SEV_IPRINT("membership dropped"),
+ {ok, maps:remove(mreq, State)};
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Failed drop membership from: "
+ "~n ~p"
+ "~n Reason: ~p",
+ [MReq, Reason]),
+ ERROR
+ end
+ end},
+ #{desc => "announce ready (drop-membership)",
+ cmd => fun(#{tester := Tester}) ->
+ ?SEV_ANNOUNCE_READY(Tester, drop_membership),
+ ok
+ end},
+
+
+ %% Termination
+ #{desc => "await terminate (from tester)",
+ cmd => fun(#{tester := Tester} = State) ->
+ case ?SEV_AWAIT_TERMINATE(Tester, tester) of
+ ok ->
+ {ok, maps:remove(tester, State)};
+ {error, _} = ERROR ->
+ ERROR
+ end
+ end},
+ #{desc => "close socket",
+ cmd => fun(#{sock := Sock} = State) ->
+ socket:close(Sock),
+ {ok, maps:remove(sock, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ TesterSeq =
+ [
+ %% *** Init part ***
+ #{desc => "monitor server",
+ cmd => fun(#{server := Server} = _State) ->
+ _MRef = erlang:monitor(process, Server),
+ ok
+ end},
+
+ %% Start the server
+ #{desc => "order server start",
+ cmd => fun(#{server := Pid, msa := MSA} = _State) ->
+ ?SEV_ANNOUNCE_START(Pid, MSA),
+ ok
+ end},
+ #{desc => "await server ready (init)",
+ cmd => fun(#{server := Pid} = _State) ->
+ case ?SEV_AWAIT_READY(Pid, server, init) of
+ ok ->
+ ok;
+ {error, Reason} = ERROR ->
+ ?SEV_EPRINT("Start of server failed: "
+ "~n ~p", [Reason]),
+ ERROR
+ end
+ end},
+
+
+ %% *** The actual test ***
+ #{desc => "order server to continue (add-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, add_membership),
+ ok
+ end},
+ #{desc => "await server ready (add-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, add_membership)
+ end},
+
+ #{desc => "order server to continue (drop-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_CONTINUE(Server, drop_membership),
+ ok
+ end},
+ #{desc => "await server ready (drop-membership)",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_AWAIT_READY(Server, server, drop_membership)
+ end},
+
+ ?SEV_SLEEP(?SECS(1)),
+
+ %% *** Termination ***
+ #{desc => "order server terminate",
+ cmd => fun(#{server := Server} = _State) ->
+ ?SEV_ANNOUNCE_TERMINATE(Server),
+ ok
+ end},
+ #{desc => "await server termination",
+ cmd => fun(#{server := Server} = State) ->
+ ?SEV_AWAIT_TERMINATION(Server),
+ {ok, maps:remove(server, State)}
+ end},
+
+ %% *** We are done ***
+ ?SEV_FINISH_NORMAL
+ ],
+
+
+ i("get multicast address"),
+ Domain = inet,
+ MAddr = which_ip_multicast_address(),
+ MSA = #{family => Domain, addr => MAddr},
+
+ i("start server evaluator"),
+ ServerInitState = #{domain => Domain},
+ Server = ?SEV_START("server", ServerSeq, ServerInitState),
+
+ i("start tester evaluator"),
+ TesterInitState = #{domain => Domain,
+ msa => MSA,
+ server => Server#ev.pid},
+ Tester = ?SEV_START("tester", TesterSeq, TesterInitState),
+
+ i("await evaluator(s)"),
+ ok = ?SEV_AWAIT_FINISH([Tester, Server]).
+
+
+
+which_ip_multicast_address() ->
+ which_multicast_address(inet).
+
+which_multicast_address(Domain) ->
+ case os:type() of
+ {unix, linux} ->
+ WhichMAddr = fun([_, _, MAddr]) -> MAddr end,
+ which_multicast_address2(Domain, WhichMAddr);
+
+ {unix, sunos} ->
+ WhichMAddr = fun([_, MAddr, _]) -> MAddr end,
+ which_multicast_address2(Domain, WhichMAddr);
+
+ Type ->
+ not_supported({multicast, Type})
+ end.
+
+%% Note that the 'netstat -g' table looks different on linux and SunOS
+%% Linux: IfName - RefCnt - Group
+%% SunOS: IfName - Group - RefCnt
+
+which_multicast_address2(Domain, WhichMAddr) ->
+ IfName = which_local_host_ifname(Domain),
+ NetstatGroupsStr = os:cmd("netstat -g | grep " ++ IfName),
+ NetstatGroups0 = string:tokens(NetstatGroupsStr, [$\n]),
+ NetstatGroups = [string:tokens(G, [$ ]) || G <- NetstatGroups0],
+ MAddrs = [WhichMAddr(NetstatGroup) || NetstatGroup <-
+ NetstatGroups],
+ which_multicast_address3(Domain, MAddrs).
+
+which_multicast_address3(_Domain, []) ->
+ not_supported({multicast, no_valid_addrs});
+which_multicast_address3(Domain, [MAddrStr|MAddrs]) ->
+ %% Even on linux some of these are not actually addresses, but
+ %% "host names", such as all-systems.mcast.net. But both
+ %% address strings, such as "224.0.0.251" and host name strings
+ %% gets translated into an address by the inet:inet:getaddr/2.
+ case inet:getaddr(MAddrStr, Domain) of
+ {ok, MAddr} ->
+ MAddr;
+ {error, _} ->
+ which_multicast_address3(Domain, MAddrs)
+ end.
+
+which_local_host_ifname(Domain) ->
+ case which_local_host_info(Domain) of
+ {ok, {Name, _Addr, _Flags}} ->
+ Name;
+ {error, Reason} ->
+ not_supported({multicast, Reason})
+ end.
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% %%
%% API OPERATIONS WITH TIMEOUT %%
@@ -11304,7 +11633,7 @@ traffic_ping_pong_send_and_receive_udp2(InitState) ->
LSA = #{family => Domain, addr => LAddr},
{ok, State#{local_sa => LSA}}
end},
- #{desc => "create listen socket",
+ #{desc => "create socket",
cmd => fun(#{domain := Domain} = State) ->
case socket:open(Domain, dgram, udp) of
{ok, Sock} ->
@@ -17899,47 +18228,146 @@ local_host() ->
end.
+
+
%% This gets the local address (not 127.0...)
%% We should really implement this using the (new) net module,
%% but until that gets the necessary functionality...
which_local_addr(Domain) ->
- case inet:getifaddrs() of
- {ok, IFL} ->
- which_addr(Domain, IFL);
+ case which_local_host_info(Domain) of
+ {ok, {_Name, _Flags, Addr}} ->
+ Addr;
{error, Reason} ->
?FAIL({inet, getifaddrs, Reason})
end.
-which_addr(_Domain, []) ->
+%% which_addr(_Domain, []) ->
+%% ?FAIL(no_address);
+%% which_addr(Domain, [{"lo" ++ _, _}|IFL]) ->
+%% which_addr(Domain, IFL);
+%% which_addr(Domain, [{"docker" ++ _, _}|IFL]) ->
+%% which_addr(Domain, IFL);
+%% which_addr(Domain, [{"br-" ++ _, _}|IFL]) ->
+%% which_addr(Domain, IFL);
+%% which_addr(Domain, [{_Name, IFO}|IFL]) ->
+%% case which_addr2(Domain, IFO) of
+%% {ok, Addr} ->
+%% Addr;
+%% {error, no_address} ->
+%% which_addr(Domain, IFL)
+%% end;
+%% which_addr(Domain, [_|IFL]) ->
+%% which_addr(Domain, IFL).
+
+%% which_addr2(_Domain, []) ->
+%% {error, no_address};
+%% which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
+%% {ok, Addr};
+%% which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
+%% {ok, Addr};
+%% which_addr2(Domain, [_|IFO]) ->
+%% which_addr2(Domain, IFO).
+
+
+%% Returns the interface (name), flags and address (not 127...)
+%% of the local host.
+which_local_host_info(Domain) ->
+ case inet:getifaddrs() of
+ {ok, IFL} ->
+ which_local_host_info(Domain, IFL);
+ {error, _} = ERROR ->
+ ERROR
+ end.
+
+which_local_host_info(_Domain, []) ->
?FAIL(no_address);
-which_addr(Domain, [{"lo" ++ _, _}|IFL]) ->
- which_addr(Domain, IFL);
-which_addr(Domain, [{_Name, IFO}|IFL]) ->
- case which_addr2(Domain, IFO) of
- {ok, Addr} ->
- Addr;
- {error, no_address} ->
- which_addr(Domain, IFL)
+which_local_host_info(Domain, [{"lo" ++ _, _}|IFL]) ->
+ which_local_host_info(Domain, IFL);
+which_local_host_info(Domain, [{"docker" ++ _, _}|IFL]) ->
+ which_local_host_info(Domain, IFL);
+which_local_host_info(Domain, [{"br-" ++ _, _}|IFL]) ->
+ which_local_host_info(Domain, IFL);
+which_local_host_info(Domain, [{Name, IFO}|IFL]) ->
+ case which_local_host_info2(Domain, IFO) of
+ {ok, {Flags, Addr}} ->
+ {ok, {Name, Flags, Addr}};
+ {error, _} ->
+ which_local_host_info(Domain, IFL)
end;
-which_addr(Domain, [_|IFL]) ->
- which_addr(Domain, IFL).
+which_local_host_info(Domain, [_|IFL]) ->
+ which_local_host_info(Domain, IFL).
+
+which_local_host_info2(Domain, IFO) ->
+ case lists:keysearch(flags, 1, IFO) of
+ {value, {flags, Flags}} ->
+ which_local_host_info2(Domain, IFO, Flags);
+ false ->
+ {error, no_flags}
+ end.
-which_addr2(_Domain, []) ->
+which_local_host_info2(_Domain, [], _Flags) ->
{error, no_address};
-which_addr2(inet = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 4) ->
- {ok, Addr};
-which_addr2(inet6 = _Domain, [{addr, Addr}|_IFO]) when (size(Addr) =:= 8) ->
- {ok, Addr};
-which_addr2(Domain, [_|IFO]) ->
- which_addr2(Domain, IFO).
+which_local_host_info2(inet = _Domain, [{addr, Addr}|_IFO], Flags)
+ when (size(Addr) =:= 4) andalso (element(1, Addr) =/= 127) ->
+ {ok, {Flags, Addr}};
+which_local_host_info2(inet6 = _Domain, [{addr, Addr}|_IFO], Flags)
+ when (size(Addr) =:= 8) andalso
+ (element(1, Addr) =/= 0) andalso
+ (element(1, Addr) =/= 16#fe80) ->
+ {ok, {Flags, Addr}};
+which_local_host_info2(Domain, [_|IFO], Flags) ->
+ which_local_host_info2(Domain, IFO, Flags).
+
+
+
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%% Here are all the *general* test case condition functions.
+
+%% We also need (be able) to figure out the the multicast address,
+%% which we only support for some platforms (linux and sunos).
+%% We don't do that here, but since we can only do that (find a
+%% multicast address) for specific platforms, we check that we are
+%% on of those platforms here.
+has_ip_multicast_support() ->
+ case os:type() of
+ {unix, OsName} when (OsName =:= linux) orelse
+ (OsName =:= sunos) ->
+ case which_local_host_info(inet) of
+ {ok, {_Name, Flags, _Addr}} ->
+ case lists:member(multicast, Flags) of
+ true ->
+ ok;
+ false ->
+ not_supported(multicast)
+ end;
+ {error, Reason} ->
+ not_supported({multicast, Reason})
+ end;
+ Type ->
+ not_supported({multicast, Type})
+ end.
+has_ip_add_membership_support() ->
+ has_socket_option_ip_support(add_membership).
+has_ip_drop_membership_support() ->
+ has_socket_option_ip_support(drop_membership).
+has_socket_option_ip_support(Opt) ->
+ has_socket_option_support(ip, Opt).
+
+has_socket_option_support(Level, Option) ->
+ case socket:supports(options, Level, Option) of
+ true ->
+ ok;
+ false ->
+ not_supported({options, Level, Option})
+ end.
-%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
-%% Here are all the *general* test vase condition functions.
%% The idea is that this function shall test if the test host has
%% support for IPv6. If not, there is no point in running IPv6 tests.
@@ -17957,6 +18385,9 @@ has_support_ipv6() ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+not_supported(What) ->
+ skip({not_supported, What}).
+
not_yet_implemented() ->
skip("not yet implemented").
diff --git a/erts/emulator/test/system_info_SUITE.erl b/erts/emulator/test/system_info_SUITE.erl
index 55b1162cfb..b48be3dd04 100644
--- a/erts/emulator/test/system_info_SUITE.erl
+++ b/erts/emulator/test/system_info_SUITE.erl
@@ -37,10 +37,13 @@
-export([process_count/1, system_version/1, misc_smoke_tests/1,
heap_size/1, wordsize/1, memory/1, ets_limit/1, atom_limit/1,
+ procs_bug/1,
ets_count/1, atom_count/1, system_logger/1]).
-export([init/1, handle_event/2, handle_call/2]).
+-export([init_per_testcase/2, end_per_testcase/2]).
+
suite() ->
[{ct_hooks,[ts_install_cth]},
{timetrap, {minutes, 2}}].
@@ -48,8 +51,20 @@ suite() ->
all() ->
[process_count, system_version, misc_smoke_tests,
ets_count, heap_size, wordsize, memory, ets_limit, atom_limit, atom_count,
+ procs_bug,
system_logger].
+
+init_per_testcase(procs_bug, Config) ->
+ procs_bug(init_per_testcase, Config);
+init_per_testcase(_, Config) ->
+ Config.
+
+end_per_testcase(procs_bug, Config) ->
+ procs_bug(end_per_testcase, Config);
+end_per_testcase(_, _) ->
+ ok.
+
%%%
%%% The test cases -------------------------------------------------------------
%%%
@@ -654,3 +669,41 @@ handle_call(Msg, State) ->
handle_event(Event, State) ->
State ! {report_handler, Event},
{ok, State}.
+
+
+%% OTP-15909: Provoke bug that would cause VM crash
+%% if doing system_info(procs) when process have queued exit/down signals.
+procs_bug(init_per_testcase, Config) ->
+ %% Use single scheduler and process prio to starve monitoring processes
+ %% from handling their received DOWN signals.
+ OldSchedOnline = erlang:system_flag(schedulers_online,1),
+ [{schedulers_online, OldSchedOnline} | Config];
+procs_bug(end_per_testcase, Config) ->
+ erlang:system_flag(schedulers_online,
+ proplists:get_value(schedulers_online, Config)),
+ ok.
+
+procs_bug(Config) when is_list(Config) ->
+ {Monee,_} = spawn_opt(fun () -> receive die -> ok end end,
+ [monitor,{priority,max}]),
+ Papa = self(),
+ Pids = [begin
+ P = spawn_opt(fun () ->
+ erlang:monitor(process, Monee),
+ Papa ! {self(),ready},
+ receive "nada" -> no end
+ end,
+ [link, {priority,normal}]),
+ {P, ready} = receive M -> M end,
+ P
+ end
+ || _ <- lists:seq(1,10)],
+ process_flag(priority,high),
+ Monee ! die,
+ {'DOWN',_,process,Monee,normal} = receive M -> M end,
+
+ %% This call did crash VM as Pids have pending DOWN signals.
+ erlang:system_info(procs),
+ process_flag(priority,normal),
+ [begin unlink(P), exit(P, kill) end || P <- Pids],
+ ok.
diff --git a/erts/vsn.mk b/erts/vsn.mk
index 224570fb09..f06fd08540 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -18,7 +18,7 @@
# %CopyrightEnd%
#
-VSN = 10.4
+VSN = 10.4.4
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index 275c6268fa..f0d869381b 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,83 @@
<p>This document describes the changes made to the Compiler
application.</p>
+<section><title>Compiler 7.4.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>Fixed an incorrect type determination for constructed
+ binaries, which could cause <c>is_binary</c> checks to
+ succeed when they shouldn't have.</p>
+ <p>
+ Own Id: OTP-15872</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Compiler 7.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>The type optimization pass of the compiler could hang
+ or loop for a long time when analyzing a
+ <c>setelement/3</c> call with a varible position.</p>
+ <p>
+ Own Id: OTP-15828 Aux Id: ERL-948 </p>
+ </item>
+ <item>
+ <p>Certain complex receive statements would result in an
+ internal compiler failure.</p>
+ <p>
+ Own Id: OTP-15832 Aux Id: ERL-950 </p>
+ </item>
+ <item>
+ <p>Fixed an unsafe type optimization.</p>
+ <p>
+ Own Id: OTP-15838</p>
+ </item>
+ <item>
+ <p>Fixed a crash when optimizing compiler-generated
+ exceptions (like badmatch) whose offending term was a
+ constructed binary.</p>
+ <p>
+ Own Id: OTP-15839 Aux Id: ERL-954 </p>
+ </item>
+ <item>
+ <p>Fixed a bad optimization related to the <c>++/2</c>
+ operator, where the compiler assumed that it always
+ produced a list (<c>[] ++ RHS</c> returns <c>RHS</c>
+ verbatim, even if it's not a list).</p>
+ <p>
+ Own Id: OTP-15841</p>
+ </item>
+ <item>
+ <p>An <c>is_binary/1</c> test followed by
+ <c>is_bitstring/1</c> (or vice versa) could fail because
+ of an usafe optimization.</p>
+ <p>
+ Own Id: OTP-15845</p>
+ </item>
+ <item>
+ <p>A Core Erlang module where the last clause in a
+ <c>case</c> matched a map would fail to load.</p>
+ <p>
+ Own Id: OTP-15846 Aux Id: ERL-955 </p>
+ </item>
+ <item>
+ <p>Fixed a bug that could cause the compiler to crash
+ when compiling complex nested case expressions.</p>
+ <p>
+ Own Id: OTP-15848 Aux Id: ERL-956 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/compiler/src/beam_except.erl b/lib/compiler/src/beam_except.erl
index 28c89782c9..2305502800 100644
--- a/lib/compiler/src/beam_except.erl
+++ b/lib/compiler/src/beam_except.erl
@@ -140,8 +140,11 @@ fix_block_1([{set,[],[],{alloc,Live,{F1,F2,Needed0,F3}}}|Is], Words) ->
[{set,[],[],{alloc,Live,{F1,F2,Needed,F3}}}|Is]
end;
fix_block_1([I|Is], Words) ->
- [I|fix_block_1(Is, Words)].
-
+ [I|fix_block_1(Is, Words)];
+fix_block_1([], _Words) ->
+ %% Rare. The heap allocation was probably done by a binary
+ %% construction instruction.
+ [].
dig_out_fc(Arity, Is0) ->
Regs0 = maps:from_list([{{x,X},{arg,X}} || X <- seq(0, Arity-1)]),
diff --git a/lib/compiler/src/beam_ssa_codegen.erl b/lib/compiler/src/beam_ssa_codegen.erl
index c2d5035b19..07f4c8b461 100644
--- a/lib/compiler/src/beam_ssa_codegen.erl
+++ b/lib/compiler/src/beam_ssa_codegen.erl
@@ -1016,6 +1016,14 @@ bif_fail({catch_tag,_}) -> {f,0}.
next_block([]) -> none;
next_block([{Next,_}|_]) -> Next.
+%% Certain instructions (such as get_map_element or is_nonempty_list)
+%% are only used in guards and **must** have a non-zero label;
+%% otherwise, the loader will refuse to load the
+%% module. ensure_label/2 replaces a zero label with the "ultimate
+%% failure" label to make the module loadable. The instruction that
+%% have had the zero label replaced is **not** supposed to ever fail
+%% and actually jump to the label.
+
ensure_label(Fail0, #cg{ultimate_fail=Lbl}) ->
case bif_fail(Fail0) of
{f,0} -> {f,Lbl};
@@ -1160,6 +1168,11 @@ cg_block([#cg_set{op=call}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,_Fail}, St) ->
%% A call in try/catch block.
cg_block([I], none, St);
+cg_block([#cg_set{op=get_map_element,dst=Dst0,args=Args0},
+ #cg_set{op=succeeded,dst=Bool}], {Bool,Fail0}, St) ->
+ [Dst,Map,Key] = beam_args([Dst0|Args0], St),
+ Fail = ensure_label(Fail0, St),
+ {[{get_map_elements,Fail,Map,{list,[Key,Dst]}}],St};
cg_block([#cg_set{op=Op,dst=Dst0,args=Args0}=I,
#cg_set{op=succeeded,dst=Bool}], {Bool,Fail}, St) ->
[Dst|Args] = beam_args([Dst0|Args0], St),
@@ -1606,8 +1619,6 @@ cg_test({float,Op0}, Fail, Args, Dst, #cg_set{anno=Anno}) ->
'/' -> fdiv
end,
[line(Anno),{bif,Op,Fail,Args,Dst}];
-cg_test(get_map_element, Fail, [Map,Key], Dst, _I) ->
- [{get_map_elements,Fail,Map,{list,[Key,Dst]}}];
cg_test(peek_message, Fail, [], Dst, _I) ->
[{loop_rec,Fail,{x,0}}|copy({x,0}, Dst)];
cg_test(put_map, Fail, [{atom,exact},SrcMap|Ss], Dst, Set) ->
diff --git a/lib/compiler/src/beam_ssa_dead.erl b/lib/compiler/src/beam_ssa_dead.erl
index bb43a550ae..64b9b3e222 100644
--- a/lib/compiler/src/beam_ssa_dead.erl
+++ b/lib/compiler/src/beam_ssa_dead.erl
@@ -436,8 +436,22 @@ 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),
- beam_ssa:normalize(Br#b_br{bool=Val});
+ case get_value(Bool, Bs) of
+ #b_literal{val=Val}=Lit ->
+ case is_boolean(Val) of
+ true ->
+ beam_ssa:normalize(Br#b_br{bool=Lit});
+ false ->
+ %% Non-boolean literal. This means that this `br`
+ %% terminator will never actually be reached with
+ %% these bindings. (There must be a previous two-way
+ %% branch that branches the other way when Bool
+ %% is bound to a non-boolean literal.)
+ none
+ end;
+ #b_var{}=Var ->
+ beam_ssa:normalize(Br#b_br{bool=Var})
+ end;
eval_terminator(#b_br{bool=#b_literal{}}=Br, _Bs, _St) ->
beam_ssa:normalize(Br);
eval_terminator(#b_switch{arg=Arg,fail=Fail,list=List}=Sw, Bs, St) ->
@@ -680,11 +694,8 @@ will_succeed_test(is_list, is_nonempty_list) ->
maybe;
will_succeed_test(is_nonempty_list, is_list) ->
yes;
-will_succeed_test(T1, T2) ->
- case is_numeric_test(T1) andalso is_numeric_test(T2) of
- true -> maybe;
- false -> no
- end.
+will_succeed_test(_T1, _T2) ->
+ maybe.
will_succeed_1('=:=', A, '<', B) ->
if
@@ -769,11 +780,6 @@ will_succeed_vars('==', Val1, '/=', Val2) when Val1 == Val2 -> no;
will_succeed_vars(_, _, _, _) -> maybe.
-is_numeric_test(is_float) -> true;
-is_numeric_test(is_integer) -> true;
-is_numeric_test(is_number) -> true;
-is_numeric_test(_) -> false.
-
eval_type_test(Test, Arg) ->
case eval_type_test_1(Test, Arg) of
true -> yes;
diff --git a/lib/compiler/src/beam_ssa_pre_codegen.erl b/lib/compiler/src/beam_ssa_pre_codegen.erl
index bf99e8fc26..a2e687930b 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -156,7 +156,9 @@ passes(Opts) ->
%% Allocate registers.
?PASS(linear_scan),
?PASS(frame_size),
- ?PASS(turn_yregs)],
+ ?PASS(turn_yregs),
+
+ ?PASS(assert_no_critical_edges)],
[P || P <- Ps, P =/= ignore].
function(#b_function{anno=Anno,args=Args,bs=Blocks0,cnt=Count0}=F0,
@@ -1321,10 +1323,11 @@ fix_receives_1([{L,Blk}|Ls], Blocks0, Count0) ->
LoopExit = find_loop_exit(Rm, Blocks0),
Defs0 = beam_ssa:def([L], Blocks0),
CommonUsed = recv_common(Defs0, LoopExit, Blocks0),
- {Blocks1,Count1} = recv_fix_common(CommonUsed, LoopExit, Rm,
- Blocks0, Count0),
+ {Blocks1,Count1} = recv_crit_edges(Rm, LoopExit, Blocks0, Count0),
+ {Blocks2,Count2} = recv_fix_common(CommonUsed, LoopExit, Rm,
+ Blocks1, Count1),
Defs = ordsets:subtract(Defs0, CommonUsed),
- {Blocks,Count} = fix_receive(Rm, Defs, Blocks1, Count1),
+ {Blocks,Count} = fix_receive(Rm, Defs, Blocks2, Count2),
fix_receives_1(Ls, Blocks, Count);
#b_blk{} ->
fix_receives_1(Ls, Blocks0, Count0)
@@ -1341,6 +1344,57 @@ recv_common(Defs, Exit, Blocks) ->
Def = ordsets:subtract(Defs, ExitDefs),
ordsets:intersection(Def, ExitUsed).
+%% recv_crit_edges([RemoveMessageLabel], LoopExit,
+%% Blocks0, Count0) -> {Blocks,Count}.
+%%
+%% Adds dummy blocks on all conditional jumps to the exit block so that
+%% recv_fix_common/5 can insert phi nodes without having to worry about
+%% critical edges.
+
+recv_crit_edges(_Rms, none, Blocks0, Count0) ->
+ {Blocks0, Count0};
+recv_crit_edges(Rms, Exit, Blocks0, Count0) ->
+ Ls = beam_ssa:rpo(Rms, Blocks0),
+ rce_insert_edges(Ls, Exit, Count0, Blocks0).
+
+rce_insert_edges([L | Ls], Exit, Count0, Blocks0) ->
+ Successors = beam_ssa:successors(map_get(L, Blocks0)),
+ case member(Exit, Successors) of
+ true when Successors =/= [Exit] ->
+ {Blocks, Count} = rce_insert_edge(L, Exit, Count0, Blocks0),
+ rce_insert_edges(Ls, Exit, Count, Blocks);
+ _ ->
+ rce_insert_edges(Ls, Exit, Count0, Blocks0)
+ end;
+rce_insert_edges([], _Exit, Count, Blocks) ->
+ {Blocks, Count}.
+
+rce_insert_edge(L, Exit, Count, Blocks0) ->
+ #b_blk{last=Last0} = FromBlk0 = map_get(L, Blocks0),
+
+ ToExit = #b_br{bool=#b_literal{val=true},succ=Exit,fail=Exit},
+
+ FromBlk = FromBlk0#b_blk{last=rce_reroute_terminator(Last0, Exit, Count)},
+ EdgeBlk = #b_blk{anno=#{},is=[],last=ToExit},
+
+ Blocks = Blocks0#{ Count => EdgeBlk, L => FromBlk },
+ {Blocks, Count + 1}.
+
+rce_reroute_terminator(#b_br{succ=Exit}=Last, Exit, New) ->
+ rce_reroute_terminator(Last#b_br{succ=New}, Exit, New);
+rce_reroute_terminator(#b_br{fail=Exit}=Last, Exit, New) ->
+ rce_reroute_terminator(Last#b_br{fail=New}, Exit, New);
+rce_reroute_terminator(#b_br{}=Last, _Exit, _New) ->
+ Last;
+rce_reroute_terminator(#b_switch{fail=Exit}=Last, Exit, New) ->
+ rce_reroute_terminator(Last#b_switch{fail=New}, Exit, New);
+rce_reroute_terminator(#b_switch{list=List0}=Last, Exit, New) ->
+ List = [if
+ Lbl =:= Exit -> {Arg, New};
+ Lbl =/= Exit -> {Arg, Lbl}
+ end || {Arg, Lbl} <- List0],
+ Last#b_switch{list=List}.
+
%% recv_fix_common([CommonVar], LoopExit, [RemoveMessageLabel],
%% Blocks0, Count0) -> {Blocks,Count}.
%% Handle variables alwys defined in a receive and used
@@ -1415,12 +1469,15 @@ fix_receive([], _Defs, Blocks, Count) ->
find_loop_exit([L1,L2|_Ls], Blocks) ->
Path1 = beam_ssa:rpo([L1], Blocks),
Path2 = beam_ssa:rpo([L2], Blocks),
- find_loop_exit_1(reverse(Path1), reverse(Path2), none);
+ find_loop_exit_1(Path1, cerl_sets:from_list(Path2));
find_loop_exit(_, _) -> none.
-find_loop_exit_1([H|T1], [H|T2], _) ->
- find_loop_exit_1(T1, T2, H);
-find_loop_exit_1(_, _, Exit) -> Exit.
+find_loop_exit_1([H|T], OtherPath) ->
+ case cerl_sets:is_element(H, OtherPath) of
+ true -> H;
+ false -> find_loop_exit_1(T, OtherPath)
+ end;
+find_loop_exit_1([], _) -> none.
%% find_rm_blocks(StartLabel, Blocks) -> [Label].
%% Find all blocks that start with remove_message within the receive
diff --git a/lib/compiler/src/beam_ssa_type.erl b/lib/compiler/src/beam_ssa_type.erl
index 06b42f1928..68920e7dd3 100644
--- a/lib/compiler/src/beam_ssa_type.erl
+++ b/lib/compiler/src/beam_ssa_type.erl
@@ -23,8 +23,8 @@
-include("beam_ssa_opt.hrl").
-import(lists, [all/2,any/2,droplast/1,foldl/3,last/1,member/2,
- keyfind/3,partition/2,reverse/1,reverse/2,
- seq/2,sort/1,split/2]).
+ keyfind/3,reverse/1,reverse/2,
+ sort/1,split/2]).
-define(UNICODE_INT, #t_integer{elements={0,16#10FFFF}}).
@@ -840,15 +840,8 @@ type({bif,Bif}, Args, Ts, _Ds) ->
Type ->
Type
end;
-type(bs_init, [#b_literal{val=Type}|Args], _Ts, _Ds) ->
- case {Type,Args} of
- {new,[_,#b_literal{val=Unit}]} ->
- {binary,Unit};
- {append,[_,_,#b_literal{val=Unit}]} ->
- {binary,Unit};
- {private_append,[_,_,#b_literal{val=Unit}]} ->
- {binary,Unit}
- end;
+type(bs_init, _Args, _Ts, _Ds) ->
+ {binary, 1};
type(bs_extract, [Ctx], Ts, _Ds) ->
#t_bs_match{type=Type} = get_type(Ctx, Ts),
Type;
@@ -874,11 +867,11 @@ type(call, [#b_remote{mod=#b_literal{val=Mod},
true ->
none
end;
- {#t_integer{elements={Min,Max}},
+ {#t_integer{elements={Min,_}}=IntType,
#t_tuple{elements=Es0,size=Size}=T} ->
- %% We know this will land between Min and Max, so kill the
- %% types for those indexes.
- Es = maps:without(seq(Min, Max), Es0),
+ %% Remove type information for all indices that
+ %% falls into the range of the integer.
+ Es = remove_element_info(IntType, Es0),
case T#t_tuple.exact of
false ->
T#t_tuple{elements=Es,size=max(Min, Size)};
@@ -896,11 +889,15 @@ type(call, [#b_remote{mod=#b_literal{val=Mod},
{_,_} ->
#t_tuple{}
end;
- {erlang,'++',[List1,List2]} ->
- case get_type(List1, Ts) =:= cons orelse
- get_type(List2, Ts) =:= cons of
- true -> cons;
- false -> list
+ {erlang,'++',[LHS,RHS]} ->
+ LType = get_type(LHS, Ts),
+ RType = get_type(RHS, Ts),
+ case LType =:= cons orelse RType =:= cons of
+ true ->
+ cons;
+ false ->
+ %% `[] ++ RHS` yields RHS, even if RHS is not a list.
+ join(list, RType)
end;
{erlang,'--',[_,_]} ->
list;
@@ -1388,24 +1385,11 @@ get_type(#b_literal{val=Val}, _Ts) ->
%% type for L. For example, if L was known to be 'list', subtracting
%% 'cons' would give 'nil' as the only possible type. The result of the
%% subtraction for L will be added to FailTypes.
-%%
-%% Here is another example, asking about the variable Bool:
-%%
-%% Head = bif:hd L
-%% Bool = succeeded Head
-%%
-%% 'succeeded Head' will evaluate to 'true' if the instrution that
-%% defined Head succeeded. In this case, it is the 'bif:hd L'
-%% instruction, which will succeed if L is 'cons'. Thus, the meet of
-%% the previous type for L and 'cons' will be added to SuccTypes.
-%%
-%% If 'succeeded Head' evaluates to 'false', it means that 'bif:hd L'
-%% failed and that L is not 'cons'. 'cons' can be subtracted from the
-%% previously known type for L and the result put in FailTypes.
infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
#{V:=#b_set{op=Op,args=Args}} = Ds,
- Types0 = infer_type(Op, Args, Ds),
+ PosTypes0 = infer_type(Op, Args, Ds),
+ NegTypes0 = infer_type_negative(Op, Args, Ds),
%% We must be careful with types inferred from '=:='.
%%
@@ -1416,13 +1400,17 @@ infer_types_br(#b_var{}=V, Ts, #d{ds=Ds}) ->
%%
%% However, it is safe to subtract a type inferred from '=:=' if
%% it is single-valued, e.g. if it is [] or the atom 'true'.
- EqTypes0 = infer_eq_type(Op, Args, Ts, Ds),
- {Types1,EqTypes} = partition(fun({_,T}) ->
- is_singleton_type(T)
- end, EqTypes0),
- Types = Types1 ++ Types0,
- {meet_types(EqTypes++Types, Ts),subtract_types(Types, Ts)}.
+ EqTypes = infer_eq_type(Op, Args, Ts, Ds),
+ NegTypes1 = [P || {_,T}=P <- EqTypes, is_singleton_type(T)],
+
+ PosTypes = EqTypes ++ PosTypes0,
+ SuccTs = meet_types(PosTypes, Ts),
+
+ NegTypes = NegTypes0 ++ NegTypes1,
+ FailTs = subtract_types(NegTypes, Ts),
+
+ {SuccTs,FailTs}.
infer_types_switch(V, Lit, Ts, #d{ds=Ds}) ->
Types = infer_eq_type({bif,'=:='}, [V, Lit], Ts, Ds),
@@ -1457,6 +1445,19 @@ infer_eq_lit(#b_set{op=get_tuple_element,
[{Tuple,#t_tuple{size=Index,elements=Es}}];
infer_eq_lit(_, _) -> [].
+infer_type_negative(Op, Args, Ds) ->
+ case is_negative_inference_safe(Op, Args) of
+ true ->
+ infer_type(Op, Args, Ds);
+ false ->
+ []
+ end.
+
+%% Conservative list of instructions for which negative
+%% inference is safe.
+is_negative_inference_safe(is_nonempty_list, _Args) -> true;
+is_negative_inference_safe(_, _) -> false.
+
infer_type({bif,element}, [#b_literal{val=Pos},#b_var{}=Tuple], _Ds) ->
if
is_integer(Pos), 1 =< Pos ->
@@ -1649,6 +1650,12 @@ get_literal_from_type(nil) ->
#b_literal{val=[]};
get_literal_from_type(_) -> none.
+remove_element_info(#t_integer{elements={Min,Max}}, Es) ->
+ foldl(fun(El, Acc) when Min =< El, El =< Max ->
+ maps:remove(El, Acc);
+ (_El, Acc) -> Acc
+ end, Es, maps:keys(Es)).
+
t_atom() ->
#t_atom{elements=any}.
diff --git a/lib/compiler/src/beam_validator.erl b/lib/compiler/src/beam_validator.erl
index 09a5a6c104..ebe9631e09 100644
--- a/lib/compiler/src/beam_validator.erl
+++ b/lib/compiler/src/beam_validator.erl
@@ -2844,10 +2844,14 @@ call_return_type_1(erlang, setelement, 3, Vst) ->
setelement(3, TupleType, #{})
end;
call_return_type_1(erlang, '++', 2, Vst) ->
- case get_term_type({x,0}, Vst) =:= cons orelse
- get_term_type({x,1}, Vst) =:= cons of
- true -> cons;
- false -> list
+ LType = get_term_type({x,0}, Vst),
+ RType = get_term_type({x,1}, Vst),
+ case LType =:= cons orelse RType =:= cons of
+ true ->
+ cons;
+ false ->
+ %% `[] ++ RHS` yields RHS, even if RHS is not a list
+ join(list, RType)
end;
call_return_type_1(erlang, '--', 2, _Vst) ->
list;
diff --git a/lib/compiler/test/beam_except_SUITE.erl b/lib/compiler/test/beam_except_SUITE.erl
index 8e3b373d29..67947dc292 100644
--- a/lib/compiler/test/beam_except_SUITE.erl
+++ b/lib/compiler/test/beam_except_SUITE.erl
@@ -21,7 +21,8 @@
-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
init_per_group/2,end_per_group/2,
- multiple_allocs/1,bs_get_tail/1,coverage/1]).
+ multiple_allocs/1,bs_get_tail/1,coverage/1,
+ binary_construction_allocation/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -32,7 +33,8 @@ groups() ->
[{p,[parallel],
[multiple_allocs,
bs_get_tail,
- coverage]}].
+ coverage,
+ binary_construction_allocation]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -118,6 +120,20 @@ coverage(_) ->
fake_function_clause(A) -> error(function_clause, [A,42.0]).
+
+binary_construction_allocation(_Config) ->
+ ok = do_binary_construction_allocation("PUT"),
+ ok.
+
+do_binary_construction_allocation(Req) ->
+ %% Allocation for building the error term was done by the
+ %% bs_init2 instruction. beam_except crashed because it expected
+ %% an explicit allocation instruction.
+ ok = case Req of
+ "POST" -> {error, <<"BAD METHOD ", Req/binary>>, Req};
+ _ -> ok
+ end.
+
id(I) -> I.
-file("fake.erl", 1).
diff --git a/lib/compiler/test/beam_ssa_SUITE.erl b/lib/compiler/test/beam_ssa_SUITE.erl
index 15cf9bcbf3..256bb95559 100644
--- a/lib/compiler/test/beam_ssa_SUITE.erl
+++ b/lib/compiler/test/beam_ssa_SUITE.erl
@@ -22,7 +22,8 @@
-export([all/0,suite/0,groups/0,init_per_suite/1,end_per_suite/1,
init_per_group/2,end_per_group/2,
calls/1,tuple_matching/1,recv/1,maps/1,
- cover_ssa_dead/1,combine_sw/1,share_opt/1]).
+ cover_ssa_dead/1,combine_sw/1,share_opt/1,
+ beam_ssa_dead_crash/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -37,7 +38,8 @@ groups() ->
maps,
cover_ssa_dead,
combine_sw,
- share_opt
+ share_opt,
+ beam_ssa_dead_crash
]}].
init_per_suite(Config) ->
@@ -188,6 +190,12 @@ recv(_Config) ->
self() ! {[self(),r1],{2,99,<<"data">>}},
{Parent,r1,<<1:32,2:8,99:8,"data">>} = tricky_recv_4(),
+ %% Test tricky_recv_5/0.
+ self() ! 1,
+ a = tricky_recv_5(),
+ self() ! 2,
+ b = tricky_recv_5(),
+
ok.
sync_wait_mon({Pid, Ref}, Timeout) ->
@@ -293,6 +301,26 @@ tricky_recv_4() ->
end,
id({Pid,R,Request}).
+%% beam_ssa_pre_codegen would accidentally create phi nodes on critical edges
+%% when fixing up receives; the call to id/2 can either succeed or land in the
+%% catch block, and we added a phi node to its immediate successor.
+tricky_recv_5() ->
+ try
+ receive
+ X=1 ->
+ id(42),
+ a;
+ X=2 ->
+ b
+ end,
+ case X of
+ 1 -> a;
+ 2 -> b
+ end
+ catch
+ _:_ -> c
+ end.
+
maps(_Config) ->
{'EXIT',{{badmatch,#{}},_}} = (catch maps_1(any)),
ok.
@@ -492,6 +520,60 @@ do_share_opt(A) ->
end,
receive after 1 -> ok end.
+beam_ssa_dead_crash(_Config) ->
+ not_A_B = do_beam_ssa_dead_crash(id(false), id(true)),
+ not_A_not_B = do_beam_ssa_dead_crash(false, false),
+ neither = do_beam_ssa_dead_crash(true, false),
+ neither = do_beam_ssa_dead_crash(true, true),
+ ok.
+
+do_beam_ssa_dead_crash(A, B) ->
+ %% beam_ssa_dead attempts to shortcut branches that branch other
+ %% branches. When a two-way branch is encountered, beam_ssa_dead
+ %% will simulate execution along both paths, in the hope that both
+ %% paths happens to end up in the same place.
+ %%
+ %% During the simulated execution of this function, the boolean
+ %% varible for a `br` instruction would be replaced with the
+ %% literal atom `nil`, which is not allowed, and would crash the
+ %% compiler. In practice, during the actual execution, control
+ %% would never be transferred to that `br` instruction when the
+ %% variable in question had the value `nil`.
+ %%
+ %% beam_ssa_dead has been updated to immediately abort the search
+ %% along the current path if there is an attempt to substitute a
+ %% non-boolean value into a `br` instruction.
+
+ case
+ case not A of
+ false ->
+ false;
+ true ->
+ B
+ end
+ of
+ V
+ when
+ V /= nil
+ andalso
+ V /= false ->
+ not_A_B;
+ _ ->
+ case
+ case not A of
+ false ->
+ false;
+ true ->
+ not B
+ end
+ of
+ true ->
+ not_A_not_B;
+ false ->
+ neither
+ end
+ end.
+
%% The identity function.
id(I) -> I.
diff --git a/lib/compiler/test/beam_type_SUITE.erl b/lib/compiler/test/beam_type_SUITE.erl
index 882e281a44..076a604aa4 100644
--- a/lib/compiler/test/beam_type_SUITE.erl
+++ b/lib/compiler/test/beam_type_SUITE.erl
@@ -24,7 +24,7 @@
integers/1,numbers/1,coverage/1,booleans/1,setelement/1,
cons/1,tuple/1,record_float/1,binary_float/1,float_compare/1,
arity_checks/1,elixir_binaries/1,find_best/1,
- test_size/1,cover_lists_functions/1]).
+ test_size/1,cover_lists_functions/1,list_append/1,bad_binary_unit/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -47,7 +47,9 @@ groups() ->
elixir_binaries,
find_best,
test_size,
- cover_lists_functions
+ cover_lists_functions,
+ list_append,
+ bad_binary_unit
]}].
init_per_suite(Config) ->
@@ -271,8 +273,22 @@ setelement(_Config) ->
T0 = id({a,42}),
{a,_} = T0,
{b,_} = setelement(1, T0, b),
+ {z,b} = do_setelement_1(<<(id(1)):32>>, {a,b}, z),
+ {new,two} = do_setelement_2(<<(id(1)):1>>, {one,two}, new),
ok.
+do_setelement_1(<<N:32>>, Tuple, NewValue) ->
+ _ = element(N, Tuple),
+ %% While updating the type for Tuple, beam_ssa_type would do:
+ %% maps:without(lists:seq(0, 4294967295), Elements)
+ setelement(N, Tuple, NewValue).
+
+do_setelement_2(<<N:1>>, Tuple, NewValue) ->
+ %% Cover the second clause in remove_element_info/2. The
+ %% type for the second element will be kept.
+ two = element(2, Tuple),
+ setelement(N, Tuple, NewValue).
+
cons(_Config) ->
[did] = cons(assigned, did),
@@ -487,5 +503,20 @@ cover_lists_functions(Config) ->
true = is_list(Zipped),
ok.
+list_append(_Config) ->
+ %% '++'/2 has a quirk where it returns the right-hand argument as-is when
+ %% the left-hand is [].
+ hello = id([]) ++ id(hello),
+ ok.
+
+%% OTP-15872: The compiler would treat the "Unit" of bs_init instructions as
+%% the unit of the result instead of the required unit of the input, causing
+%% is_binary checks to be wrongly optimized away.
+bad_binary_unit(_Config) ->
+ Bin = id(<<1,2,3>>),
+ Bitstring = <<Bin/binary,1:1>>,
+ false = is_binary(Bitstring),
+ ok.
+
id(I) ->
I.
diff --git a/lib/compiler/test/core_SUITE.erl b/lib/compiler/test/core_SUITE.erl
index e5611e99d1..72016c6d76 100644
--- a/lib/compiler/test/core_SUITE.erl
+++ b/lib/compiler/test/core_SUITE.erl
@@ -29,7 +29,8 @@
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,fun_letrec_effect/1]).
+ non_variable_apply/1,name_capture/1,fun_letrec_effect/1,
+ get_map_element/1]).
-include_lib("common_test/include/ct.hrl").
@@ -57,7 +58,8 @@ 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,fun_letrec_effect
+ non_variable_apply,name_capture,fun_letrec_effect,
+ get_map_element
]}].
@@ -95,6 +97,7 @@ end_per_group(_GroupName, Config) ->
?comp(non_variable_apply).
?comp(name_capture).
?comp(fun_letrec_effect).
+?comp(get_map_element).
try_it(Mod, Conf) ->
Src = filename:join(proplists:get_value(data_dir, Conf),
diff --git a/lib/compiler/test/core_SUITE_data/get_map_element.core b/lib/compiler/test/core_SUITE_data/get_map_element.core
new file mode 100644
index 0000000000..092b5e71eb
--- /dev/null
+++ b/lib/compiler/test/core_SUITE_data/get_map_element.core
@@ -0,0 +1,18 @@
+module 'get_map_element' ['get_map_element'/0]
+attributes []
+
+'get_map_element'/0 =
+ fun () ->
+ apply 'match_map'/1(~{'foo'=>'bar'}~)
+
+'match_map'/1 =
+ fun (_0) ->
+ case _0 of
+ <~{'foo':='bar'}~> when 'true' ->
+ 'ok'
+ %% It will be undefined behaviour at runtime if no
+ %% clause of the case can be selected. That can't
+ %% happen for this module, because match_map/1 is
+ %% always called with a matching map argument.
+ end
+end
diff --git a/lib/compiler/test/guard_SUITE.erl b/lib/compiler/test/guard_SUITE.erl
index ed0a56f064..cea7a374cd 100644
--- a/lib/compiler/test/guard_SUITE.erl
+++ b/lib/compiler/test/guard_SUITE.erl
@@ -35,7 +35,8 @@
basic_andalso_orelse/1,traverse_dcd/1,
check_qlc_hrl/1,andalso_semi/1,t_tuple_size/1,binary_part/1,
bad_constants/1,bad_guards/1,
- guard_in_catch/1,beam_bool_SUITE/1]).
+ guard_in_catch/1,beam_bool_SUITE/1,
+ repeated_type_tests/1]).
suite() -> [{ct_hooks,[ts_install_cth]}].
@@ -53,7 +54,8 @@ groups() ->
rel_ops,rel_op_combinations,
literal_type_tests,basic_andalso_orelse,traverse_dcd,
check_qlc_hrl,andalso_semi,t_tuple_size,binary_part,
- bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE]}].
+ bad_constants,bad_guards,guard_in_catch,beam_bool_SUITE,
+ repeated_type_tests]}].
init_per_suite(Config) ->
test_lib:recompile(?MODULE),
@@ -2261,6 +2263,25 @@ maps() ->
evidence(#{0 := Charge}) when 0; #{[] => Charge} == #{[] => 42} ->
ok.
+repeated_type_tests(_Config) ->
+ binary = repeated_type_test(<<42>>),
+ bitstring = repeated_type_test(<<1:1>>),
+ other = repeated_type_test(atom),
+ ok.
+
+repeated_type_test(T) ->
+ %% Test for a bug in beam_ssa_dead.
+ if is_bitstring(T) ->
+ if is_binary(T) -> %This test would be optimized away.
+ binary;
+ true ->
+ bitstring
+ end;
+ true ->
+ other
+ end.
+
+
%% Call this function to turn off constant propagation.
id(I) -> I.
diff --git a/lib/compiler/test/match_SUITE.erl b/lib/compiler/test/match_SUITE.erl
index 94bfbb0efe..aac9de278d 100644
--- a/lib/compiler/test/match_SUITE.erl
+++ b/lib/compiler/test/match_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 2004-2018. All Rights Reserved.
+%% Copyright Ericsson AB 2004-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.
@@ -25,7 +25,7 @@
match_in_call/1,untuplify/1,shortcut_boolean/1,letify_guard/1,
selectify/1,deselectify/1,underscore/1,match_map/1,map_vars_used/1,
coverage/1,grab_bag/1,literal_binary/1,
- unary_op/1,eq_types/1,match_after_return/1]).
+ unary_op/1,eq_types/1,match_after_return/1,match_right_tuple/1]).
-include_lib("common_test/include/ct.hrl").
@@ -41,7 +41,7 @@ groups() ->
shortcut_boolean,letify_guard,selectify,deselectify,
underscore,match_map,map_vars_used,coverage,
grab_bag,literal_binary,unary_op,eq_types,
- match_after_return]}].
+ match_after_return,match_right_tuple]}].
init_per_suite(Config) ->
@@ -902,4 +902,24 @@ match_after_return(Config) when is_list(Config) ->
mar_test_tuple(I) -> {gurka, I}.
+match_right_tuple(Config) when is_list(Config) ->
+ %% The loader wrongly coalesced certain get_tuple_element sequences, fusing
+ %% the code below into a single i_get_tuple_element2 operating on {x,0}
+ %% even though the first one overwrites it.
+ %%
+ %% {get_tuple_element,{x,0},0,{x,0}}.
+ %% {get_tuple_element,{x,0},1,{x,1}}.
+
+ Inner = {id(wrong_element), id(ok)},
+ Outer = {Inner, id(wrong_tuple)},
+ ok = match_right_tuple_1(Outer).
+
+match_right_tuple_1(T) ->
+ {A, _} = T,
+ {_, B} = A,
+ %% The call ensures that A is in {x,0} and B is in {x,1}
+ id(force_succ_regs(A, B)).
+
+force_succ_regs(_A, B) -> B.
+
id(I) -> I.
diff --git a/lib/compiler/test/receive_SUITE.erl b/lib/compiler/test/receive_SUITE.erl
index 0038eb1a4b..752491f0f8 100644
--- a/lib/compiler/test/receive_SUITE.erl
+++ b/lib/compiler/test/receive_SUITE.erl
@@ -26,7 +26,7 @@
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,
- match_built_terms/1]).
+ match_built_terms/1,elusive_common_exit/1]).
-include_lib("common_test/include/ct.hrl").
@@ -47,7 +47,7 @@ groups() ->
[{p,test_lib:parallel(),
[recv,coverage,otp_7980,ref_opt,export,wait,
recv_in_try,double_recv,receive_var_zero,
- match_built_terms]}].
+ match_built_terms,elusive_common_exit]}].
init_per_suite(Config) ->
@@ -427,4 +427,26 @@ match_built_terms(Config) when is_list(Config) ->
?MATCH_BUILT_TERM(Ref, <<A, B>>),
?MATCH_BUILT_TERM(Ref, #{ 1 => A, 2 => B}).
+elusive_common_exit(_Config) ->
+ self() ! {1, a},
+ self() ! {2, b},
+ {[z], [{2,b},{1,a}]} = elusive_loop([x,y,z], 2, []),
+ ok.
+
+elusive_loop(List, 0, Results) ->
+ {List, Results};
+elusive_loop(List, ToReceive, Results) ->
+ {Result, RemList} =
+ receive
+ {_Pos, _R} = Res when List =/= [] ->
+ [_H|T] = List,
+ {Res, T};
+ {_Pos, _R} = Res when List =:= [] ->
+ {Res, []}
+ end,
+ %% beam_ssa_pre_codegen:fix_receives() would fail to find
+ %% the common exit block for this receive. That would mean
+ %% that it would not insert all necessary copy instructions.
+ elusive_loop(RemList, ToReceive-1, [Result | Results]).
+
id(I) -> I.
diff --git a/lib/compiler/vsn.mk b/lib/compiler/vsn.mk
index 494de072ff..508bbc902c 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.4
+COMPILER_VSN = 7.4.2
diff --git a/lib/crypto/c_src/cipher.c b/lib/crypto/c_src/cipher.c
index 00072af632..0532fb7566 100644
--- a/lib/crypto/c_src/cipher.c
+++ b/lib/crypto/c_src/cipher.c
@@ -334,6 +334,7 @@ ERL_NIF_TERM cipher_types_as_list(ErlNifEnv* env)
continue;
if ((p->cipher.p != NULL) ||
+ (p->flags & AES_CTR_COMPAT) ||
(p->type.atom == atom_aes_ige256)) /* Special handling. Bad indeed... */
{
hd = enif_make_list_cell(env, p->type.atom, hd);
diff --git a/lib/crypto/doc/src/notes.xml b/lib/crypto/doc/src/notes.xml
index b69657bfa8..5f47981855 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,23 @@
</header>
<p>This document describes the changes made to the Crypto application.</p>
+<section><title>Crypto 4.5.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The cipher aes-ctr was disabled by misstake in
+ crypto:supports for cryptolibs before 1.0.1. It worked
+ however in the encrypt and decrypt functions.</p>
+ <p>
+ Own Id: OTP-15829</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.5</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 72a51bfec9..2315cb3c48 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.5
+CRYPTO_VSN = 4.5.1
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index dd0a2bfd7d..b72453aac7 100644
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -32,6 +32,38 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<section><title>Dialyzer 4.0.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Make sure Dialyzer does not crash if the formatting
+ of results fails. Instead of crashing, an unformatted
+ version of the results is returned. </p>
+ <p>
+ Own Id: OTP-15922 Aux Id: PR-2240, ERL-949 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Dialyzer 4.0.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug that caused a crash when indenting a
+ Dialyzer warning mentioning more than one record field.
+ </p>
+ <p>
+ Own Id: OTP-15861 Aux Id: ERL-953 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 4.0</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index cfe5fa9b3f..c1bc5ff597 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -605,11 +605,9 @@ ordinal(N) when is_integer(N) -> io_lib:format("~wth", [N]).
%% Functions that parse type strings, literal strings, and contract
%% strings. Return strings formatted by erl_pp.
-%% If lib/hipe/cerl/erl_types.erl is compiled with DEBUG=true,
-%% the contents of opaque types are showed inside brackets.
-%% Since erl_parse:parse_form() cannot handle the bracket syntax,
-%% no indentation is done.
-%%-define(DEBUG , true).
+%% Note we always have to catch any error when trying to parse
+%% the syntax because other BEAM languages may not emit an
+%% Erlang AST that transforms into valid Erlang Source Code.
-define(IND, 10).
@@ -620,13 +618,15 @@ con(M, F, Src, I) ->
sig(Src, false) ->
Src;
sig(Src, true) ->
- Str = lists:flatten(io_lib:format("-spec ~w:~tw~ts.", [a, b, Src])),
- {ok, Tokens, _EndLocation} = erl_scan:string(Str),
- exec(fun() ->
- {ok, {attribute, _, spec, {_MFA, Types}}} =
- erl_parse:parse_form(Tokens),
- indentation(?IND) ++ pp_spec(Types)
- end, Src).
+ try
+ Str = lists:flatten(io_lib:format("-spec ~w:~tw~ts.", [a, b, Src])),
+ {ok, Tokens, _EndLocation} = erl_scan:string(Str),
+ {ok, {attribute, _, spec, {_MFA, Types}}} =
+ erl_parse:parse_form(Tokens),
+ indentation(?IND) ++ pp_spec(Types)
+ catch
+ _:_ -> Src
+ end.
%% Argument(list)s are a mix of types and Erlang code. Note: sometimes
%% (contract_range, call_without_opaque, opaque_type_test), the initial
@@ -642,11 +642,11 @@ c(Cerl, _I) ->
field_diffs(Src, false) ->
Src;
field_diffs(Src, true) ->
- Fields = string:split(Src, " and "),
+ Fields = string:split(Src, " and ", all),
lists:join(" and ", [field_diff(Field) || Field <- Fields]).
field_diff(Field) ->
- [F | Ts] = string:split(Field, "::"),
+ [F | Ts] = string:split(Field, "::", all),
F ++ " ::" ++ t(lists:flatten(lists:join("::", Ts)), true).
rec_type("record "++Src, I) ->
@@ -658,7 +658,7 @@ ps("pattern "++Src, I) ->
ps("variable "++_=Src, _I) ->
Src;
ps("record field"++Rest, I) ->
- [S, TypeStr] = string:split(Rest, "of type "),
+ [S, TypeStr] = string:split(Rest, "of type ", all),
"record field" ++ S ++ "of type " ++ t(TypeStr, I).
%% Scan and parse a type or a literal, and pretty-print it using erl_pp.
@@ -681,21 +681,15 @@ ts(Src) ->
[C1|Src1] = Src, % $< (product) or $( (arglist)
[C2|RevSrc2] = lists:reverse(Src1),
Src2 = lists:reverse(RevSrc2),
- exec(fun() ->
- Types = parse_types_and_literals(Src2),
- CommaInd = [$, | Ind],
- (indentation(?IND-1) ++
- [C1 | lists:join(CommaInd, [pp_type(Type) || Type <- Types])] ++
- [C2])
- end, Src).
-
--ifdef(DEBUG).
-exec(F, R) ->
- try F() catch _:_ -> R end.
--else.
-exec(F, _) ->
- F().
--endif.
+ try
+ Types = parse_types_and_literals(Src2),
+ CommaInd = [$, | Ind],
+ (indentation(?IND-1) ++
+ [C1 | lists:join(CommaInd, [pp_type(Type) || Type <- Types])] ++
+ [C2])
+ catch
+ _:_ -> Src
+ end.
indentation(I) ->
[$\n | lists:duplicate(I, $\s)].
diff --git a/lib/dialyzer/test/small_SUITE_data/results/union_paren b/lib/dialyzer/test/small_SUITE_data/results/union_paren
index 3a3526df89..1766773f2d 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/union_paren
+++ b/lib/dialyzer/test/small_SUITE_data/results/union_paren
@@ -1,7 +1,25 @@
-union_paren.erl:12: Function t2/0 has no local return
-union_paren.erl:13: The call union_paren:t2(3.14) breaks the contract (integer() | atom()) -> integer()
-union_paren.erl:19: Function t3/0 has no local return
-union_paren.erl:20: The pattern 3.14 can never match the type atom() | integer()
-union_paren.erl:5: Function t1/0 has no local return
-union_paren.erl:6: The call union_paren:t1(3.14) breaks the contract ((A::integer()) | (B::atom())) -> integer()
+union_paren.erl:20: Function r1/0 has no local return
+union_paren.erl:21: Record construction #r1{f1::[4,...],f2::'undefined',f3::'undefined',f8::float()} violates the declared type of field f2::[atom() | pid() | integer()] and f3::[atom() | pid() | integer()] and f8::[atom() | pid() | integer()]
+union_paren.erl:23: Function t1/0 has no local return
+union_paren.erl:24: The call union_paren:t1(3.14) breaks the contract ((A::integer()) | (B::atom())) -> integer()
+union_paren.erl:30: Function t2/0 has no local return
+union_paren.erl:31: The call union_paren:t2(3.14) breaks the contract (integer() | atom()) -> integer()
+union_paren.erl:37: Function t3/0 has no local return
+union_paren.erl:38: The pattern 3.14 can never match the type atom() | integer()
+union_paren.erl:44: Function c1/0 has no local return
+union_paren.erl:45: The call union_paren:c1({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::integer() | pid()}) -> atom()
+union_paren.erl:51: Function c2/0 has no local return
+union_paren.erl:52: The call union_paren:c2({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::A::integer() | pid()}) -> atom()
+union_paren.erl:58: Function c3/0 has no local return
+union_paren.erl:59: The call union_paren:c3({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::(A::integer()) | (B::pid())}) -> atom()
+union_paren.erl:65: Function c4/0 has no local return
+union_paren.erl:66: The call union_paren:c4({'r0', 'a', 'undefined', 'undefined'}) breaks the contract (#r0{f1::X::(A::integer()) | (B::pid())}) -> atom()
+union_paren.erl:72: Function c5/0 has no local return
+union_paren.erl:73: The call union_paren:c5({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::[integer()] | [pid()]}) -> atom()
+union_paren.erl:79: Function c6/0 has no local return
+union_paren.erl:80: The call union_paren:c6({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::A::[integer()] | [pid()]}) -> atom()
+union_paren.erl:86: Function c7/0 has no local return
+union_paren.erl:87: The call union_paren:c7({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::(A::[integer()]) | (B::[pid()])}) -> atom()
+union_paren.erl:93: Function c8/0 has no local return
+union_paren.erl:94: The call union_paren:c8({'r1', ['a'], [1], ['a'], ['u']}) breaks the contract (#r1{f1::X::(A::[integer()]) | (B::[pid()])}) -> atom()
diff --git a/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl b/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl
index 4691a57d98..65bda1876e 100644
--- a/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl
+++ b/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl
@@ -2,6 +2,24 @@
-compile(export_all).
+-record(r0,
+ {
+ f1 = 4 :: atom () | integer() | pid(),
+ f2 :: atom() | integer() | pid(),
+ f3 :: A :: atom() | integer() | pid
+ }).
+
+-record(r1,
+ {
+ f1 = [4] :: [atom ()] | [integer()] | [pid()],
+ f2 :: [atom()] | [integer()] | [pid()],
+ f3 :: A :: [atom()] | [integer()] | [pid()],
+ f8 = [u] :: X :: [A :: atom()] | [B :: integer()] | (C :: [pid()])
+ }).
+
+r1() ->
+ #r1{f8 = 3.14}.
+
t1() ->
t1(3.14).
@@ -22,3 +40,59 @@ t3() ->
-spec t3(_) -> (I :: integer()) | (A :: atom()).
t3(A) when is_atom(A) -> A;
t3(I) when is_integer(I) -> I.
+
+c1() ->
+ c1(#r0{f1 = a}).
+
+-spec c1(#r0{f1 :: integer() | pid()}) -> atom().
+c1(_) ->
+ a.
+
+c2() ->
+ c2(#r0{f1 = a}).
+
+-spec c2(#r0{f1 :: A :: integer() | pid()}) -> atom().
+c2(_) ->
+ a.
+
+c3() ->
+ c3(#r0{f1 = a}).
+
+-spec c3(#r0{f1 :: (A :: integer()) | (B :: pid())}) -> atom().
+c3(_) ->
+ a.
+
+c4() ->
+ c4(#r0{f1 = a}).
+
+-spec c4(#r0{f1 :: X :: (A :: integer()) | (B :: pid())}) -> atom().
+c4(_) ->
+ a.
+
+c5() ->
+ c5(#r1{f1 = [a], f2 = [1], f3 = [a]}).
+
+-spec c5(#r1{f1 :: [integer()] | [pid()]}) -> atom().
+c5(_) ->
+ a.
+
+c6() ->
+ c6(#r1{f1 = [a], f2 = [1], f3 = [a]}).
+
+-spec c6(#r1{f1 :: A :: [integer()] | [pid()]}) -> atom().
+c6(_) ->
+ a.
+
+c7() ->
+ c7(#r1{f1 = [a], f2 = [1], f3 = [a]}).
+
+-spec c7(#r1{f1 :: (A :: [integer()]) | (B :: [pid()])}) -> atom().
+c7(_) ->
+ a.
+
+c8() ->
+ c8(#r1{f1 = [a], f2 = [1], f3 = [a]}).
+
+-spec c8(#r1{f1 :: X :: (A :: [integer()]) | (B :: [pid()])}) -> atom().
+c8(_) ->
+ a.
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index 95984c7c85..7a44daf683 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 4.0
+DIALYZER_VSN = 4.0.2
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 03bd1d8042..45533c4f4b 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,23 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.0.8</title>
+ <section><title>Inets 7.0.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix a regression in http client that causes a crash when
+ request URI has no scheme.</p>
+ <p>
+ Own Id: OTP-15930 Aux Id: ERL-969 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.0.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/src/http_client/httpc.erl b/lib/inets/src/http_client/httpc.erl
index 24a205ced9..9967488f61 100644
--- a/lib/inets/src/http_client/httpc.erl
+++ b/lib/inets/src/http_client/httpc.erl
@@ -317,7 +317,7 @@ store_cookies(SetCookieHeaders, Url, Profile)
{error, Bad, _} ->
{error, {parse_failed, Bad}};
URI ->
- Scheme = scheme_to_atom(maps:get(scheme, URI, '')),
+ Scheme = scheme_to_atom(maps:get(scheme, URI, undefined)),
Host = maps:get(host, URI, ""),
Port = maps:get(port, URI, default_port(Scheme)),
Path = uri_string:recompose(#{path => maps:get(path, URI, "")}),
@@ -536,7 +536,7 @@ handle_request(Method, Url,
BracketedHost = proplists:get_value(ipv6_host_with_brackets,
Options),
- Scheme = scheme_to_atom(maps:get(scheme, URI, '')),
+ Scheme = scheme_to_atom(maps:get(scheme, URI, undefined)),
Userinfo = maps:get(userinfo, URI, ""),
Host = http_util:maybe_add_brackets(maps:get(host, URI, ""), BracketedHost),
Port = maps:get(port, URI, default_port(Scheme)),
@@ -591,8 +591,8 @@ scheme_to_atom("http") ->
http;
scheme_to_atom("https") ->
https;
-scheme_to_atom('') ->
- '';
+scheme_to_atom(undefined) ->
+ throw({error, {no_scheme}});
scheme_to_atom(Scheme) ->
throw({error, {bad_scheme, Scheme}}).
diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl
index d4b33ae2c6..1d37e71847 100644
--- a/lib/inets/test/httpc_SUITE.erl
+++ b/lib/inets/test/httpc_SUITE.erl
@@ -106,7 +106,8 @@ real_requests()->
streaming_error,
inet_opts,
invalid_headers,
- invalid_body
+ invalid_body,
+ no_scheme
].
real_requests_esi() ->
@@ -1231,6 +1232,16 @@ invalid_body(Config) ->
ok
end.
+
+%%-------------------------------------------------------------------------
+
+no_scheme(_Config) ->
+ {error,{bad_scheme,"ftp"}} = httpc:request("ftp://foobar"),
+ {error,{no_scheme}} = httpc:request("//foobar"),
+ {error,{no_scheme}} = httpc:request("foobar"),
+ ok.
+
+
%%-------------------------------------------------------------------------
remote_socket_close(Config) when is_list(Config) ->
URL = url(group_name(Config), "/just_close.html", Config),
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index 5dbec9e7b3..d948204618 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.0.8
+INETS_VSN = 7.0.9
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 6f68a67174..b065cc2cd8 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,24 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 6.4.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p><c>user</c>/<c>user_drv</c> could respond to io
+ requests before they had been processed, which could
+ cause data to be dropped if the emulator was halted soon
+ after a call to <c>io:format/2</c>, such as in an
+ escript.</p>
+ <p>
+ Own Id: OTP-15805</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index cd0397a98c..95853a7a8f 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -38,7 +38,9 @@
{<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.3$">>,[restart_new_emulator]},
{<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.4$">>,[restart_new_emulator]},
+ {<<"^6\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
[{<<"^6\\.0$">>,[restart_new_emulator]},
{<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -50,4 +52,6 @@
{<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.3$">>,[restart_new_emulator]},
{<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^6\\.4$">>,[restart_new_emulator]},
+ {<<"^6\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/src/user.erl b/lib/kernel/src/user.erl
index 0c9e1ea303..5a3487a9ba 100644
--- a/lib/kernel/src/user.erl
+++ b/lib/kernel/src/user.erl
@@ -296,7 +296,8 @@ io_requests([], Stat, _) ->
%% port.
put_port(List, Port) ->
- send_port(Port, {command, List}).
+ true = port_command(Port, List),
+ ok.
%% send_port(Port, Command)
diff --git a/lib/kernel/src/user_drv.erl b/lib/kernel/src/user_drv.erl
index 69ff8e7971..644aa752b6 100644
--- a/lib/kernel/src/user_drv.erl
+++ b/lib/kernel/src/user_drv.erl
@@ -543,19 +543,14 @@ set_unicode_state(Iport, Bool) ->
%% io_request(Request, InPort, OutPort)
%% io_requests(Requests, InPort, OutPort)
%% Note: InPort is unused.
-
-io_request(Request, Iport, Oport) ->
- try io_command(Request) of
- {command,_} = Command ->
- Oport ! {self(),Command},
- ok;
- {Command,Reply} ->
- Oport ! {self(),Command},
- Reply
- catch
- {requests,Rs} ->
- io_requests(Rs, Iport, Oport);
- _ ->
+io_request({requests,Rs}, Iport, Oport) ->
+ io_requests(Rs, Iport, Oport);
+io_request(Request, _Iport, Oport) ->
+ case io_command(Request) of
+ {Data, Reply} ->
+ true = port_command(Oport, Data),
+ Reply;
+ unhandled ->
ok
end.
@@ -575,19 +570,19 @@ put_int16(N, Tail) ->
%% to the console before the vm stops when calling erlang:halt(integer()).
-dialyzer({no_improper_lists, io_command/1}).
io_command({put_chars_sync, unicode,Cs,Reply}) ->
- {{command,[?OP_PUTC_SYNC|unicode:characters_to_binary(Cs,utf8)]},Reply};
+ {[?OP_PUTC_SYNC|unicode:characters_to_binary(Cs,utf8)], Reply};
io_command({put_chars, unicode,Cs}) ->
- {command,[?OP_PUTC|unicode:characters_to_binary(Cs,utf8)]};
+ {[?OP_PUTC|unicode:characters_to_binary(Cs,utf8)], ok};
io_command({move_rel,N}) ->
- {command,[?OP_MOVE|put_int16(N, [])]};
+ {[?OP_MOVE|put_int16(N, [])], ok};
io_command({insert_chars,unicode,Cs}) ->
- {command,[?OP_INSC|unicode:characters_to_binary(Cs,utf8)]};
+ {[?OP_INSC|unicode:characters_to_binary(Cs,utf8)], ok};
io_command({delete_chars,N}) ->
- {command,[?OP_DELC|put_int16(N, [])]};
+ {[?OP_DELC|put_int16(N, [])], ok};
io_command(beep) ->
- {command,[?OP_BEEP]};
-io_command(Else) ->
- throw(Else).
+ {[?OP_BEEP], ok};
+io_command(_) ->
+ unhandled.
%% gr_new()
%% gr_get_num(Group, Index)
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index 765e890157..e5188aa9b5 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 6.4
+KERNEL_VSN = 6.4.1
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index 5fdcf15b5f..bb41b7e9ea 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,114 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<section><title>SSL 9.3.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix handling of certificate decoding problems in TLS 1.3
+ similarly as in TLS 1.2.</p>
+ <p>
+ Own Id: OTP-15900</p>
+ </item>
+ <item>
+ <p>
+ Hibernation now works as expected in all cases, was
+ accidently broken by optimization efforts.</p>
+ <p>
+ Own Id: OTP-15910</p>
+ </item>
+ <item>
+ <p>
+ Fix interoperability problems with openssl when the TLS
+ 1.3 server is configured wirh the option
+ signature_algs_cert.</p>
+ <p>
+ Own Id: OTP-15913</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.3.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Correct handshake handling, might cause strange symptoms
+ such as ASN.1 certificate decoding issues.</p>
+ <p>
+ Own Id: OTP-15879 Aux Id: ERL-968 </p>
+ </item>
+ <item>
+ <p>
+ Fix handling of the signature_algorithms_cert extension
+ in the ClientHello handshake message.</p>
+ <p>
+ Own Id: OTP-15887 Aux Id: ERL-973 </p>
+ </item>
+ <item>
+ <p>
+ Handle new ClientHello extensions when handshake is
+ paused by the {handshake, hello} ssl option.</p>
+ <p>
+ Own Id: OTP-15888 Aux Id: ERL-975 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Returned "alert error string" is now same as logged alert
+ string</p>
+ <p>
+ Own Id: OTP-15844</p>
+ </item>
+ <item>
+ <p>
+ Fix returned extension map fields to follow the
+ documentation.</p>
+ <p>
+ Own Id: OTP-15862 Aux Id: ERL-951 </p>
+ </item>
+ <item>
+ <p>
+ Avoid DTLS crash due to missing gen_server return value
+ in DTLS packet demux process.</p>
+ <p>
+ Own Id: OTP-15864 Aux Id: ERL-962 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.3.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Missing check of size of user_data_buffer made internal
+ socket behave as an active socket instead of active N.
+ This could cause memory problems.</p>
+ <p>
+ Own Id: OTP-15825 Aux Id: ERL-934, OTP-15823 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/src/dtls_connection.erl b/lib/ssl/src/dtls_connection.erl
index e070006900..6928d7a93d 100644
--- a/lib/ssl/src/dtls_connection.erl
+++ b/lib/ssl/src/dtls_connection.erl
@@ -51,7 +51,7 @@
-export([encode_alert/3, send_alert/2, send_alert_in_connection/2, close/5, protocol_name/0]).
%% Data handling
--export([next_record/1, socket/4, setopts/3, getopts/3]).
+-export([socket/4, setopts/3, getopts/3]).
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
@@ -451,11 +451,11 @@ init({call, From}, {start, Timeout},
HelloVersion = dtls_record:hello_version(Version, SslOpts#ssl_options.versions),
State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
{State2, Actions} = send_handshake(Hello, State1#state{connection_env = CEnv#connection_env{negotiated_version = HelloVersion}}),
- State3 = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
+ State = State2#state{connection_env = CEnv#connection_env{negotiated_version = Version}, %% RequestedVersion
session =
Session0#session{session_id = Hello#client_hello.session_id},
start_or_recv_from = From},
- next_event(hello, no_record, State3, [{{timeout, handshake}, Timeout, close} | Actions]);
+ next_event(hello, no_record, State, [{{timeout, handshake}, Timeout, close} | Actions]);
init({call, _} = Type, Event, #state{static_env = #static_env{role = server},
protocol_specific = PS} = State) ->
Result = gen_handshake(?FUNCTION_NAME, Type, Event,
@@ -514,7 +514,7 @@ hello(internal, #client_hello{cookie = <<>>,
VerifyRequest = dtls_handshake:hello_verify_request(Cookie, ?HELLO_VERIFY_REQUEST_VERSION),
State1 = prepare_flight(State0#state{connection_env = CEnv#connection_env{negotiated_version = Version}}),
{State, Actions} = send_handshake(VerifyRequest, State1),
- next_event(?FUNCTION_NAME, no_record,
+ next_event(?FUNCTION_NAME, no_record,
State#state{handshake_env = HsEnv#handshake_env{
tls_handshake_history =
ssl_handshake:init_handshake_history()}},
diff --git a/lib/ssl/src/dtls_packet_demux.erl b/lib/ssl/src/dtls_packet_demux.erl
index c6431b55a9..94b350eaa5 100644
--- a/lib/ssl/src/dtls_packet_demux.erl
+++ b/lib/ssl/src/dtls_packet_demux.erl
@@ -154,9 +154,9 @@ handle_info({Transport, Socket, IP, InPortNo, _} = Msg, #state{listener = Socket
handle_info({PassiveTag, Socket},
#state{active_n = N,
listener = Socket,
- transport = {_,_,_, udp_error, PassiveTag}}) ->
- next_datagram(Socket, N);
-
+ transport = {_, _, _, _, PassiveTag}} = State) ->
+ next_datagram(Socket, N),
+ {noreply, State};
%% UDP socket does not have a connection and should not receive an econnreset
%% This does however happens on some windows versions. Just ignoring it
%% appears to make things work as expected!
diff --git a/lib/ssl/src/ssl_alert.erl b/lib/ssl/src/ssl_alert.erl
index 06b1b005a5..2d57b72f7b 100644
--- a/lib/ssl/src/ssl_alert.erl
+++ b/lib/ssl/src/ssl_alert.erl
@@ -32,7 +32,11 @@
-include("ssl_record.hrl").
-include("ssl_internal.hrl").
--export([decode/1, own_alert_txt/1, alert_txt/1, reason_code/2]).
+-export([decode/1,
+ own_alert_txt/1,
+ alert_txt/1,
+ alert_txt/4,
+ reason_code/4]).
%%====================================================================
%% Internal application API
@@ -48,20 +52,29 @@ decode(Bin) ->
decode(Bin, [], 0).
%%--------------------------------------------------------------------
-%% -spec reason_code(#alert{}, client | server) ->
-%% {tls_alert, unicode:chardata()} | closed.
-%-spec reason_code(#alert{}, client | server) -> closed | {essl, string()}.
+-spec reason_code(#alert{}, client | server, ProtocolName::string(), StateName::atom()) ->
+ {tls_alert, {atom(), unicode:chardata()}} | closed.
%%
%% Description: Returns the error reason that will be returned to the
%% user.
%%--------------------------------------------------------------------
-reason_code(#alert{description = ?CLOSE_NOTIFY}, _) ->
+reason_code(#alert{description = ?CLOSE_NOTIFY}, _, _, _) ->
closed;
-reason_code(#alert{description = Description, role = Role} = Alert, Role) ->
- {tls_alert, {description_atom(Description), own_alert_txt(Alert)}};
-reason_code(#alert{description = Description} = Alert, Role) ->
- {tls_alert, {description_atom(Description), alert_txt(Alert#alert{role = Role})}}.
+reason_code(#alert{description = Description, role = Role} = Alert, Role, ProtocolName, StateName) ->
+ Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, own_alert_txt(Alert))),
+ {tls_alert, {description_atom(Description), Txt}};
+reason_code(#alert{description = Description} = Alert, Role, ProtocolName, StateName) ->
+ Txt = lists:flatten(alert_txt(ProtocolName, Role, StateName, alert_txt(Alert))),
+ {tls_alert, {description_atom(Description), Txt}}.
+
+%%--------------------------------------------------------------------
+-spec alert_txt(string(), server | client, StateNam::atom(), string()) -> string().
+%%
+%% Description: Generates alert text for log or string part of error return.
+%%--------------------------------------------------------------------
+alert_txt(ProtocolName, Role, StateName, Txt) ->
+ io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]).
%%--------------------------------------------------------------------
-spec own_alert_txt(#alert{}) -> string().
diff --git a/lib/ssl/src/ssl_cipher.erl b/lib/ssl/src/ssl_cipher.erl
index 21db887bb5..4da50d2af8 100644
--- a/lib/ssl/src/ssl_cipher.erl
+++ b/lib/ssl/src/ssl_cipher.erl
@@ -923,6 +923,12 @@ signature_scheme(rsa_pss_pss_sha384) -> ?RSA_PSS_PSS_SHA384;
signature_scheme(rsa_pss_pss_sha512) -> ?RSA_PSS_PSS_SHA512;
signature_scheme(rsa_pkcs1_sha1) -> ?RSA_PKCS1_SHA1;
signature_scheme(ecdsa_sha1) -> ?ECDSA_SHA1;
+%% Handling legacy signature algorithms
+signature_scheme({Hash0, Sign0}) ->
+ Hash = hash_algorithm(Hash0),
+ Sign = sign_algorithm(Sign0),
+ <<?UINT16(SigAlg)>> = <<?BYTE(Hash),?BYTE(Sign)>>,
+ SigAlg;
signature_scheme(?RSA_PKCS1_SHA256) -> rsa_pkcs1_sha256;
signature_scheme(?RSA_PKCS1_SHA384) -> rsa_pkcs1_sha384;
signature_scheme(?RSA_PKCS1_SHA512) -> rsa_pkcs1_sha512;
@@ -962,7 +968,9 @@ scheme_to_components(rsa_pss_pss_sha256) -> {sha256, rsa_pss_pss, undefined};
scheme_to_components(rsa_pss_pss_sha384) -> {sha384, rsa_pss_pss, undefined};
scheme_to_components(rsa_pss_pss_sha512) -> {sha512, rsa_pss_pss, undefined};
scheme_to_components(rsa_pkcs1_sha1) -> {sha1, rsa_pkcs1, undefined};
-scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined}.
+scheme_to_components(ecdsa_sha1) -> {sha1, ecdsa, undefined};
+%% Handling legacy signature algorithms
+scheme_to_components({Hash,Sign}) -> {Hash, Sign, undefined}.
%% TODO: Add support for EC and RSA-SSA signatures
diff --git a/lib/ssl/src/ssl_connection.erl b/lib/ssl/src/ssl_connection.erl
index a5f754d2e3..a8cb9ea815 100644
--- a/lib/ssl/src/ssl_connection.erl
+++ b/lib/ssl/src/ssl_connection.erl
@@ -124,7 +124,7 @@ handshake(#sslsocket{pid = [Pid|_]} = Socket, Timeout) ->
connected ->
{ok, Socket};
{ok, Ext} ->
- {ok, Socket, Ext};
+ {ok, Socket, no_records(Ext)};
Error ->
Error
end.
@@ -328,34 +328,33 @@ prf(ConnectionPid, Secret, Label, Seed, WantedLength) ->
%%====================================================================
%% Alert and close handling
%%====================================================================
-handle_own_alert(Alert, _, StateName,
+handle_own_alert(Alert0, _, StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
ssl_options = SslOpts} = State) ->
try %% Try to tell the other side
- send_alert(Alert, StateName, State)
+ send_alert(Alert0, StateName, State)
catch _:_ -> %% Can crash if we are in a uninitialized state
ignore
end,
try %% Try to tell the local user
- log_alert(SslOpts#ssl_options.log_level, Role,
- Connection:protocol_name(), StateName,
- Alert#alert{role = Role}),
+ Alert = Alert0#alert{role = Role},
+ log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(), StateName, Alert),
handle_normal_shutdown(Alert,StateName, State)
catch _:_ ->
ok
end,
{stop, {shutdown, own_alert}, State}.
-handle_normal_shutdown(Alert, _, #state{static_env = #static_env{role = Role,
- socket = Socket,
- transport_cb = Transport,
- protocol_cb = Connection,
- tracker = Tracker},
- handshake_env = #handshake_env{renegotiation = {false, first}},
- start_or_recv_from = StartFrom} = State) ->
+handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
+ socket = Socket,
+ transport_cb = Transport,
+ protocol_cb = Connection,
+ tracker = Tracker},
+ handshake_env = #handshake_env{renegotiation = {false, first}},
+ start_or_recv_from = StartFrom} = State) ->
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, Connection);
+ alert_user(Pids, Transport, Tracker,Socket, StartFrom, Alert, Role, StateName, Connection);
handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role = Role,
socket = Socket,
@@ -366,9 +365,9 @@ handle_normal_shutdown(Alert, StateName, #state{static_env = #static_env{role =
socket_options = Opts,
start_or_recv_from = RecvFrom} = State) ->
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, Connection).
+ alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, RecvFrom, Alert, Role, StateName, Connection).
-handle_alert(#alert{level = ?FATAL} = Alert, StateName,
+handle_alert(#alert{level = ?FATAL} = Alert0, StateName,
#state{static_env = #static_env{role = Role,
socket = Socket,
host = Host,
@@ -382,11 +381,11 @@ handle_alert(#alert{level = ?FATAL} = Alert, StateName,
session = Session,
socket_options = Opts} = State) ->
invalidate_session(Role, Host, Port, Session),
+ Alert = Alert0#alert{role = opposite_role(Role)},
log_alert(SslOpts#ssl_options.log_level, Role, Connection:protocol_name(),
- StateName, Alert#alert{role = opposite_role(Role)}),
+ StateName, Alert),
Pids = Connection:pids(State),
- alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert,
- opposite_role(Role), Connection),
+ alert_user(Pids, Transport, Tracker, Socket, StateName, Opts, Pid, From, Alert, Role, StateName, Connection),
{stop, {shutdown, normal}, State};
handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
@@ -396,13 +395,14 @@ handle_alert(#alert{level = ?WARNING, description = ?CLOSE_NOTIFY} = Alert,
StateName, State) ->
handle_normal_shutdown(Alert, StateName, State),
{stop,{shutdown, peer_close}, State};
-handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert, StateName,
+handle_alert(#alert{level = ?WARNING, description = ?NO_RENEGOTIATION} = Alert0, StateName,
#state{static_env = #static_env{role = Role,
protocol_cb = Connection},
handshake_env = #handshake_env{renegotiation = {true, internal}},
ssl_options = SslOpts} = State) ->
+ Alert = Alert0#alert{role = opposite_role(Role)},
log_alert(SslOpts#ssl_options.log_level, Role,
- Connection:protocol_name(), StateName, Alert#alert{role = opposite_role(Role)}),
+ Connection:protocol_name(), StateName, Alert),
handle_normal_shutdown(Alert, StateName, State),
{stop,{shutdown, peer_close}, State};
@@ -709,6 +709,7 @@ handle_session(#server_hello{cipher_suite = CipherSuite,
{ExpectNPN, Protocol} = case Protocol0 of
undefined ->
+
{false, CurrentProtocol};
_ ->
{ProtoExt =:= npn, Protocol0}
@@ -1445,7 +1446,7 @@ handle_info({ErrorTag, Socket, econnaborted}, StateName,
} = State) when StateName =/= connection ->
Pids = Connection:pids(State),
alert_user(Pids, Transport, Tracker,Socket,
- StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, Connection),
+ StartFrom, ?ALERT_REC(?FATAL, ?CLOSE_NOTIFY), Role, StateName, Connection),
{stop, {shutdown, normal}, State};
handle_info({ErrorTag, Socket, Reason}, StateName, #state{static_env = #static_env{socket = Socket,
@@ -2907,22 +2908,22 @@ send_user(Pid, Msg) ->
Pid ! Msg,
ok.
-alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, Connection) ->
- alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, Connection);
-alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, Connection) ->
- alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection).
+alert_user(Pids, Transport, Tracker, Socket, connection, Opts, Pid, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Tracker, Socket, Opts#socket_options.active, Pid, From, Alert, Role, StateName, Connection);
+alert_user(Pids, Transport, Tracker, Socket,_, _, _, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection).
-alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, Connection) ->
- alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, Connection).
+alert_user(Pids, Transport, Tracker, Socket, From, Alert, Role, StateName, Connection) ->
+ alert_user(Pids, Transport, Tracker, Socket, false, no_pid, From, Alert, Role, StateName, Connection).
-alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, _) when From =/= undefined ->
+alert_user(_, _, _, _, false = Active, Pid, From, Alert, Role, StateName, Connection) when From =/= undefined ->
%% If there is an outstanding ssl_accept | recv
%% From will be defined and send_or_reply will
%% send the appropriate error message.
- ReasonCode = ssl_alert:reason_code(Alert, Role),
+ ReasonCode = ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName),
send_or_reply(Active, Pid, From, {error, ReasonCode});
-alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Connection) ->
- case ssl_alert:reason_code(Alert, Role) of
+alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, StateName, Connection) ->
+ case ssl_alert:reason_code(Alert, Role, Connection:protocol_name(), StateName) of
closed ->
send_or_reply(Active, Pid, From,
{ssl_closed, Connection:socket(Pids, Transport, Socket, Tracker)});
@@ -2933,11 +2934,11 @@ alert_user(Pids, Transport, Tracker, Socket, Active, Pid, From, Alert, Role, Con
log_alert(Level, Role, ProtocolName, StateName, #alert{role = Role} = Alert) ->
Txt = ssl_alert:own_alert_txt(Alert),
- Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]),
+ Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt),
ssl_logger:notice(Level, Report);
log_alert(Level, Role, ProtocolName, StateName, Alert) ->
Txt = ssl_alert:alert_txt(Alert),
- Report = io_lib:format("~s ~p: In state ~p ~s\n", [ProtocolName, Role, StateName, Txt]),
+ Report = ssl_alert:alert_txt(ProtocolName, Role, StateName, Txt),
ssl_logger:notice(Level, Report).
invalidate_session(client, Host, Port, Session) ->
@@ -3000,3 +3001,8 @@ new_emulated([], EmOpts) ->
EmOpts;
new_emulated(NewEmOpts, _) ->
NewEmOpts.
+
+no_records(Extensions) ->
+ maps:map(fun(_, Value) ->
+ ssl_handshake:extension_value(Value)
+ end, Extensions).
diff --git a/lib/ssl/src/ssl_handshake.erl b/lib/ssl/src/ssl_handshake.erl
index 7b34991f4f..fbed7258c6 100644
--- a/lib/ssl/src/ssl_handshake.erl
+++ b/lib/ssl/src/ssl_handshake.erl
@@ -98,8 +98,8 @@ hello_request() ->
#hello_request{}.
%%--------------------------------------------------------------------
--spec server_hello(binary(), ssl_record:ssl_version(), ssl_record:connection_states(),
- Extension::map()) -> #server_hello{}.
+%%-spec server_hello(binary(), ssl_record:ssl_version(), ssl_record:connection_states(),
+%% Extension::map()) -> #server_hello{}.
%%
%% Description: Creates a server hello message.
%%--------------------------------------------------------------------
@@ -363,7 +363,7 @@ certify(#certificate{asn1_certificates = ASN1Certs}, CertDbHandle, CertDbRef,
CertDbHandle, CertDbRef)
end
catch
- error:{badmatch,{asn1, Asn1Reason}} ->
+ error:{badmatch,{error, {asn1, Asn1Reason}}} ->
%% ASN-1 decode of certificate somehow failed
?ALERT_REC(?FATAL, ?CERTIFICATE_UNKNOWN, {failed_to_decode_certificate, Asn1Reason});
error:OtherReason ->
@@ -1186,10 +1186,7 @@ signature_algs_ext(undefined) ->
signature_algs_ext(SignatureSchemes0) ->
%% The SSL option signature_algs contains both hash-sign algorithms (tuples) and
%% signature schemes (atoms) if TLS 1.3 is configured.
- %% Filter out all hash-sign tuples when creating the signature_algs extension.
- %% (TLS 1.3 specific record type)
- SignatureSchemes = lists:filter(fun is_atom/1, SignatureSchemes0),
- #signature_algorithms{signature_scheme_list = SignatureSchemes}.
+ #signature_algorithms{signature_scheme_list = SignatureSchemes0}.
signature_algs_cert(undefined) ->
undefined;
@@ -1474,7 +1471,16 @@ extension_value(#next_protocol_negotiation{extension_data = Data}) ->
extension_value(#srp{username = Name}) ->
Name;
extension_value(#renegotiation_info{renegotiated_connection = Data}) ->
- Data.
+ Data;
+extension_value(#signature_algorithms{signature_scheme_list = Schemes}) ->
+ Schemes;
+extension_value(#signature_algorithms_cert{signature_scheme_list = Schemes}) ->
+ Schemes;
+extension_value(#key_share_client_hello{client_shares = ClientShares}) ->
+ ClientShares;
+extension_value(#client_hello_versions{versions = Versions}) ->
+ Versions.
+
%%--------------------------------------------------------------------
%%% Internal functions
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index a05858221a..24bc508a10 100644
--- a/lib/ssl/src/tls_connection.erl
+++ b/lib/ssl/src/tls_connection.erl
@@ -62,7 +62,7 @@
close/5, protocol_name/0]).
%% Data handling
--export([next_record/1, socket/4, setopts/3, getopts/3]).
+-export([socket/4, setopts/3, getopts/3]).
%% gen_statem state functions
-export([init/3, error/3, downgrade/3, %% Initiation and take down states
@@ -161,32 +161,60 @@ pids(#state{protocol_specific = #{sender := Sender}}) ->
%%====================================================================
%% State transition handling
%%====================================================================
-next_record(#state{handshake_env =
+next_record(_, #state{handshake_env =
#handshake_env{unprocessed_handshake_events = N} = HsEnv}
= State) when N > 0 ->
{no_record, State#state{handshake_env =
HsEnv#handshake_env{unprocessed_handshake_events = N-1}}};
-next_record(#state{protocol_buffers =
- #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts},
- connection_states = ConnectionStates,
- ssl_options = #ssl_options{padding_check = Check}} = State) ->
+next_record(_, #state{protocol_buffers =
+ #protocol_buffers{tls_cipher_texts = [_|_] = CipherTexts},
+ connection_states = ConnectionStates,
+ ssl_options = #ssl_options{padding_check = Check}} = State) ->
next_record(State, CipherTexts, ConnectionStates, Check);
-next_record(#state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
- protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
- static_env = #static_env{socket = Socket,
- close_tag = CloseTag,
- transport_cb = Transport}
- } = State) ->
- case tls_socket:setopts(Transport, Socket, [{active, N}]) of
- ok ->
- {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
- _ ->
- self() ! {CloseTag, Socket},
- {no_record, State}
- end;
-next_record(State) ->
+next_record(connection, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
+ protocol_specific = #{active_n_toggle := true}
+ } = State) ->
+ %% If ssl application user is not reading data wait to activate socket
+ flow_ctrl(State);
+
+next_record(_, #state{protocol_buffers = #protocol_buffers{tls_cipher_texts = []},
+ protocol_specific = #{active_n_toggle := true}
+ } = State) ->
+ activate_socket(State);
+next_record(_, State) ->
{no_record, State}.
+
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = undefined} = State) when Size =/= 0 ->
+ {no_record, State};
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = 0} = State) when Size =/= 0 ->
+ {no_record, State};
+flow_ctrl(#state{user_data_buffer = {_,Size,_},
+ socket_options = #socket_options{active = false},
+ bytes_to_read = BytesToRead} = State) when (Size >= BytesToRead) andalso
+ (BytesToRead > 0) ->
+ {no_record, State};
+flow_ctrl(State) ->
+ activate_socket(State).
+
+
+activate_socket(#state{protocol_specific = #{active_n_toggle := true, active_n := N} = ProtocolSpec,
+ static_env = #static_env{socket = Socket,
+ close_tag = CloseTag,
+ transport_cb = Transport}
+ } = State) ->
+ case tls_socket:setopts(Transport, Socket, [{active, N}]) of
+ ok ->
+ {no_record, State#state{protocol_specific = ProtocolSpec#{active_n_toggle => false}}};
+ _ ->
+ self() ! {CloseTag, Socket},
+ {no_record, State}
+ end.
+
%% Decipher next record and concatenate consecutive ?APPLICATION_DATA records into one
%%
next_record(State, CipherTexts, ConnectionStates, Check) ->
@@ -224,31 +252,20 @@ next_record_done(#state{protocol_buffers = Buffers} = State, CipherTexts, Connec
State#state{protocol_buffers = Buffers#protocol_buffers{tls_cipher_texts = CipherTexts},
connection_states = ConnectionStates}}.
-
next_event(StateName, Record, State) ->
next_event(StateName, Record, State, []).
%%
next_event(StateName, no_record, State0, Actions) ->
- case next_record(State0) of
+ case next_record(StateName, State0) of
{no_record, State} ->
- {next_state, StateName, State, Actions};
- {#ssl_tls{} = Record, State} ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- #alert{} = Alert ->
- Version = State0#state.connection_env#connection_env.negotiated_version,
- ssl_connection:handle_own_alert(Alert, Version, StateName, State0)
+ ssl_connection:hibernate_after(StateName, State, Actions);
+ {Record, State} ->
+ next_event(StateName, Record, State, Actions)
end;
-next_event(StateName, Record, State, Actions) ->
- case Record of
- no_record ->
- {next_state, StateName, State, Actions};
- #ssl_tls{} = Record ->
- {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
- #alert{} = Alert ->
- Version = State#state.connection_env#connection_env.negotiated_version,
- ssl_connection:handle_own_alert(Alert, Version, StateName, State)
- end.
-
+next_event(StateName, #ssl_tls{} = Record, State, Actions) ->
+ {next_state, StateName, State, [{next_event, internal, {protocol_record, Record}} | Actions]};
+next_event(StateName, #alert{} = Alert, State, Actions) ->
+ {next_state, StateName, State, [{next_event, internal, Alert} | Actions]}.
%%% TLS record protocol level application data messages
handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, StateName,
@@ -272,12 +289,8 @@ handle_protocol_record(#ssl_tls{type = ?APPLICATION_DATA, fragment = Data}, Stat
{stop, _, _} = Stop->
Stop;
{Record, State1} ->
- case next_event(StateName, Record, State1) of
- {next_state, StateName, State, Actions} ->
- ssl_connection:hibernate_after(StateName, State, Actions);
- {stop, _, _} = Stop ->
- Stop
- end
+ {next_state, StateName, State, Actions} = next_event(StateName, Record, State1),
+ ssl_connection:hibernate_after(StateName, State, Actions)
end;
%%% TLS record protocol level handshake messages
handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
@@ -303,8 +316,7 @@ handle_protocol_record(#ssl_tls{type = ?HANDSHAKE, fragment = Data},
_ ->
HsEnv = State#state.handshake_env,
{next_state, StateName,
- State#state{protocol_buffers = Buffers,
- handshake_env =
+ State#state{handshake_env =
HsEnv#handshake_env{unprocessed_handshake_events
= unprocessed_events(Events)}}, Events}
end
@@ -1049,7 +1061,7 @@ next_tls_record(Data, StateName,
case tls_record:get_tls_records(Data, Versions, Buf0, SslOpts) of
{Records, Buf1} ->
CT1 = CT0 ++ Records,
- next_record(State0#state{protocol_buffers =
+ next_record(StateName, State0#state{protocol_buffers =
Buffers#protocol_buffers{tls_record_buffer = Buf1,
tls_cipher_texts = CT1}});
#alert{} = Alert ->
diff --git a/lib/ssl/src/tls_connection_1_3.erl b/lib/ssl/src/tls_connection_1_3.erl
index 701a5860c2..76cdebc76f 100644
--- a/lib/ssl/src/tls_connection_1_3.erl
+++ b/lib/ssl/src/tls_connection_1_3.erl
@@ -116,9 +116,8 @@
]).
-start(internal, #change_cipher_spec{}, State0, _Module) ->
- {Record, State} = tls_connection:next_record(State0),
- tls_connection:next_event(?FUNCTION_NAME, Record, State);
+start(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
start(internal, #client_hello{} = Hello, State0, _Module) ->
case tls_handshake_1_3:do_start(Hello, State0) of
#alert{} = Alert ->
@@ -132,9 +131,8 @@ start(Type, Msg, State, Connection) ->
ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-negotiated(internal, #change_cipher_spec{}, State0, _Module) ->
- {Record, State} = tls_connection:next_record(State0),
- tls_connection:next_event(?FUNCTION_NAME, Record, State);
+negotiated(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
negotiated(internal, Message, State0, _Module) ->
case tls_handshake_1_3:do_negotiated(Message, State0) of
#alert{} = Alert ->
@@ -144,41 +142,36 @@ negotiated(internal, Message, State0, _Module) ->
end.
-wait_cert(internal, #change_cipher_spec{}, State0, _Module) ->
- {Record, State} = tls_connection:next_record(State0),
- tls_connection:next_event(?FUNCTION_NAME, Record, State);
+wait_cert(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
wait_cert(internal,
#certificate_1_3{} = Certificate, State0, _Module) ->
case tls_handshake_1_3:do_wait_cert(Certificate, State0) of
{#alert{} = Alert, State} ->
ssl_connection:handle_own_alert(Alert, {3,4}, wait_cert, State);
- {State1, NextState} ->
- {Record, State} = tls_connection:next_record(State1),
- tls_connection:next_event(NextState, Record, State)
+ {State, NextState} ->
+ tls_connection:next_event(NextState, no_record, State)
end;
wait_cert(Type, Msg, State, Connection) ->
ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-wait_cv(internal, #change_cipher_spec{}, State0, _Module) ->
- {Record, State} = tls_connection:next_record(State0),
- tls_connection:next_event(?FUNCTION_NAME, Record, State);
+wait_cv(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
wait_cv(internal,
#certificate_verify_1_3{} = CertificateVerify, State0, _Module) ->
case tls_handshake_1_3:do_wait_cv(CertificateVerify, State0) of
{#alert{} = Alert, State} ->
ssl_connection:handle_own_alert(Alert, {3,4}, wait_cv, State);
- {State1, NextState} ->
- {Record, State} = tls_connection:next_record(State1),
- tls_connection:next_event(NextState, Record, State)
+ {State, NextState} ->
+ tls_connection:next_event(NextState, no_record, State)
end;
wait_cv(Type, Msg, State, Connection) ->
ssl_connection:handle_common_event(Type, Msg, ?FUNCTION_NAME, State, Connection).
-wait_finished(internal, #change_cipher_spec{}, State0, _Module) ->
- {Record, State} = tls_connection:next_record(State0),
- tls_connection:next_event(?FUNCTION_NAME, Record, State);
+wait_finished(internal, #change_cipher_spec{}, State, _Module) ->
+ tls_connection:next_event(?FUNCTION_NAME, no_record, State);
wait_finished(internal,
#finished{} = Finished, State0, Module) ->
case tls_handshake_1_3:do_wait_finished(Finished, State0) of
diff --git a/lib/ssl/src/tls_handshake_1_3.erl b/lib/ssl/src/tls_handshake_1_3.erl
index 8a4ad922e1..53f9adbbd3 100644
--- a/lib/ssl/src/tls_handshake_1_3.erl
+++ b/lib/ssl/src/tls_handshake_1_3.erl
@@ -111,7 +111,7 @@ add_signature_algorithms_cert(Extensions, undefined) ->
Extensions;
add_signature_algorithms_cert(Extensions, SignAlgsCert) ->
Extensions#{signature_algorithms_cert =>
- #signature_algorithms{signature_scheme_list = SignAlgsCert}}.
+ #signature_algorithms_cert{signature_scheme_list = SignAlgsCert}}.
filter_tls13_algs(undefined) -> undefined;
@@ -802,7 +802,7 @@ validate_certificate_chain(Certs, CertDbHandle, CertDbRef, SslOptions, CRLDbHand
CertDbHandle, CertDbRef)
end
catch
- error:{badmatch,{asn1, Asn1Reason}} ->
+ error:{badmatch,{error, {asn1, Asn1Reason}}} ->
%% ASN-1 decode of certificate somehow failed
{error, {certificate_unknown, {failed_to_decode_certificate, Asn1Reason}}};
error:OtherReason ->
diff --git a/lib/ssl/src/tls_v1.erl b/lib/ssl/src/tls_v1.erl
index 27cd5765e5..f7c8c770ae 100644
--- a/lib/ssl/src/tls_v1.erl
+++ b/lib/ssl/src/tls_v1.erl
@@ -606,8 +606,26 @@ signature_schemes(Version, SignatureSchemes) when is_tuple(Version)
Acc
end;
%% Special clause for filtering out the legacy hash-sign tuples.
- (_ , Acc) ->
- Acc
+ ({Hash, dsa = Sign} = Alg, Acc) ->
+ case proplists:get_bool(dss, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Alg | Acc];
+ false ->
+ Acc
+ end;
+ ({Hash, Sign} = Alg, Acc) ->
+ case proplists:get_bool(Sign, PubKeys)
+ andalso proplists:get_bool(Hash, Hashes)
+ andalso is_pair(Hash, Sign, Hashes)
+ of
+ true ->
+ [Alg | Acc];
+ false ->
+ Acc
+ end
end,
Supported = lists:foldl(Fun, [], SignatureSchemes),
lists:reverse(Supported);
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 20d9f28512..f5ce3a2b65 100644
--- a/lib/ssl/test/ssl_basic_SUITE.erl
+++ b/lib/ssl/test/ssl_basic_SUITE.erl
@@ -164,6 +164,7 @@ api_tests() ->
prf,
socket_options,
active_n,
+ internal_active_1,
cipher_suites,
handshake_continue,
handshake_continue_timeout,
@@ -243,11 +244,13 @@ rizzo_tests() ->
%% For testing TLS 1.3 features and possible regressions
tls13_test_group() ->
- [tls13_enable_client_side,
+ [handshake_continue_tls13_client,
+ tls13_enable_client_side,
tls13_enable_server_side,
tls_record_1_3_encode_decode,
tls13_finished_verify_data,
tls13_1_RTT_handshake,
+ tls12_ssl_server_tls13_ssl_client,
tls13_basic_ssl_server_openssl_client,
tls13_custom_groups_ssl_server_openssl_client,
tls13_hello_retry_request_ssl_server_openssl_client,
@@ -488,6 +491,15 @@ init_per_testcase(accept_pool, Config) ->
ssl_test_lib:ct_log_supported_protocol_versions(Config),
Config
end;
+
+init_per_testcase(internal_active_1, Config) ->
+ ssl:stop(),
+ application:load(ssl),
+ application:set_env(ssl, internal_active_n, 1),
+ ssl:start(),
+ ct:timetrap({seconds, 5}),
+ Config;
+
init_per_testcase(controller_dies, Config) ->
ct:timetrap({seconds, 10}),
Config;
@@ -510,6 +522,10 @@ end_per_testcase(reuse_session_expired, Config) ->
application:unset_env(ssl, session_delay_cleanup_time),
end_per_testcase(default_action, Config);
+end_per_testcase(internal_active_n, Config) ->
+ application:unset_env(ssl, internal_active_n),
+ end_per_testcase(default_action, Config);
+
end_per_testcase(Case, Config) when Case == protocol_versions;
Case == empty_protocol_versions->
application:unset_env(ssl, protocol_versions),
@@ -657,6 +673,43 @@ handshake_continue(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+
+%%--------------------------------------------------------------------
+handshake_continue_tls13_client() ->
+ [{doc, "Test API function ssl:handshake_continue/3"}].
+handshake_continue_tls13_client(Config) when is_list(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_verify_opts, Config),
+ ServerOpts = ssl_test_lib:ssl_options(server_rsa_verify_opts, Config),
+ ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOpts0],
+
+ ClientOptsHello0 = ssl_test_lib:ssl_options([{handshake, hello}], Config),
+ ClientOptsHello = [{versions, ['tlsv1.2','tlsv1.3']}|ClientOptsHello0],
+
+ {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, ssl_test_lib:ssl_options([{reuseaddr, true}, {handshake, hello}],
+ Config)},
+ {continue_options, proplists:delete(reuseaddr, ServerOpts)}
+ ]),
+
+ Port = ssl_test_lib:inet_port(Server),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ClientOptsHello},
+ {continue_options, proplists:delete(reuseaddr, ClientOpts)}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
%%------------------------------------------------------------------
handshake_continue_timeout() ->
[{doc, "Test API function ssl:handshake_continue/3 with short timeout"}].
@@ -1975,6 +2028,10 @@ recv_active_once(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+
+
+
+
%%--------------------------------------------------------------------
recv_active_n() ->
[{doc,"Test recv on active (n) socket"}].
@@ -2001,6 +2058,7 @@ recv_active_n(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+
%%--------------------------------------------------------------------
%% Test case adapted from gen_tcp_misc_SUITE.
active_n() ->
@@ -2226,6 +2284,33 @@ upgrade_result(Socket) ->
ok
end.
+
+%%--------------------------------------------------------------------
+internal_active_1() ->
+ [{doc,"Test internal active 1 (behave as internal active once)"}].
+
+internal_active_1(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, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{active, true} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, [{active, true} | ClientOpts]}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
%%--------------------------------------------------------------------
tls_upgrade_with_timeout() ->
[{doc,"Test ssl_accept/3"}].
@@ -3575,7 +3660,7 @@ hibernate(Config) ->
ssl_test_lib:check_result(Server, ok, Client, ok),
- timer:sleep(1500),
+ ct:sleep(1500),
{current_function, {erlang, hibernate, 3}} =
process_info(Pid, current_function),
@@ -3611,6 +3696,8 @@ hibernate_right_away(Config) ->
[{port, Port1}, {options, [{hibernate_after, 0}|ClientOpts]}]),
ssl_test_lib:check_result(Server1, ok, Client1, ok),
+
+ ct:sleep(1000), %% Schedule out
{current_function, {erlang, hibernate, 3}} =
process_info(Pid1, current_function),
@@ -5305,6 +5392,41 @@ tls13_finished_verify_data(_Config) ->
FinishedKey = tls_v1:finished_key(BaseKey, sha256),
VerifyData = tls_v1:finished_verify_data(FinishedKey, sha256, Messages).
+
+tls12_ssl_server_tls13_ssl_client() ->
+ [{doc,"Test basic connection between TLS 1.2 server and TLS 1.3 client"}].
+
+tls12_ssl_server_tls13_ssl_client(Config) ->
+ ClientOpts0 = ssl_test_lib:ssl_options(client_rsa_opts, Config),
+ ServerOpts0 = ssl_test_lib:ssl_options(server_rsa_opts, Config),
+ {ClientNode, ServerNode, Hostname} = ssl_test_lib:run_where(Config),
+
+ %% Set versions
+ ServerOpts = [{versions, ['tlsv1.2']}|ServerOpts0],
+ ClientOpts = [{versions, ['tlsv1.2','tlsv1.3']},
+ {signature_algs_cert, [ecdsa_secp384r1_sha384,
+ rsa_pss_rsae_sha256,
+ rsa_pkcs1_sha256,
+ {sha256,rsa},{sha256,dsa}]}|ClientOpts0],
+
+ 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),
+
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, send_recv_result_active, []}},
+ {options, ClientOpts}]),
+
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close_port(Client).
+
+
tls13_basic_ssl_server_openssl_client() ->
[{doc,"Test TLS 1.3 basic connection between ssl server and openssl s_client"}].
@@ -5400,14 +5522,11 @@ tls13_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) ->
Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
- ssl_test_lib:check_result(Server,
- {error,
- {tls_alert,
- {certificate_required,
- "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}),
+ ssl_test_lib:check_server_alert(Server, certificate_required),
ssl_test_lib:close(Server),
ssl_test_lib:close_port(Client).
+
tls13_client_auth_empty_cert_ssl_server_openssl_client() ->
[{doc,"TLS 1.3: Test client authentication when client sends an empty certificate and fail_if_no_peer_cert is set to false."}].
@@ -5489,11 +5608,7 @@ tls13_hrr_client_auth_empty_cert_alert_ssl_server_openssl_client(Config) ->
Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
- ssl_test_lib:check_result(Server,
- {error,
- {tls_alert,
- {certificate_required,
- "received SERVER ALERT: Fatal - Certificate required - certificate_required"}}}),
+ ssl_test_lib:check_server_alert(Server, certificate_required),
ssl_test_lib:close(Server),
ssl_test_lib:close_port(Client).
@@ -5580,13 +5695,7 @@ tls13_unsupported_sign_algo_client_auth_ssl_server_openssl_client(Config) ->
Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
- ssl_test_lib:check_result(
- Server,
- {error,
- {tls_alert,
- {insufficient_security,
- "received SERVER ALERT: Fatal - Insufficient Security - "
- "\"No suitable signature algorithm\""}}}),
+ ssl_test_lib:check_server_alert(Server, insufficient_security),
ssl_test_lib:close(Server),
ssl_test_lib:close_port(Client).
@@ -5619,12 +5728,7 @@ tls13_unsupported_sign_algo_cert_client_auth_ssl_server_openssl_client(Config) -
Client = ssl_test_lib:start_basic_client(openssl, 'tlsv1.3', Port, ClientOpts),
- ssl_test_lib:check_result(
- Server,
- {error,
- {tls_alert,
- {illegal_parameter,
- "received CLIENT ALERT: Fatal - Illegal Parameter"}}}),
+ ssl_test_lib:check_server_alert(Server, certificate_required),
ssl_test_lib:close(Server),
ssl_test_lib:close_port(Client).
diff --git a/lib/ssl/test/ssl_certificate_verify_SUITE.erl b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
index 55dee9a48f..5431cda5af 100644
--- a/lib/ssl/test/ssl_certificate_verify_SUITE.erl
+++ b/lib/ssl/test/ssl_certificate_verify_SUITE.erl
@@ -89,7 +89,8 @@ tests() ->
critical_extension_verify_server,
critical_extension_verify_none,
customize_hostname_check,
- incomplete_chain
+ incomplete_chain,
+ long_chain
].
error_handling_tests()->
@@ -1157,6 +1158,44 @@ incomplete_chain(Config) when is_list(Config) ->
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+long_chain() ->
+ [{doc,"Test option verify_peer"}].
+long_chain(Config) when is_list(Config) ->
+ #{server_config := ServerConf,
+ client_config := ClientConf} = public_key:pkix_test_data(#{server_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(1)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}],
+ [{key, ssl_test_lib:hardcode_rsa_key(3)}],
+ [{key, ssl_test_lib:hardcode_rsa_key(4)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(5)}]},
+ client_chain => #{root => [{key, ssl_test_lib:hardcode_rsa_key(3)}],
+ intermediates => [[{key, ssl_test_lib:hardcode_rsa_key(2)}]],
+ peer => [{key, ssl_test_lib:hardcode_rsa_key(1)}]}}),
+ [ServerRoot| _] = ServerCas = proplists:get_value(cacerts, ServerConf),
+ ClientCas = proplists:get_value(cacerts, ClientConf),
+
+ Active = proplists:get_value(active, Config),
+ ReceiveFunction = proplists:get_value(receive_function, Config),
+ {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, ReceiveFunction, []}},
+ {options, [{active, Active}, {verify, verify_peer},
+ {cacerts, [ServerRoot]} |
+ proplists:delete(cacerts, ServerConf)]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client = ssl_test_lib:start_client([{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {ssl_test_lib, ReceiveFunction, []}},
+ {options, [{active, Active},
+ {verify, verify_peer},
+ {depth, 5},
+ {cacerts, ServerCas ++ ClientCas} |
+ proplists:delete(cacerts, ClientConf)]}]),
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
%%--------------------------------------------------------------------
%% Internal functions ------------------------------------------------
diff --git a/lib/ssl/test/ssl_payload_SUITE.erl b/lib/ssl/test/ssl_payload_SUITE.erl
index 27b9c258a0..2d0ffd03d7 100644
--- a/lib/ssl/test/ssl_payload_SUITE.erl
+++ b/lib/ssl/test/ssl_payload_SUITE.erl
@@ -48,21 +48,27 @@ groups() ->
payload_tests() ->
[server_echos_passive_small,
+ server_echos_passive_chunk_small,
server_echos_active_once_small,
server_echos_active_small,
client_echos_passive_small,
+ client_echos_passive_chunk_small,
client_echos_active_once_small,
client_echos_active_small,
server_echos_passive_big,
+ server_echos_passive_chunk_big,
server_echos_active_once_big,
server_echos_active_big,
client_echos_passive_big,
+ client_echos_passive_chunk_big,
client_echos_active_once_big,
client_echos_active_big,
server_echos_passive_huge,
+ server_echos_passive_chunk_huge,
server_echos_active_once_huge,
server_echos_active_huge,
client_echos_passive_huge,
+ client_echos_passive_chunk_huge,
client_echos_active_once_huge,
client_echos_active_huge,
client_active_once_server_close].
@@ -109,9 +115,11 @@ end_per_group(GroupName, Config) ->
init_per_testcase(TestCase, Config)
when TestCase == server_echos_passive_huge;
+ TestCase == server_echos_passive_chunk_huge;
TestCase == server_echos_active_once_huge;
TestCase == server_echos_active_huge;
TestCase == client_echos_passive_huge;
+ TestCase == client_echos_passive_chunk_huge;
TestCase == client_echos_active_once_huge;
TestCase == client_echos_active_huge ->
case erlang:system_info(system_architecture) of
@@ -124,9 +132,11 @@ init_per_testcase(TestCase, Config)
init_per_testcase(TestCase, Config)
when TestCase == server_echos_passive_big;
+ TestCase == server_echos_passive_chunk_big;
TestCase == server_echos_active_once_big;
TestCase == server_echos_active_big;
TestCase == client_echos_passive_big;
+ TestCase == client_echos_passive_chunk_big;
TestCase == client_echos_active_once_big;
TestCase == client_echos_active_big ->
ct:timetrap({seconds, 60}),
@@ -157,6 +167,22 @@ server_echos_passive_small(Config) when is_list(Config) ->
%%--------------------------------------------------------------------
+server_echos_passive_chunk_small() ->
+ [{doc, "Client sends 1000 bytes in passive mode to server, that receives them in chunks, "
+ "sends them back, and closes."}].
+
+server_echos_passive_chunk_small(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),
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ server_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+
+
+%%--------------------------------------------------------------------
+
server_echos_active_once_small() ->
[{doc, "Client sends 1000 bytes in active once mode to server, that receives "
" them, sends them back, and closes."}].
@@ -200,6 +226,21 @@ client_echos_passive_small(Config) when is_list(Config) ->
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
+client_echos_passive_chunk__small() ->
+ [{doc, "Server sends 1000 bytes in passive mode to client, that receives them in chunks, "
+ "sends them back, and closes."}].
+
+client_echos_passive_chunk_small(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),
+ %%
+ Data = binary:copy(<<"1234567890">>, 100),
+ client_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+
+
+%%--------------------------------------------------------------------
client_echos_active_once_small() ->
["Server sends 1000 bytes in active once mode to client, that receives "
"them, sends them back, and closes."].
@@ -241,6 +282,19 @@ server_echos_passive_big(Config) when is_list(Config) ->
Data = binary:copy(<<"1234567890">>, 5000),
server_echos_passive(
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+%%--------------------------------------------------------------------
+server_echos_passive_chunk_big() ->
+ [{doc, "Client sends 50000 bytes to server in passive mode, that receives them, "
+ "sends them back, and closes."}].
+
+server_echos_passive_chunk_big(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),
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ server_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
@@ -286,6 +340,22 @@ client_echos_passive_big(Config) when is_list(Config) ->
client_echos_passive(
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+
+%%--------------------------------------------------------------------
+client_echos_passive_chunk_big() ->
+ [{doc, "Server sends 50000 bytes to client in passive mode, that receives them, "
+ "sends them back, and closes."}].
+
+client_echos_passive_chunk_big(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),
+ %%
+ Data = binary:copy(<<"1234567890">>, 5000),
+ client_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+
+
%%--------------------------------------------------------------------
client_echos_active_once_big() ->
[{doc, "Server sends 50000 bytes to client in active once mode, that receives"
@@ -329,6 +399,20 @@ server_echos_passive_huge(Config) when is_list(Config) ->
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
+server_echos_passive_chunk_huge() ->
+ [{doc, "Client sends 500000 bytes to server in passive mode, that receives "
+ " them, sends them back, and closes."}].
+
+server_echos_passive_chunk_huge(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),
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ server_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+
+%%--------------------------------------------------------------------
server_echos_active_once_huge() ->
[{doc, "Client sends 500000 bytes to server in active once mode, that receives "
"them, sends them back, and closes."}].
@@ -369,7 +453,19 @@ client_echos_passive_huge(Config) when is_list(Config) ->
Data = binary:copy(<<"1234567890">>, 50000),
client_echos_passive(
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
+%%--------------------------------------------------------------------
+client_echos_passive_chunk_huge() ->
+ [{doc, "Server sends 500000 bytes to client in passive mode, that receives "
+ "them, sends them back, and closes."}].
+client_echos_passive_chunk_huge(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),
+ %%
+ Data = binary:copy(<<"1234567890">>, 50000),
+ client_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname).
%%--------------------------------------------------------------------
client_echos_active_once_huge() ->
[{doc, "Server sends 500000 bytes to client in active once mode, that receives "
@@ -442,6 +538,28 @@ server_echos_passive(
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+server_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, echoer_chunk, [Length]}},
+ {options, [{active, false}, {mode, binary} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, sender, [Data]}},
+ {options, [{active, false}, {mode, binary} | ClientOpts]}]),
+ %%
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ %%
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
server_echos_active_once(
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
@@ -513,6 +631,31 @@ client_echos_passive(
ssl_test_lib:close(Server),
ssl_test_lib:close(Client).
+
+client_echos_passive_chunk(
+ Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
+ Length = byte_size(Data),
+ Server =
+ ssl_test_lib:start_server(
+ [{node, ServerNode}, {port, 0},
+ {from, self()},
+ {mfa, {?MODULE, sender, [Data]}},
+ {options, [{active, false}, {mode, binary} | ServerOpts]}]),
+ Port = ssl_test_lib:inet_port(Server),
+ Client =
+ ssl_test_lib:start_client(
+ [{node, ClientNode}, {port, Port},
+ {host, Hostname},
+ {from, self()},
+ {mfa, {?MODULE, echoer_chunk, [Length]}},
+ {options, [{active, false}, {mode, binary} | ClientOpts]}]),
+ %%
+ ssl_test_lib:check_result(Server, ok, Client, ok),
+ %%
+ ssl_test_lib:close(Server),
+ ssl_test_lib:close(Client).
+
+
client_echos_active_once(
Data, ClientOpts, ServerOpts, ClientNode, ServerNode, Hostname) ->
Length = byte_size(Data),
@@ -615,6 +758,10 @@ echoer(Socket, Size) ->
ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]),
echo_recv(Socket, Size * 100).
+echoer_chunk(Socket, Size) ->
+ ct:log("Echoer recv: ~p~n", [ssl:getopts(Socket, [active])]),
+ echo_recv_chunk(Socket, Size, Size * 100).
+
echoer_active_once(Socket, Size) ->
ct:log("Echoer active once: ~p~n", [ssl:getopts(Socket, [active])]),
echo_active_once(Socket, Size * 100).
@@ -632,6 +779,16 @@ echo_recv(Socket, Size) ->
ok = ssl:send(Socket, Data),
echo_recv(Socket, Size - byte_size(Data)).
+
+%% Receive Size bytes
+echo_recv_chunk(_Socket, _, 0) ->
+ ok;
+echo_recv_chunk(Socket, ChunkSize, Size) ->
+ {ok, Data} = ssl:recv(Socket, ChunkSize),
+ ok = ssl:send(Socket, Data),
+ echo_recv_chunk(Socket, ChunkSize, Size - ChunkSize).
+
+
%% Receive Size bytes
echo_active_once(_Socket, 0) ->
ok;
diff --git a/lib/ssl/test/ssl_test_lib.erl b/lib/ssl/test/ssl_test_lib.erl
index 3b161a0c8a..d46ba1f787 100644
--- a/lib/ssl/test/ssl_test_lib.erl
+++ b/lib/ssl/test/ssl_test_lib.erl
@@ -159,6 +159,7 @@ connect(ListenSocket, Node, N, _, Timeout, SslOpts, [_|_] =ContOpts) ->
case ssl:handshake(AcceptSocket, SslOpts, Timeout) of
{ok, Socket0, Ext} ->
+ [_|_] = maps:get(sni, Ext),
ct:log("Ext ~p:~n", [Ext]),
ct:log("~p:~p~nssl:handshake_continue(~p,~p,~p)~n", [?MODULE,?LINE, Socket0, ContOpts,Timeout]),
case ssl:handshake_continue(Socket0, ContOpts, Timeout) of
@@ -429,14 +430,17 @@ check_result(Pid, Msg) ->
end.
check_server_alert(Pid, Alert) ->
receive
- {Pid, {error, {tls_alert, {Alert, _}}}} ->
+ {Pid, {error, {tls_alert, {Alert, STxt}}}} ->
+ check_server_txt(STxt),
ok
end.
check_server_alert(Server, Client, Alert) ->
receive
- {Server, {error, {tls_alert, {Alert, _}}}} ->
+ {Server, {error, {tls_alert, {Alert, STxt}}}} ->
+ check_server_txt(STxt),
receive
- {Client, {error, {tls_alert, {Alert, _}}}} ->
+ {Client, {error, {tls_alert, {Alert, CTxt}}}} ->
+ check_client_txt(CTxt),
ok;
{Client, {error, closed}} ->
ok
@@ -444,20 +448,35 @@ check_server_alert(Server, Client, Alert) ->
end.
check_client_alert(Pid, Alert) ->
receive
- {Pid, {error, {tls_alert, {Alert, _}}}} ->
+ {Pid, {error, {tls_alert, {Alert, CTxt}}}} ->
+ check_client_txt(CTxt),
ok
end.
check_client_alert(Server, Client, Alert) ->
receive
- {Client, {error, {tls_alert, {Alert, _}}}} ->
+ {Client, {error, {tls_alert, {Alert, CTxt}}}} ->
+ check_client_txt(CTxt),
receive
- {Server, {error, {tls_alert, {Alert, _}}}} ->
+ {Server, {error, {tls_alert, {Alert, STxt}}}} ->
+ check_server_txt(STxt),
ok;
{Server, {error, closed}} ->
ok
end
end.
+check_server_txt("TLS server" ++ _) ->
+ ok;
+check_server_txt("DTLS server" ++ _) ->
+ ok;
+check_server_txt(Txt) ->
+ ct:fail({expected_server, {got, Txt}}).
+check_client_txt("TLS client" ++ _) ->
+ ok;
+check_client_txt("DTLS client" ++ _) ->
+ ok;
+check_client_txt(Txt) ->
+ ct:fail({expected_server, {got, Txt}}).
wait_for_result(Server, ServerMsg, Client, ClientMsg) ->
receive
@@ -1084,7 +1103,15 @@ run_client_error(Opts) ->
Options = proplists:get_value(options, Opts),
ct:log("~p:~p~nssl:connect(~p, ~p, ~p)~n", [?MODULE,?LINE, Host, Port, Options]),
Error = Transport:connect(Host, Port, Options),
- Pid ! {self(), Error}.
+ case Error of
+ {error, _} ->
+ Pid ! {self(), Error};
+ {ok, _Socket} ->
+ receive
+ {ssl_error, _, {tls_alert, _}} = SslError ->
+ Pid ! {self(), SslError}
+ end
+ end.
accepters(N) ->
accepters([], N).
@@ -2149,7 +2176,8 @@ clean_env() ->
application:unset_env(ssl, session_cache_server_max),
application:unset_env(ssl, ssl_pem_cache_clean),
application:unset_env(ssl, bypass_pem_cache),
- application:unset_env(ssl, alert_timeout).
+ application:unset_env(ssl, alert_timeout),
+ application:unset_env(ssl, internal_active_n).
clean_start() ->
ssl:stop(),
diff --git a/lib/ssl/vsn.mk b/lib/ssl/vsn.mk
index 3c66ffd852..df38aea017 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 9.3
+SSL_VSN = 9.3.4
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 605a9f224d..092056ffde 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,40 @@
</header>
<p>This document describes the changes made to the STDLIB application.</p>
+<section><title>STDLIB 3.9.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug that could cause a loop when formatting
+ terms using the control sequences <c>p</c> or <c>P</c>
+ and limiting the output with the option
+ <c>chars_limit</c>. </p>
+ <p>
+ Own Id: OTP-15875 Aux Id: ERL-967 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 3.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p> Fix a bug that could cause a failure when formatting
+ binaries using the control sequences <c>p</c> or <c>P</c>
+ and limiting the output with the option
+ <c>chars_limit</c>. </p>
+ <p>
+ Own Id: OTP-15847 Aux Id: ERL-957 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.9</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index b1a5991bf0..77f02eafe0 100644
--- a/lib/stdlib/src/io_lib_pretty.erl
+++ b/lib/stdlib/src/io_lib_pretty.erl
@@ -462,7 +462,9 @@ find_upper(Lower, Term, T, Dl, Dd, D, RF, Enc, Str) ->
case If of
{_, _, _Dots=0, _} -> % even if Len > T
If;
- {_, Len, _, _} when Len =< T, D1 < D orelse D < 0 ->
+ {_, _Len=T, _, _} -> % increasing the depth is meaningless
+ If;
+ {_, Len, _, _} when Len < T, D1 < D orelse D < 0 ->
find_upper(If, Term, T, D1, Dd2, D, RF, Enc, Str);
_ ->
search_depth(Lower, If, Term, T, Dl, D1, RF, Enc, Str)
@@ -780,6 +782,8 @@ printable_bin0(Bin, D, T, Enc) ->
end,
printable_bin(Bin, Len, D, Enc).
+printable_bin(_Bin, 0, _D, _Enc) ->
+ false;
printable_bin(Bin, Len, D, latin1) ->
N = erlang:min(20, Len),
L = binary_to_list(Bin, 1, N),
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 7038cc159c..0c270e9dd5 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -38,7 +38,10 @@
{<<"^3\\.8$">>,[restart_new_emulator]},
{<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.8\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
+ {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.9$">>,[restart_new_emulator]},
+ {<<"^3\\.9\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.9\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
[{<<"^3\\.5$">>,[restart_new_emulator]},
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
@@ -50,4 +53,7 @@
{<<"^3\\.8$">>,[restart_new_emulator]},
{<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.8\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.8\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.9$">>,[restart_new_emulator]},
+ {<<"^3\\.9\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^3\\.9\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index 9b6d8d7401..4eb5b1772c 100644
--- a/lib/stdlib/test/io_SUITE.erl
+++ b/lib/stdlib/test/io_SUITE.erl
@@ -32,7 +32,7 @@
io_with_huge_message_queue/1, format_string/1,
maps/1, coverage/1, otp_14178_unicode_atoms/1, otp_14175/1,
otp_14285/1, limit_term/1, otp_14983/1, otp_15103/1, otp_15076/1,
- otp_15159/1, otp_15639/1, otp_15705/1]).
+ otp_15159/1, otp_15639/1, otp_15705/1, otp_15847/1, otp_15875/1]).
-export([pretty/2, trf/3]).
@@ -65,7 +65,7 @@ all() ->
io_lib_width_too_small, io_with_huge_message_queue,
format_string, maps, coverage, otp_14178_unicode_atoms, otp_14175,
otp_14285, limit_term, otp_14983, otp_15103, otp_15076, otp_15159,
- otp_15639, otp_15705].
+ otp_15639, otp_15705, otp_15847, otp_15875].
%% Error cases for output.
error_1(Config) when is_list(Config) ->
@@ -2708,3 +2708,13 @@ otp_15705(_Config) ->
"|кирилли́чес|" = trf("|~10ts|", [U], -1),
ok.
+
+otp_15847(_Config) ->
+ T = {someRecord,<<"1234567890">>,some,more},
+ "{someRecord,<<...>>,...}" =
+ pretty(T, [{chars_limit,20}, {encoding,latin1}]),
+ ok.
+
+otp_15875(_Config) ->
+ S = io_lib:format("~tp", [[{0, [<<"00">>]}]], [{chars_limit, 18}]),
+ "[{0,[<<48,...>>]}]" = lists:flatten(S).
diff --git a/lib/stdlib/test/lists_SUITE.erl b/lib/stdlib/test/lists_SUITE.erl
index 5dab6f6697..c3c54710eb 100644
--- a/lib/stdlib/test/lists_SUITE.erl
+++ b/lib/stdlib/test/lists_SUITE.erl
@@ -2600,6 +2600,15 @@ subtract(Config) when is_list(Config) ->
[1,2,3,4,5,6,7,8,9,9999,10000,20,21,22] =
sub(lists:seq(1, 10000)++[20,21,22], lists:seq(10, 9998)),
+ %% ERL-986; an integer overflow relating to term comparison
+ %% caused subtraction to be inconsistent.
+ Ids = [2985095936,47540628,135460048,1266126295,240535295,
+ 115724671,161800351,4187206564,4178142725,234897063,
+ 14773162,6662515191,133150693,378034895,1874402262,
+ 3507611978,22850922,415521280,253360400,71683243],
+
+ [] = id(Ids) -- id(Ids),
+
%% Floats/integers.
[42.0,42.0] = sub([42.0,42,42.0], [42,42,42]),
[1,2,3,4,43.0] = sub([1,2,3,4,5,42.0,43.0], [42.0,5]),
@@ -2627,6 +2636,8 @@ subtract(Config) when is_list(Config) ->
ok.
+id(I) -> I.
+
sub_non_matching(A, B) ->
A = sub(A, B).
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index 07224afdc9..c2f586fef5 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.9
+STDLIB_VSN = 3.9.2
diff --git a/make/otp_version_tickets b/make/otp_version_tickets
index 12cf3070e5..b741526c88 100644
--- a/make/otp_version_tickets
+++ b/make/otp_version_tickets
@@ -1,147 +1,10 @@
-OTP-13397
-OTP-13726
-OTP-13819
-OTP-14282
-OTP-14461
-OTP-14625
-OTP-14703
-OTP-14724
-OTP-14731
-OTP-14732
-OTP-14737
-OTP-14792
-OTP-14831
-OTP-14865
-OTP-14894
-OTP-14984
-OTP-15011
-OTP-15055
-OTP-15063
-OTP-15075
-OTP-15095
-OTP-15111
-OTP-15128
-OTP-15129
-OTP-15135
-OTP-15195
-OTP-15200
-OTP-15211
-OTP-15220
-OTP-15238
-OTP-15248
-OTP-15261
-OTP-15262
-OTP-15273
-OTP-15310
-OTP-15323
-OTP-15325
-OTP-15326
-OTP-15336
-OTP-15340
-OTP-15349
-OTP-15351
-OTP-15362
-OTP-15372
-OTP-15373
-OTP-15374
-OTP-15381
-OTP-15383
-OTP-15385
-OTP-15386
-OTP-15427
-OTP-15429
-OTP-15439
-OTP-15440
-OTP-15451
-OTP-15452
-OTP-15456
-OTP-15483
-OTP-15491
-OTP-15496
-OTP-15513
-OTP-15534
-OTP-15535
-OTP-15543
-OTP-15550
-OTP-15563
-OTP-15564
-OTP-15565
-OTP-15571
-OTP-15575
-OTP-15581
-OTP-15588
-OTP-15590
-OTP-15591
-OTP-15596
-OTP-15607
-OTP-15610
-OTP-15611
-OTP-15612
-OTP-15613
-OTP-15615
-OTP-15617
-OTP-15620
-OTP-15621
-OTP-15622
-OTP-15623
-OTP-15626
-OTP-15631
-OTP-15632
-OTP-15633
-OTP-15635
-OTP-15640
-OTP-15641
-OTP-15644
-OTP-15649
-OTP-15655
-OTP-15658
-OTP-15668
-OTP-15672
-OTP-15673
-OTP-15674
-OTP-15677
-OTP-15678
-OTP-15680
-OTP-15683
-OTP-15687
-OTP-15689
-OTP-15691
-OTP-15692
-OTP-15693
-OTP-15694
-OTP-15697
-OTP-15698
-OTP-15699
-OTP-15703
-OTP-15705
-OTP-15707
-OTP-15708
-OTP-15710
-OTP-15712
-OTP-15715
-OTP-15740
-OTP-15746
-OTP-15749
-OTP-15751
-OTP-15752
-OTP-15754
-OTP-15755
-OTP-15756
-OTP-15759
-OTP-15760
-OTP-15761
-OTP-15770
-OTP-15771
-OTP-15773
-OTP-15774
-OTP-15775
-OTP-15776
-OTP-15777
-OTP-15779
-OTP-15782
-OTP-15786
-OTP-15788
-OTP-15791
-OTP-15794
-OTP-15804
-OTP-15806
+OTP-15900
+OTP-15908
+OTP-15909
+OTP-15910
+OTP-15913
+OTP-15922
+OTP-15930
+OTP-15938
+OTP-15939
+OTP-15941
diff --git a/otp_versions.table b/otp_versions.table
index 101915dc6e..43a41780b2 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,8 @@
+OTP-22.0.5 : dialyzer-4.0.2 erts-10.4.4 inets-7.0.9 ssl-9.3.4 # asn1-5.0.9 common_test-1.17.3 compiler-7.4.2 crypto-4.5.1 debugger-4.2.7 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 jinterface-1.10 kernel-6.4.1 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9.2 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
+OTP-22.0.4 : erts-10.4.3 kernel-6.4.1 ssl-9.3.3 # asn1-5.0.9 common_test-1.17.3 compiler-7.4.2 crypto-4.5.1 debugger-4.2.7 dialyzer-4.0.1 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9.2 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
+OTP-22.0.3 : compiler-7.4.2 dialyzer-4.0.1 erts-10.4.2 ssl-9.3.2 stdlib-3.9.2 # asn1-5.0.9 common_test-1.17.3 crypto-4.5.1 debugger-4.2.7 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
+OTP-22.0.2 : compiler-7.4.1 crypto-4.5.1 erts-10.4.1 stdlib-3.9.1 # asn1-5.0.9 common_test-1.17.3 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3.1 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
+OTP-22.0.1 : ssl-9.3.1 # asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 diameter-2.2.1 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 parsetools-2.1.8 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 stdlib-3.9 syntax_tools-2.2 tftp-1.0.1 tools-3.2 wx-1.8.8 xmerl-1.3.21 :
OTP-22.0 : asn1-5.0.9 common_test-1.17.3 compiler-7.4 crypto-4.5 debugger-4.2.7 dialyzer-4.0 edoc-0.11 eldap-1.2.8 erl_docgen-0.9.1 erl_interface-3.12 erts-10.4 hipe-3.19 inets-7.0.8 jinterface-1.10 kernel-6.4 megaco-3.18.5 mnesia-4.16 observer-2.9.1 odbc-2.12.4 os_mon-2.5 public_key-1.6.7 reltool-0.8 runtime_tools-1.13.3 sasl-3.4 snmp-5.3 ssh-4.7.7 ssl-9.3 stdlib-3.9 syntax_tools-2.2 tools-3.2 wx-1.8.8 xmerl-1.3.21 # diameter-2.2.1 et-1.6.4 eunit-2.3.7 ftp-1.0.2 parsetools-2.1.8 tftp-1.0.1 :
OTP-21.3.8 : common_test-1.17.2 eldap-1.2.7 erl_interface-3.11.3 erts-10.3.5 public_key-1.6.6 ssl-9.2.3 stdlib-3.8.2 # asn1-5.0.8 compiler-7.3.2 crypto-4.4.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 erl_docgen-0.9 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.18.3 inets-7.0.7 jinterface-1.9.1 kernel-6.3.1 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6 syntax_tools-2.1.7 tftp-1.0.1 tools-3.1 wx-1.8.7 xmerl-1.3.20 :
OTP-21.3.7.1 : erl_interface-3.11.2.1 # asn1-5.0.8 common_test-1.17.1 compiler-7.3.2 crypto-4.4.2 debugger-4.2.6 dialyzer-3.3.2 diameter-2.2.1 edoc-0.10 eldap-1.2.6 erl_docgen-0.9 erts-10.3.4 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.18.3 inets-7.0.7 jinterface-1.9.1 kernel-6.3.1 megaco-3.18.4 mnesia-4.15.6 observer-2.9 odbc-2.12.3 os_mon-2.4.7 otp_mibs-1.2.1 parsetools-2.1.8 public_key-1.6.5 reltool-0.7.8 runtime_tools-1.13.2 sasl-3.3 snmp-5.2.12 ssh-4.7.6 ssl-9.2.2 stdlib-3.8.1 syntax_tools-2.1.7 tftp-1.0.1 tools-3.1 wx-1.8.7 xmerl-1.3.20 :