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_disasm.beambin20828 -> 20868 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.beambin45292 -> 45604 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_ssa_type.beambin28400 -> 28636 bytes
-rw-r--r--bootstrap/lib/compiler/ebin/beam_validator.beambin50048 -> 50216 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/disk_log_1.beambin22360 -> 22352 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/erl_distribution.beambin1596 -> 1872 bytes
-rw-r--r--bootstrap/lib/kernel/ebin/file.beambin13356 -> 13348 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/kernel/ebin/user_drv.beambin10844 -> 11020 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/beam_lib.beambin18820 -> 18852 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_utils.beambin25700 -> 25696 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/dets_v9.beambin45216 -> 45360 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/epp.beambin28252 -> 28328 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_lint.beambin86228 -> 86340 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_pp.beambin26012 -> 27160 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/erl_tar.beambin30932 -> 30972 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/filename.beambin14776 -> 14772 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/re.beambin12312 -> 12308 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/string.beambin35136 -> 35872 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/unicode_util.beambin200156 -> 200320 bytes
-rw-r--r--bootstrap/lib/stdlib/ebin/uri_string.beambin24988 -> 25084 bytes
-rw-r--r--erts/doc/src/erl_nif.xml10
-rw-r--r--erts/doc/src/erlang.xml4
-rw-r--r--erts/doc/src/net.xml20
-rw-r--r--erts/doc/src/notes.xml551
-rw-r--r--erts/doc/src/socket.xml158
-rw-r--r--erts/emulator/beam/atom.c77
-rw-r--r--erts/emulator/beam/atom.h8
-rw-r--r--erts/emulator/beam/beam_emu.c22
-rw-r--r--erts/emulator/beam/break.c5
-rw-r--r--erts/emulator/beam/dist.c11
-rw-r--r--erts/emulator/beam/erl_bif_guard.c2
-rw-r--r--erts/emulator/beam/erl_nif.h2
-rw-r--r--erts/emulator/beam/instrs.tab17
-rw-r--r--erts/emulator/beam/ops.tab19
-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/vsn.mk2
-rw-r--r--lib/asn1/doc/src/notes.xml26
-rw-r--r--lib/asn1/vsn.mk2
-rw-r--r--lib/common_test/doc/src/ct_hooks.xml6
-rw-r--r--lib/common_test/doc/src/notes.xml89
-rw-r--r--lib/common_test/vsn.mk2
-rw-r--r--lib/compiler/doc/src/notes.xml170
-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.erl11
-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.erl60
-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.xml148
-rw-r--r--lib/crypto/vsn.mk2
-rw-r--r--lib/debugger/doc/src/notes.xml17
-rw-r--r--lib/debugger/vsn.mk2
-rw-r--r--lib/dialyzer/doc/src/dialyzer.xml26
-rw-r--r--lib/dialyzer/doc/src/notes.xml62
-rw-r--r--lib/dialyzer/src/dialyzer.erl449
-rw-r--r--lib/dialyzer/src/dialyzer.hrl4
-rw-r--r--lib/dialyzer/src/dialyzer_cl.erl12
-rw-r--r--lib/dialyzer/src/dialyzer_cl_parse.erl10
-rw-r--r--lib/dialyzer/src/dialyzer_contracts.erl1
-rw-r--r--lib/dialyzer/src/dialyzer_dataflow.erl15
-rw-r--r--lib/dialyzer/src/dialyzer_gui_wx.erl4
-rw-r--r--lib/dialyzer/src/dialyzer_options.erl2
-rw-r--r--lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/callgraph_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/dialyzer_common.erl3
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/dialyzer_options1
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/results/arr15
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/results/iodata24
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/results/remote25
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/src/arr.erl41
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/src/iodata.erl41
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/src/remote/remotes1.erl61
-rw-r--r--lib/dialyzer/test/indent2_SUITE_data/src/remote/some_known_remote.erl5
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/dialyzer_options1
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/abs13
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/app_call9
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/arr4
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/blame_contract_range8
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/bs_fail_constr9
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs23
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/contract33
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes142
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/dict_use48
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/fun_app7
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/fun_app_args5
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/guard_update6
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/guard_warnings134
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/map_galore713
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/order23
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/queue_use34
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/rec15
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_construct11
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_creation_diffs4
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_match4
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_pat4
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_send_test6
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_test6
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/record_update3
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour23
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/simple289
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/suppress_request11
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/trec10
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/results/whereis_control_flow14
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/abs.erl78
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/app_call.erl17
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/blame_contract_range.erl16
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/bs_fail_constr.erl15
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_behaviour.erl11
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_correct.erl59
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl61
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/contract3.erl40
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/contracts_with_subtypes.erl300
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/dict_use.erl82
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/fun_app.erl41
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/fun_app_args.erl12
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/guard_update.erl18
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/guard_warnings.erl118
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl2800
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/order.erl56
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/queue_use.erl65
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/rec/rec_adt.erl22
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/rec/rec_use.erl30
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/record_construct.erl21
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/record_creation_diffs.erl11
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/record_match.erl17
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/record_pat.erl15
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/record_send_test.erl32
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/record_test.erl22
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/record_update.erl10
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_behaviour.erl13
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct.erl32
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct_2.erl38
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl26
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/exact_adt.erl17
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/exact_api.erl60
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/is_rec.erl65
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/rec_adt.erl28
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/rec_api.erl123
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_adt.erl138
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_api.erl571
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/simple/simple2_api.erl125
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/suppress_request.erl50
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/trec.erl39
-rw-r--r--lib/dialyzer/test/indent_SUITE_data/src/whereis_control_flow1.erl17
-rw-r--r--lib/dialyzer/test/map_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/contract6
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/guard_update2
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/map_galore10
-rw-r--r--lib/dialyzer/test/map_SUITE_data/results/typesig2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/dict6
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/queue6
-rw-r--r--lib/dialyzer/test/opaque_SUITE_data/results/simple2
-rw-r--r--lib/dialyzer/test/options1_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/options1_SUITE_data/results/compiler4
-rw-r--r--lib/dialyzer/test/options2_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/overspecs_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/r9c_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/r9c_SUITE_data/results/inets6
-rw-r--r--lib/dialyzer/test/race_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/race_SUITE_data/results/ets_insert_double12
-rw-r--r--lib/dialyzer/test/small_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/chars2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes22
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes22
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/failing_guard14
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/guard_warnings74
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/spec_other_module2
-rw-r--r--lib/dialyzer/test/small_SUITE_data/results/union_paren25
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl7
-rw-r--r--lib/dialyzer/test/small_SUITE_data/src/union_paren.erl98
-rw-r--r--lib/dialyzer/test/specdiffs_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/test/user_SUITE_data/dialyzer_options2
-rw-r--r--lib/dialyzer/vsn.mk2
-rw-r--r--lib/edoc/doc/src/notes.xml14
-rw-r--r--lib/edoc/vsn.mk2
-rw-r--r--lib/eldap/doc/src/notes.xml36
-rw-r--r--lib/eldap/vsn.mk2
-rw-r--r--lib/erl_docgen/doc/src/notes.xml19
-rw-r--r--lib/erl_docgen/vsn.mk2
-rw-r--r--lib/erl_interface/doc/src/ei.xml62
-rw-r--r--lib/erl_interface/doc/src/notes.xml142
-rw-r--r--lib/erl_interface/include/ei.h6
-rw-r--r--lib/erl_interface/src/connect/ei_connect.c12
-rw-r--r--lib/erl_interface/vsn.mk2
-rw-r--r--lib/hipe/cerl/erl_types.erl10
-rw-r--r--lib/hipe/doc/src/notes.xml46
-rw-r--r--lib/hipe/vsn.mk2
-rw-r--r--lib/inets/doc/src/notes.xml19
-rw-r--r--lib/inets/vsn.mk2
-rw-r--r--lib/jinterface/doc/src/notes.xml36
-rw-r--r--lib/jinterface/vsn.mk2
-rw-r--r--lib/kernel/doc/src/notes.xml98
-rw-r--r--lib/kernel/src/inet_db.erl3
-rw-r--r--lib/kernel/src/kernel.appup.src28
-rw-r--r--lib/kernel/test/inet_res_SUITE.erl7
-rw-r--r--lib/kernel/vsn.mk2
-rw-r--r--lib/megaco/doc/src/notes.xml25
-rw-r--r--lib/megaco/vsn.mk2
-rw-r--r--lib/mnesia/doc/src/notes.xml46
-rw-r--r--lib/mnesia/src/mnesia_tm.erl5
-rw-r--r--lib/mnesia/test/mnesia_dirty_access_test.erl34
-rw-r--r--lib/mnesia/vsn.mk2
-rw-r--r--lib/observer/doc/src/notes.xml17
-rw-r--r--lib/observer/vsn.mk2
-rw-r--r--lib/odbc/doc/src/notes.xml18
-rw-r--r--lib/odbc/vsn.mk2
-rw-r--r--lib/os_mon/doc/src/notes.xml29
-rw-r--r--lib/os_mon/vsn.mk2
-rw-r--r--lib/public_key/doc/src/notes.xml72
-rw-r--r--lib/public_key/vsn.mk2
-rw-r--r--lib/reltool/doc/src/notes.xml35
-rw-r--r--lib/reltool/vsn.mk2
-rw-r--r--lib/runtime_tools/doc/src/notes.xml16
-rw-r--r--lib/runtime_tools/vsn.mk2
-rw-r--r--lib/sasl/doc/src/notes.xml17
-rw-r--r--lib/sasl/src/sasl.appup.src24
-rw-r--r--lib/sasl/vsn.mk2
-rw-r--r--lib/snmp/doc/src/notes.xml27
-rw-r--r--lib/ssh/doc/src/notes.xml15
-rw-r--r--lib/ssh/src/ssh.app.src2
-rw-r--r--lib/ssh/vsn.mk2
-rw-r--r--lib/ssl/doc/src/notes.xml156
-rw-r--r--lib/ssl/doc/src/ssl.xml37
-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.erl125
-rw-r--r--lib/ssl/src/ssl_alert.erl31
-rw-r--r--lib/ssl/src/ssl_cipher_format.erl109
-rw-r--r--lib/ssl/src/ssl_connection.erl74
-rw-r--r--lib/ssl/src/ssl_handshake.erl6
-rw-r--r--lib/ssl/src/tls_connection.erl106
-rw-r--r--lib/ssl/src/tls_connection_1_3.erl35
-rw-r--r--lib/ssl/test/inet_crypto_dist.erl940
-rw-r--r--lib/ssl/test/openssl_server_cipher_suite_SUITE.erl8
-rw-r--r--lib/ssl/test/property_test/ssl_eqc_cipher_format.erl96
-rw-r--r--lib/ssl/test/ssl_basic_SUITE.erl46
-rw-r--r--lib/ssl/test/ssl_certificate_verify_SUITE.erl41
-rw-r--r--lib/ssl/test/ssl_dist_bench_SUITE.erl14
-rw-r--r--lib/ssl/test/ssl_eqc_SUITE.erl14
-rw-r--r--lib/ssl/test/ssl_payload_SUITE.erl157
-rw-r--r--lib/ssl/test/ssl_test_lib.erl34
-rw-r--r--lib/ssl/vsn.mk2
-rw-r--r--lib/stdlib/doc/src/notes.xml293
-rw-r--r--lib/stdlib/src/erl_lint.erl12
-rw-r--r--lib/stdlib/src/io_lib.erl23
-rw-r--r--lib/stdlib/src/io_lib_format.erl110
-rw-r--r--lib/stdlib/src/io_lib_pretty.erl57
-rw-r--r--lib/stdlib/src/stdlib.app.src2
-rw-r--r--lib/stdlib/src/stdlib.appup.src34
-rw-r--r--lib/stdlib/src/string.erl78
-rw-r--r--lib/stdlib/test/erl_lint_SUITE.erl24
-rw-r--r--lib/stdlib/test/erl_pp_SUITE.erl2
-rw-r--r--lib/stdlib/test/io_SUITE.erl46
-rw-r--r--lib/stdlib/test/string_SUITE.erl63
-rw-r--r--lib/stdlib/test/unicode_util_SUITE.erl10
-rw-r--r--lib/stdlib/uc_spec/gen_unicode_mod.escript26
-rw-r--r--lib/stdlib/vsn.mk2
-rw-r--r--lib/syntax_tools/doc/src/notes.xml28
-rw-r--r--lib/syntax_tools/vsn.mk2
-rw-r--r--lib/tools/doc/src/notes.xml43
-rw-r--r--lib/tools/vsn.mk2
-rw-r--r--lib/wx/doc/src/notes.xml17
-rw-r--r--lib/wx/vsn.mk2
-rw-r--r--lib/xmerl/doc/src/notes.xml15
-rw-r--r--lib/xmerl/vsn.mk2
-rw-r--r--make/otp_patch_solve_forward_merge_version2
-rw-r--r--make/otp_version_tickets10
-rw-r--r--otp_versions.table6
-rwxr-xr-xscripts/run-dialyzer2
298 files changed, 12504 insertions, 1292 deletions
diff --git a/OTP_VERSION b/OTP_VERSION
index 9664138791..8b74066182 100644
--- a/OTP_VERSION
+++ b/OTP_VERSION
@@ -1 +1 @@
-22.0-rc3
+22.0.3
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_disasm.beam b/bootstrap/lib/compiler/ebin/beam_disasm.beam
index 3fe71cfe1b..eb2a13d620 100644
--- a/bootstrap/lib/compiler/ebin/beam_disasm.beam
+++ b/bootstrap/lib/compiler/ebin/beam_disasm.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 329f31f546..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 07daf4ed81..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/disk_log_1.beam b/bootstrap/lib/kernel/ebin/disk_log_1.beam
index 36beba1f57..17f65d6ceb 100644
--- a/bootstrap/lib/kernel/ebin/disk_log_1.beam
+++ b/bootstrap/lib/kernel/ebin/disk_log_1.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/erl_distribution.beam b/bootstrap/lib/kernel/ebin/erl_distribution.beam
index 6a096155d6..4490f9c81f 100644
--- a/bootstrap/lib/kernel/ebin/erl_distribution.beam
+++ b/bootstrap/lib/kernel/ebin/erl_distribution.beam
Binary files differ
diff --git a/bootstrap/lib/kernel/ebin/file.beam b/bootstrap/lib/kernel/ebin/file.beam
index 9c2759adc9..a7a82a1f12 100644
--- a/bootstrap/lib/kernel/ebin/file.beam
+++ b/bootstrap/lib/kernel/ebin/file.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/kernel/ebin/user_drv.beam b/bootstrap/lib/kernel/ebin/user_drv.beam
index ce4d4328de..1b59423b71 100644
--- a/bootstrap/lib/kernel/ebin/user_drv.beam
+++ b/bootstrap/lib/kernel/ebin/user_drv.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/beam_lib.beam b/bootstrap/lib/stdlib/ebin/beam_lib.beam
index 44d783a6d0..a96ccb6f8c 100644
--- a/bootstrap/lib/stdlib/ebin/beam_lib.beam
+++ b/bootstrap/lib/stdlib/ebin/beam_lib.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_utils.beam b/bootstrap/lib/stdlib/ebin/dets_utils.beam
index 25bb217db1..e38eb384a1 100644
--- a/bootstrap/lib/stdlib/ebin/dets_utils.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_utils.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/dets_v9.beam b/bootstrap/lib/stdlib/ebin/dets_v9.beam
index 1532c51f8e..046de3bd5a 100644
--- a/bootstrap/lib/stdlib/ebin/dets_v9.beam
+++ b/bootstrap/lib/stdlib/ebin/dets_v9.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/epp.beam b/bootstrap/lib/stdlib/ebin/epp.beam
index a507106263..e3d8c21edf 100644
--- a/bootstrap/lib/stdlib/ebin/epp.beam
+++ b/bootstrap/lib/stdlib/ebin/epp.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/erl_pp.beam b/bootstrap/lib/stdlib/ebin/erl_pp.beam
index be2fe2c4c0..1965b9e656 100644
--- a/bootstrap/lib/stdlib/ebin/erl_pp.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_pp.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/erl_tar.beam b/bootstrap/lib/stdlib/ebin/erl_tar.beam
index 526d151a12..3c5004871d 100644
--- a/bootstrap/lib/stdlib/ebin/erl_tar.beam
+++ b/bootstrap/lib/stdlib/ebin/erl_tar.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/filename.beam b/bootstrap/lib/stdlib/ebin/filename.beam
index 2b76d5bce8..d353a11f3e 100644
--- a/bootstrap/lib/stdlib/ebin/filename.beam
+++ b/bootstrap/lib/stdlib/ebin/filename.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/re.beam b/bootstrap/lib/stdlib/ebin/re.beam
index d6920d54ce..5662594d4c 100644
--- a/bootstrap/lib/stdlib/ebin/re.beam
+++ b/bootstrap/lib/stdlib/ebin/re.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/string.beam b/bootstrap/lib/stdlib/ebin/string.beam
index 437f63357c..5d1d66892b 100644
--- a/bootstrap/lib/stdlib/ebin/string.beam
+++ b/bootstrap/lib/stdlib/ebin/string.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/unicode_util.beam b/bootstrap/lib/stdlib/ebin/unicode_util.beam
index c9db0483ca..c0523f2e52 100644
--- a/bootstrap/lib/stdlib/ebin/unicode_util.beam
+++ b/bootstrap/lib/stdlib/ebin/unicode_util.beam
Binary files differ
diff --git a/bootstrap/lib/stdlib/ebin/uri_string.beam b/bootstrap/lib/stdlib/ebin/uri_string.beam
index 001118f0b9..b57c84a302 100644
--- a/bootstrap/lib/stdlib/ebin/uri_string.beam
+++ b/bootstrap/lib/stdlib/ebin/uri_string.beam
Binary files differ
diff --git a/erts/doc/src/erl_nif.xml b/erts/doc/src/erl_nif.xml
index c0be715678..f88d255296 100644
--- a/erts/doc/src/erl_nif.xml
+++ b/erts/doc/src/erl_nif.xml
@@ -1090,7 +1090,7 @@ typedef struct {
</func>
<func>
- <name since="OTP @OTP-15011@"><ret>int</ret>
+ <name since="OTP 22.0"><ret>int</ret>
<nametext>enif_compare_pids(const ErlNifPid *pid1, const ErlNifPid *pid2)
</nametext></name>
<fsummary>Compare two pids.</fsummary>
@@ -1890,7 +1890,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name since="OTP @OTP-15011@"><ret>int</ret>
+ <name since="OTP 22.0"><ret>int</ret>
<nametext>enif_is_pid_undefined(const ErlNifPid* pid)</nametext></name>
<fsummary>Determine if pid is undefined.</fsummary>
<desc>
@@ -2247,7 +2247,7 @@ enif_inspect_iovec(env, max_elements, term, &amp;tail, &amp;iovec);
</func>
<func>
- <name since="OTP @OTP-15362@"><ret>ERL_NIF_TERM</ret>
+ <name since="OTP 22.0"><ret>ERL_NIF_TERM</ret>
<nametext>enif_make_monitor_term(ErlNifEnv* env, const ErlNifMonitor* mon)</nametext></name>
<fsummary>Make monitor term from the given monitor identifier.</fsummary>
<desc>
@@ -3306,7 +3306,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name since="OTP @OTP-15011@"><ret>void</ret>
+ <name since="OTP 22.0"><ret>void</ret>
<nametext>enif_set_pid_undefined(ErlNifPid* pid)</nametext></name>
<fsummary>Set pid as undefined.</fsummary>
<desc>
@@ -3369,7 +3369,7 @@ if (retval &amp; ERL_NIF_SELECT_STOP_CALLED) {
</func>
<func>
- <name since="OTP @OTP-15640@"><ret>ErlNifTermType</ret>
+ <name since="OTP 22.0"><ret>ErlNifTermType</ret>
<nametext>enif_term_type(ErlNifEnv *env, ERL_NIF_TERM term)</nametext>
</name>
<fsummary>Determine the type of a term.</fsummary>
diff --git a/erts/doc/src/erlang.xml b/erts/doc/src/erlang.xml
index 2b444ccf01..a879cce840 100644
--- a/erts/doc/src/erlang.xml
+++ b/erts/doc/src/erlang.xml
@@ -1297,7 +1297,7 @@ end</code>
</func>
<func>
- <name name="dist_ctrl_get_opt" arity="2" clause_i="1" since="OTP @OTP-15617@"/>
+ <name name="dist_ctrl_get_opt" arity="2" clause_i="1" since="OTP 22.0"/>
<fsummary>Get value of the get_size option on a distribution channel</fsummary>
<desc>
<p>Returns the value of the <c>get_size</c> option on the distribution channel
@@ -1428,7 +1428,7 @@ end</code>
</func>
<func>
- <name name="dist_ctrl_set_opt" arity="3" clause_i="1" since="OTP @OTP-15617@"/>
+ <name name="dist_ctrl_set_opt" arity="3" clause_i="1" since="OTP 22.0"/>
<fsummary>Set value of the get_size option on a distribution channel</fsummary>
<desc>
<p>Sets the value of the <c>get_size</c> option on the distribution channel
diff --git a/erts/doc/src/net.xml b/erts/doc/src/net.xml
index b9e2cffce9..6fbc37076c 100644
--- a/erts/doc/src/net.xml
+++ b/erts/doc/src/net.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>net.xml</file>
</header>
- <module since="OTP @OTP-14831@">net</module>
+ <module since="OTP 22.0">net</module>
<modulesummary>Network interface.</modulesummary>
<description>
<p>This module provides an API for the network interface.</p>
@@ -72,8 +72,8 @@
</func>
<func>
- <name name="getnameinfo" arity="1" since="OTP @OTP-14831@"/>
- <name name="getnameinfo" arity="2" since="OTP @OTP-14831@"/>
+ <name name="getnameinfo" arity="1" since="OTP 22.0"/>
+ <name name="getnameinfo" arity="2" since="OTP 22.0"/>
<fsummary>Address-to-name transaltion.</fsummary>
<desc>
<p>Address-to-name translation in a protocol-independant manner.</p>
@@ -84,10 +84,10 @@
</func>
<func>
- <name name="getaddrinfo" arity="1" since="OTP @OTP-14831@"/>
- <name name="getaddrinfo" arity="2" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="getaddrinfo" arity="2" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="getaddrinfo" arity="2" clause_i="3" since="OTP @OTP-14831@"/>
+ <name name="getaddrinfo" arity="1" since="OTP 22.0"/>
+ <name name="getaddrinfo" arity="2" clause_i="1" since="OTP 22.0"/>
+ <name name="getaddrinfo" arity="2" clause_i="2" since="OTP 22.0"/>
+ <name name="getaddrinfo" arity="2" clause_i="3" since="OTP 22.0"/>
<fsummary>Network address and service transation.</fsummary>
<desc>
<p>Network address and service translation.</p>
@@ -100,7 +100,7 @@
</func>
<func>
- <name name="if_name2index" arity="1" since="OTP @OTP-14831@"/>
+ <name name="if_name2index" arity="1" since="OTP 22.0"/>
<fsummary>Mappings between network interface names and indexes.</fsummary>
<desc>
<p>Mappings between network interface names and indexes.</p>
@@ -108,7 +108,7 @@
</func>
<func>
- <name name="if_index2name" arity="1" since="OTP @OTP-14831@"/>
+ <name name="if_index2name" arity="1" since="OTP 22.0"/>
<fsummary>Mappings between network interface index and names.</fsummary>
<desc>
<p>Mappings between network interface index and names.</p>
@@ -116,7 +116,7 @@
</func>
<func>
- <name name="if_names" arity="0" since="OTP @OTP-14831@"/>
+ <name name="if_names" arity="0" since="OTP 22.0"/>
<fsummary>Get network interface names and indexes.</fsummary>
<desc>
<p>Get network interface names and indexes.</p>
diff --git a/erts/doc/src/notes.xml b/erts/doc/src/notes.xml
index da470b51ec..075972c979 100644
--- a/erts/doc/src/notes.xml
+++ b/erts/doc/src/notes.xml
@@ -31,6 +31,557 @@
</header>
<p>This document describes the changes made to the ERTS application.</p>
+<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>
+ <list>
+ <item>
+ <p>
+ Do not allocate new bitstring/binary when an empty binary
+ is appended.</p>
+ <p>
+ Own Id: OTP-15535 Aux Id: PR-2055 </p>
+ </item>
+ <item>
+ <p>
+ Document that <c>process_info(_, current_function)</c>
+ can return <c>{current_function, undefined}</c> in case
+ of execution of native code.</p>
+ <p>
+ Own Id: OTP-15543 Aux Id: PR-2089 </p>
+ </item>
+ <item>
+ <p>
+ Fixed bug in <c>ets:select</c>, <c>ets:match</c> and
+ friends which could cause the table to remain fixated (as
+ if <c>ets:safe_fixtable</c> had been called) after the
+ call returned. This could happen for <c>protected</c>
+ tables if another concurrent running process transferred
+ table ownership to the process during its
+ ets:select/match call. Ownership can be transferred using
+ either <c>ets:give_away</c> or the <c>heir</c> table
+ option.</p>
+ <p>
+ Own Id: OTP-15672</p>
+ </item>
+ <item>
+ <p>Fixed a Windows-specific bug in <c>file:list_dir/1</c>
+ that caused it to misbehave on network shares.</p>
+ <p>
+ Own Id: OTP-15693</p>
+ </item>
+ <item>
+ <p>
+ Fixed bug when calling <c>enif_whereis_*</c> from NIF
+ resource destructor. Symptoms could be emulator crash or
+ hanging scheduler threads.</p>
+ <p>
+ Own Id: OTP-15694 Aux Id: ERL-863 </p>
+ </item>
+ <item>
+ <p>Fixed a bug in the error case of <c>apply/3</c>, where
+ the exception would erroneously have an empty argument
+ list in some cases.</p>
+ <p>
+ Own Id: OTP-15698</p>
+ </item>
+ <item>
+ <p>A bug has been fixed in the <c>maps</c> implementation
+ that could cause a crash or memory usage to grow until
+ the machine ran out of memory. This could happen when
+ inserting a new key-value pair with a key <c>K1</c>
+ containing a binary <c>B1</c> into a map <c>M</c> having
+ a key <c>K2</c> with a binary <c>B2</c> if the following
+ conditions were met:</p> <list> <item><c>B1 =/=
+ B2</c></item> <item><c>size(B1) >= 4294967296</c></item>
+ <item><c>size(B2) >= 4294967296</c></item>
+ <item><c>size(M) >= 32</c></item> <item><c>(size(B1) rem
+ 4294967296) == (size(B2) rem 4294967296)</c></item>
+ <item>the first <c>(size(B1) rem 4294967296)</c> bytes
+ are the same both in <c>B1</c> and <c>B2</c></item>
+ <item>substituting <c>B1</c> in <c>K1</c> with <c>B2</c>
+ would create a term with the same value as
+ <c>K2</c></item> </list> <p>The root cause of the problem
+ is that the <c>maps</c> implementation only hashed the
+ first <c>(X rem 4294967296)</c> bytes of binaries so that
+ different binaries could get the same hash value
+ independently of the hash seed.</p>
+ <p>
+ Own Id: OTP-15707</p>
+ </item>
+ <item>
+ <p>
+ <c>term_to_binary()</c> and distributed sends will now
+ throw a <c>system_limit</c> exception instead of
+ producing erroneous results when trying to encode a
+ binary larger than 4 GB.</p>
+ <p>
+ Own Id: OTP-15708</p>
+ </item>
+ <item>
+ <p>
+ The vxworks configure has been updated to respect the
+ environment CFLAGS.</p>
+ <p>
+ Own Id: OTP-15773</p>
+ </item>
+ <item>
+ <p>
+ Fix configure to not enable PGO (profile guided
+ optimizations) when linking of the PGO binary fails. For
+ instance this happens when there is no gcov lib
+ installed.</p>
+ <p>
+ Own Id: OTP-15788</p>
+ </item>
+ <item>
+ <p>
+ Fix bug on OpenBSD where sockets using <c>active,
+ true</c> or <c>active, N</c> could cause the system to
+ live lock. The bug was introduced in erts-10.2
+ (OTP-21.2).</p>
+ <p>
+ Own Id: OTP-15791</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add support for Erlang Distribution protocol to split the
+ payload of large signals into several fragments. This
+ allows other processes to communicate uninterrupted
+ during the transmission of these signals.</p>
+ <p>
+ Own Id: OTP-13397</p>
+ </item>
+ <item>
+ <p>
+ A simple socket API is provided through the socket
+ module. This is a low level API that does *not* replace
+ gen_[tcp|udp|sctp]. It is intended to *eventually*
+ replace the inet driver, but not the high level
+ gen-modules (gen_tcp, gen_udp and gen_sctp). It also
+ provides a basic API that facilitates the implementation
+ of other protocols, that is TCP, UDP and SCTP. </p>
+ <p>
+ Known issues are; No support for the Windows OS
+ (currently).</p>
+ <p>
+ Own Id: OTP-14831</p>
+ </item>
+ <item>
+ <p>Added NIF functions <seealso
+ marker="erl_nif#enif_set_pid_undefined"><c>enif_set_pid_undefined</c></seealso>,
+ <seealso
+ marker="erl_nif#enif_is_pid_undefined"><c>enif_is_pid_undefined</c></seealso>
+ and <seealso
+ marker="erl_nif#enif_compare_pids"><c>enif_compare_pids</c></seealso>.</p>
+ <p>
+ Own Id: OTP-15011 Aux Id: PR-2147 </p>
+ </item>
+ <item>
+ <p>Underutilized memory segments (carriers) can now move
+ between all allocator instances, rather than just between
+ instances of the same type, which greatly reduces memory
+ usage in some scenarios. </p>
+ <p>
+ Own Id: OTP-15063</p>
+ </item>
+ <item>
+ <p>The emulator will now mark free blocks in pooled
+ carriers with <c>madvise(2) + MADV_FREE</c> (or similar),
+ letting the OS reclaim the associated physical memory if
+ necessary.</p>
+ <p>
+ Own Id: OTP-15075</p>
+ </item>
+ <item>
+ <p>
+ New <c>ERL_NIF_SELECT_CANCEL</c> feature added to
+ <c>enif_select</c> in order to cancel (or "deselect") a
+ read or write event on a previously selected file
+ descriptor.</p>
+ <p>
+ Own Id: OTP-15095</p>
+ </item>
+ <item>
+ <p>
+ ETS option <c>write_concurrency</c> now also affects and
+ improves the scalability of <c>ordered_set</c> tables.
+ The implementation is based on a data structure called
+ contention adapting search tree, where the lock
+ granularity adapts to the actual amount of concurrency
+ exploited by the applications in runtime.</p>
+ <p>
+ Own Id: OTP-15128</p>
+ </item>
+ <item>
+ <p>
+ Build configuration of the <c>crypto</c> application has
+ been moved from the <c>erts</c> application into the
+ <c>crypto</c> application.</p>
+ <p>
+ Own Id: OTP-15129</p>
+ </item>
+ <item>
+ <p>Anonymous functions that don't capture environment are
+ now created at load-time instead of in run-time.</p>
+ <p>
+ Own Id: OTP-15195 Aux Id: PR-1812 </p>
+ </item>
+ <item>
+ <p>
+ Optimize updates of maps with identical keys and values.
+ E.g. in the example below the original Map will be reused
+ as the return of the update.</p>
+ <p>
+ 1> Map = #{ a => b }. #{ a => b } 2> Map#{ a := b }.</p>
+ <p>
+ Own Id: OTP-15211 Aux Id: PR-1889 </p>
+ </item>
+ <item>
+ <p>
+ Optimize <c>binary:match/2</c> and
+ <c>binary:matches/2</c> to use memchr internally.</p>
+ <p>
+ Own Id: OTP-15238 Aux Id: PR-1803 </p>
+ </item>
+ <item>
+ <p>
+ The runtime system used to terminate when a message
+ larger than 2 Gb was passed over the distribution. The
+ send operation will now instead throw a
+ <c>system_limit</c> exception.</p>
+ <p>
+ Own Id: OTP-15261</p>
+ </item>
+ <item>
+ <p>
+ Change the first module called by erts to be named
+ erl_init instead of otp_ring0. systools in sasl have been
+ updated to reflect this change.</p>
+ <p>
+ Own Id: OTP-15336 Aux Id: PR-1825 </p>
+ </item>
+ <item>
+ <p>
+ Minor adjustments made to build system for parallel
+ configure.</p>
+ <p>
+ Own Id: OTP-15340 Aux Id: OTP-14625 </p>
+ </item>
+ <item>
+ <p>
+ Two new NIF interface functions <c>enif_select_read</c>
+ and <c>enif_select_write</c>. They are similar to
+ existing <c>enif_select</c> but allow a custom event
+ message as an argument.</p>
+ <p>
+ Own Id: OTP-15349 Aux Id: PR-2084 </p>
+ </item>
+ <item>
+ <p>The embedded copy of <c>zlib</c> has been updated from
+ <c>1.2.8</c> to <c>1.2.11</c>.</p>
+ <p>Note that this copy is only used as a fallback when
+ the target platform doesn't provide any <c>zlib</c>
+ development libraries. If your system provides
+ <c>zlib</c> then it will be used even if it is older than
+ <c>1.2.11</c>.</p>
+ <p>
+ Own Id: OTP-15351 Aux Id: ERL-749 </p>
+ </item>
+ <item>
+ <p>
+ New NIF function <c>enif_make_monitor_term</c>.</p>
+ <p>
+ Own Id: OTP-15362 Aux Id: PR-2127 </p>
+ </item>
+ <item>
+ <p>Appending lists (The ++ operator) will now yield
+ properly on large inputs.</p>
+ <p>
+ Own Id: OTP-15427</p>
+ </item>
+ <item>
+ <p>The <c>length/1</c> BIF used to calculate the length
+ of the list in one go without yielding, even if the list
+ was very long. In OTP 22, <c>length/1</c> will yield when
+ called with long lists.</p>
+ <p>
+ Own Id: OTP-15439</p>
+ </item>
+ <item>
+ <p>
+ Processes sending messages are now punished with a
+ reduction cost based on message size. That is, a process
+ sending a large message will yield earlier than before.</p>
+ <p>
+ Own Id: OTP-15513 Aux Id: ERL-773 </p>
+ </item>
+ <item>
+ <p>The transitory emulator option <c>+ztma true</c>
+ (introduced in OTP 21.3) has been removed.</p>
+ <p>
+ Own Id: OTP-15581 Aux Id: OTP-15580 </p>
+ </item>
+ <item>
+ <p>In OTP 22, HiPE (the native code compiler) is not
+ fully functional. The reasons for this are:</p>
+ <p>There are new BEAM instructions for binary matching
+ that the HiPE native code compiler does not support.</p>
+ <p>The new optimizations in the Erlang compiler create
+ new combination of instructions that HiPE currently does
+ not handle correctly.</p>
+ <p>If erlc is invoked with the <c>+native</c> option, and
+ if any of the new binary matching instructions are used,
+ the compiler will issue a warning and produce a BEAM file
+ without native code.</p>
+ <p>
+ Own Id: OTP-15596</p>
+ </item>
+ <item>
+ <p>
+ The termination behaviour of processes has changed to
+ allow processes to yield while sending link exit/monitor
+ down signals.</p>
+ <p>
+ The erl crash dump has been expanded to now also include
+ processes that are termeinating but have not yet
+ terminated.</p>
+ <p>
+ Own Id: OTP-15610</p>
+ </item>
+ <item>
+ <p>
+ The dist messages EXIT, EXIT2 and MONITOR_DOWN have been
+ updated with new versions that send the reason term as
+ part of the payload of the message instead of as part of
+ the control message.</p>
+ <p>
+ The old versions are still present and can be used when
+ communicating with nodes that don't support the new
+ versions.</p>
+ <p>
+ Own Id: OTP-15611</p>
+ </item>
+ <item>
+ <p>
+ When sending messages, exit, exit2 and monitor down
+ distributed signals, the process sending will now yield
+ appropriately.</p>
+ <p>
+ This means that a terminating process will yield and
+ possibly be suspended on busy distribution entries while
+ they are terminating. This means that any memory held by
+ such processes will not be released until after all
+ exit/monitor down signals have been sent.</p>
+ <p>
+ Own Id: OTP-15612</p>
+ </item>
+ <item>
+ <p>
+ All external pids/ports/refs created by
+ erlang:list_to_pid/port/ref debug functions now compare
+ equal to any other pid/port/ref with the same number from
+ that node.</p>
+ <p>
+ Before this change they compared differently because the
+ node creation of the pid/port/ref did not compare equal
+ to any real pid/port/ref creation.</p>
+ <p>
+ This will mostly effect pids/ports/refs typed in the
+ shell.</p>
+ <p>
+ Own Id: OTP-15613</p>
+ </item>
+ <item>
+ <p>
+ The <c>persistent_term</c> functions <c>put/2</c> and
+ <c>erase/1</c> are now yielding.</p>
+ <p>
+ Own Id: OTP-15615</p>
+ </item>
+ <item>
+ <p>
+ A new <seealso
+ marker="erlang#dist_ctrl_set_opt/3"><c>erlang:dist_ctrl_set_opt(DHandle,
+ get_size, Value)</c></seealso> option has been added.
+ This option makes it possible to configure the
+ distribution channel identified by <c>DHandle</c> so that
+ <seealso
+ marker="erlang#dist_ctrl_get_data/1"><c>erlang:dist_ctrl_get_data(DHandle)</c></seealso>
+ also returns the size of the data to pass over the
+ channel.</p>
+ <p>
+ Own Id: OTP-15617</p>
+ </item>
+ <item>
+ <p>Previously, all ETS tables used centralized counter
+ variables to keep track of the number of items stored and
+ the amount of memory consumed. These counters can cause
+ scalability problems (especially on big NUMA systems).
+ This change adds an implementation of a decentralized
+ counter and modifies the implementation of ETS so that
+ ETS tables of type <c>ordered_set</c> with
+ <c>write_concurrency</c> enabled use the decentralized
+ counter. Experiments indicate that this change
+ substantially improves the scalability of ETS
+ <c>ordered_set</c> tables with <c>write_concurrency</c>
+ enabled in scenarios with frequent <c>ets:insert/2</c>
+ and <c>ets:delete/2</c> calls.</p>
+ <p>
+ Own Id: OTP-15623 Aux Id: PR-2190 </p>
+ </item>
+ <item>
+ <p>The <c>iolist_size/1</c> function is now yielding
+ which means that an Erlang/OTP system will be responsive
+ even if the applications running on the system frequently
+ call <c>iolist_size/1</c> with large iolists.</p>
+ <p>
+ Own Id: OTP-15631</p>
+ </item>
+ <item>
+ <p>
+ A simple test suite for the net module has been added.</p>
+ <p>
+ Own Id: OTP-15635</p>
+ </item>
+ <item>
+ <p>Added the NIF function <c>enif_term_type</c>, which
+ helps avoid long sequences of <c>enif_is_xyz</c> by
+ returning the type of the given term. This is especially
+ helpful for NIFs that serialize terms, such as JSON
+ encoders, where it can improve both performance and
+ readability.</p>
+ <p>
+ Own Id: OTP-15640</p>
+ </item>
+ <item>
+ <p>The last call optimization is now applied to BIFs.
+ When calling a BIF in the tail position of a function,
+ the return address and stack frame will now be discarded
+ before calling the BIF. As a consequence of this change,
+ the immediate caller of a tail-called BIF will no longer
+ be available in stack backtraces.</p>
+ <p>
+ Own Id: OTP-15674 Aux Id: PR-2177 </p>
+ </item>
+ <item>
+ <p>
+ Fix GC bug where distributed messages in a processes
+ mailbox would cause extra GCs. This can be very expensive
+ if there many messages in the mailbox.</p>
+ <p>
+ Own Id: OTP-15703</p>
+ </item>
+ <item>
+ <p>Internal documentation has now been added to the
+ <em>Erts</em> and <em>Compiler</em> applications.</p>
+ <p>The internal documents for <em>Erts</em> describe
+ miscellaneous interesting implementation details. Those
+ details can change at any time.</p>
+ <p>The internal documentation for <em>Compiler</em>
+ documents the API for the Core Erlang modules. While we
+ will not change those APIs without good reason, we don't
+ give the same guarantees about backward compatibility as
+ for the rest of the APIs in OTP.</p>
+ <p>
+ Own Id: OTP-15715</p>
+ </item>
+ <item>
+ <p>The performance of non-bignum integer arithmetic has
+ been improved.</p>
+ <p>
+ Own Id: OTP-15740</p>
+ </item>
+ <item>
+ <p>
+ The <c>-remsh</c> option to <c>erl</c> now automatically
+ adds the local systems hostname to the target node if no
+ nodename is given. e.g.</p>
+ <p>
+ <c> erl -name foo -remsh bar </c><br/> <c> erl -sname foo
+ -remsh bar </c></p>
+ <p>
+ Own Id: OTP-15794 Aux Id: PR-2219 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erts 10.3.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fixed more bugs in <c>process_info(reductions)</c>
+ causing it to sometimes behave non-monotonic. That is, a
+ subsequent call toward the same process could return a
+ lower reduction value.</p>
+ <p>
+ Own Id: OTP-15793 Aux Id: ERIERL-337, OTP-15709 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erts 10.3.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/erts/doc/src/socket.xml b/erts/doc/src/socket.xml
index f6195a65b2..343b61d4aa 100644
--- a/erts/doc/src/socket.xml
+++ b/erts/doc/src/socket.xml
@@ -29,7 +29,7 @@
<rev></rev>
<file>socket.xml</file>
</header>
- <module since="OTP @OTP-14831@">socket</module>
+ <module since="OTP 22.0">socket</module>
<modulesummary>Socket interface.</modulesummary>
<description>
<p>This module provides an API for the socket interface.
@@ -256,8 +256,8 @@
<funcs>
<func>
- <name name="accept" arity="1" since="OTP @OTP-14831@"/>
- <name name="accept" arity="2" since="OTP @OTP-14831@"/>
+ <name name="accept" arity="1" since="OTP 22.0"/>
+ <name name="accept" arity="2" since="OTP 22.0"/>
<fsummary>Accept a connection on a socket.</fsummary>
<desc>
<p>Accept a connection on a socket.</p>
@@ -269,7 +269,7 @@
</func>
<func>
- <name name="bind" arity="2" since="OTP @OTP-14831@"/>
+ <name name="bind" arity="2" since="OTP 22.0"/>
<fsummary>Bind a name to a socket.</fsummary>
<desc>
<p>Bind a name to a socket.</p>
@@ -282,7 +282,7 @@
</func>
<func>
- <name name="close" arity="1" since="OTP @OTP-14831@"/>
+ <name name="close" arity="1" since="OTP 22.0"/>
<fsummary>Close a socket.</fsummary>
<desc>
<p>Closes the socket.</p>
@@ -301,8 +301,8 @@
</func>
<func>
- <name name="connect" arity="2" since="OTP @OTP-14831@"/>
- <name name="connect" arity="3" since="OTP @OTP-14831@"/>
+ <name name="connect" arity="2" since="OTP 22.0"/>
+ <name name="connect" arity="3" since="OTP 22.0"/>
<fsummary>Initiate a connection on a socket.</fsummary>
<desc>
<p>This function connects the socket to the address
@@ -311,13 +311,13 @@
</func>
<func>
- <name name="getopt" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="getopt" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="getopt" arity="3" clause_i="3" since="OTP @OTP-14831@"/>
- <name name="getopt" arity="3" clause_i="4" since="OTP @OTP-14831@"/>
- <name name="getopt" arity="3" clause_i="5" since="OTP @OTP-14831@"/>
- <name name="getopt" arity="3" clause_i="6" since="OTP @OTP-14831@"/>
- <name name="getopt" arity="3" clause_i="7" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="4" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="5" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="6" since="OTP 22.0"/>
+ <name name="getopt" arity="3" clause_i="7" since="OTP 22.0"/>
<fsummary>Get an option on a socket.</fsummary>
<desc>
<p>Get an option on a socket.</p>
@@ -337,7 +337,7 @@
</func>
<func>
- <name name="getopt" arity="3" clause_i="8" since="OTP @OTP-14831@"/>
+ <name name="getopt" arity="3" clause_i="8" since="OTP 22.0"/>
<fsummary>Get an option on a socket.</fsummary>
<desc>
<p>Get an option on a socket.</p>
@@ -359,8 +359,8 @@
</func>
<func>
- <name name="listen" arity="1" since="OTP @OTP-14831@"/>
- <name name="listen" arity="2" since="OTP @OTP-14831@"/>
+ <name name="listen" arity="1" since="OTP 22.0"/>
+ <name name="listen" arity="2" since="OTP 22.0"/>
<fsummary>Listen for connections on a socket.</fsummary>
<desc>
<p>Listen for connections on a socket.</p>
@@ -368,9 +368,9 @@
</func>
<func>
- <name name="open" arity="2" since="OTP @OTP-14831@"/>
- <name name="open" arity="3" since="OTP @OTP-14831@"/>
- <name name="open" arity="4" since="OTP @OTP-14831@"/>
+ <name name="open" arity="2" since="OTP 22.0"/>
+ <name name="open" arity="3" since="OTP 22.0"/>
+ <name name="open" arity="4" since="OTP 22.0"/>
<fsummary>Create an endpoint for communication.</fsummary>
<desc>
<p>Creates an endpoint (socket) for communication.</p>
@@ -390,7 +390,7 @@
</func>
<func>
- <name name="peername" arity="1" since="OTP @OTP-14831@"/>
+ <name name="peername" arity="1" since="OTP 22.0"/>
<fsummary>Get name of connected socket peer.</fsummary>
<desc>
<p>Returns the address of the peer connected to the socket.</p>
@@ -398,11 +398,11 @@
</func>
<func>
- <name name="recv" arity="1" since="OTP @OTP-14831@"/>
- <name name="recv" arity="2" since="OTP @OTP-14831@"/>
- <name name="recv" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="recv" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="recv" arity="4" since="OTP @OTP-14831@"/>
+ <name name="recv" arity="1" since="OTP 22.0"/>
+ <name name="recv" arity="2" since="OTP 22.0"/>
+ <name name="recv" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="recv" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="recv" arity="4" since="OTP 22.0"/>
<fsummary>Receive a message from a socket.</fsummary>
<desc>
<p>Receive a message from a socket.</p>
@@ -413,12 +413,12 @@
</func>
<func>
- <name name="recvfrom" arity="1" since="OTP @OTP-14831@"/>
- <name name="recvfrom" arity="2" since="OTP @OTP-14831@"/>
- <name name="recvfrom" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="recvfrom" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="recvfrom" arity="3" clause_i="3" since="OTP @OTP-14831@"/>
- <name name="recvfrom" arity="4" since="OTP @OTP-14831@"/>
+ <name name="recvfrom" arity="1" since="OTP 22.0"/>
+ <name name="recvfrom" arity="2" since="OTP 22.0"/>
+ <name name="recvfrom" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="recvfrom" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="recvfrom" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="recvfrom" arity="4" since="OTP 22.0"/>
<fsummary>Receive a message from a socket.</fsummary>
<desc>
<p>Receive a message from a socket.</p>
@@ -437,12 +437,12 @@
</func>
<func>
- <name name="recvmsg" arity="1" since="OTP @OTP-14831@"/>
- <name name="recvmsg" arity="2" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="recvmsg" arity="2" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="recvmsg" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="recvmsg" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="recvmsg" arity="5" since="OTP @OTP-14831@"/>
+ <name name="recvmsg" arity="1" since="OTP 22.0"/>
+ <name name="recvmsg" arity="2" clause_i="1" since="OTP 22.0"/>
+ <name name="recvmsg" arity="2" clause_i="2" since="OTP 22.0"/>
+ <name name="recvmsg" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="recvmsg" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="recvmsg" arity="5" since="OTP 22.0"/>
<fsummary>Receive a message from a socket.</fsummary>
<desc>
<p>Receive a message from a socket.</p>
@@ -473,10 +473,10 @@
</func>
<func>
- <name name="send" arity="2" since="OTP @OTP-14831@"/>
- <name name="send" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="send" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="send" arity="4" since="OTP @OTP-14831@"/>
+ <name name="send" arity="2" since="OTP 22.0"/>
+ <name name="send" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="send" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="send" arity="4" since="OTP 22.0"/>
<fsummary>Send a message on a socket.</fsummary>
<desc>
<p>Send a message on a connected socket.</p>
@@ -484,10 +484,10 @@
</func>
<func>
- <name name="sendmsg" arity="2" since="OTP @OTP-14831@"/>
- <name name="sendmsg" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="sendmsg" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="sendmsg" arity="4" since="OTP @OTP-14831@"/>
+ <name name="sendmsg" arity="2" since="OTP 22.0"/>
+ <name name="sendmsg" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="sendmsg" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="sendmsg" arity="4" since="OTP 22.0"/>
<fsummary>Send a message on a socket.</fsummary>
<desc>
<p>Send a message on a socket. The destination, if needed
@@ -508,10 +508,10 @@
</func>
<func>
- <name name="sendto" arity="3" since="OTP @OTP-14831@"/>
- <name name="sendto" arity="4" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="sendto" arity="4" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="sendto" arity="5" since="OTP @OTP-14831@"/>
+ <name name="sendto" arity="3" since="OTP 22.0"/>
+ <name name="sendto" arity="4" clause_i="1" since="OTP 22.0"/>
+ <name name="sendto" arity="4" clause_i="2" since="OTP 22.0"/>
+ <name name="sendto" arity="5" since="OTP 22.0"/>
<fsummary>Send a message on a socket.</fsummary>
<desc>
<p>Send a message on a socket, to the specified destination.</p>
@@ -519,13 +519,13 @@
</func>
<func>
- <name name="setopt" arity="4" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="setopt" arity="4" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="setopt" arity="4" clause_i="3" since="OTP @OTP-14831@"/>
- <name name="setopt" arity="4" clause_i="4" since="OTP @OTP-14831@"/>
- <name name="setopt" arity="4" clause_i="5" since="OTP @OTP-14831@"/>
- <name name="setopt" arity="4" clause_i="6" since="OTP @OTP-14831@"/>
- <name name="setopt" arity="4" clause_i="7" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="1" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="2" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="3" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="4" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="5" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="6" since="OTP 22.0"/>
+ <name name="setopt" arity="4" clause_i="7" since="OTP 22.0"/>
<fsummary>Set options on a socket.</fsummary>
<desc>
<p>Set options on a socket.</p>
@@ -548,7 +548,7 @@
</func>
<func>
- <name name="setopt" arity="4" clause_i="8" since="OTP @OTP-14831@"/>
+ <name name="setopt" arity="4" clause_i="8" since="OTP 22.0"/>
<fsummary>Set options on a socket.</fsummary>
<desc>
<p>Set options on a socket.</p>
@@ -571,7 +571,7 @@
</func>
<func>
- <name name="shutdown" arity="2" since="OTP @OTP-14831@"/>
+ <name name="shutdown" arity="2" since="OTP 22.0"/>
<fsummary>Shut down part of a full-duplex connection.</fsummary>
<desc>
<p>Shut down all or part of a full-duplex connection.</p>
@@ -579,7 +579,7 @@
</func>
<func>
- <name name="sockname" arity="1" since="OTP @OTP-14831@"/>
+ <name name="sockname" arity="1" since="OTP 22.0"/>
<fsummary>Get socket name.</fsummary>
<desc>
<p>Returns the current address to which the socket is bound.</p>
@@ -587,25 +587,25 @@
</func>
<func>
- <name name="supports" arity="0" since="OTP @OTP-14831@"/>
- <name name="supports" arity="1" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="supports" arity="1" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="supports" arity="1" clause_i="3" since="OTP @OTP-14831@"/>
- <name name="supports" arity="1" clause_i="4" since="OTP @OTP-14831@"/>
- <name name="supports" arity="2" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="supports" arity="2" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="supports" arity="2" clause_i="3" since="OTP @OTP-14831@"/>
- <name name="supports" arity="2" clause_i="4" since="OTP @OTP-14831@"/>
- <name name="supports" arity="2" clause_i="5" since="OTP @OTP-14831@"/>
- <name name="supports" arity="2" clause_i="6" since="OTP @OTP-14831@"/>
- <name name="supports" arity="2" clause_i="7" since="OTP @OTP-14831@"/>
- <name name="supports" arity="3" clause_i="1" since="OTP @OTP-14831@"/>
- <name name="supports" arity="3" clause_i="2" since="OTP @OTP-14831@"/>
- <name name="supports" arity="3" clause_i="3" since="OTP @OTP-14831@"/>
- <name name="supports" arity="3" clause_i="4" since="OTP @OTP-14831@"/>
- <name name="supports" arity="3" clause_i="5" since="OTP @OTP-14831@"/>
- <name name="supports" arity="3" clause_i="6" since="OTP @OTP-14831@"/>
- <name name="supports" arity="3" clause_i="7" since="OTP @OTP-14831@"/>
+ <name name="supports" arity="0" since="OTP 22.0"/>
+ <name name="supports" arity="1" clause_i="1" since="OTP 22.0"/>
+ <name name="supports" arity="1" clause_i="2" since="OTP 22.0"/>
+ <name name="supports" arity="1" clause_i="3" since="OTP 22.0"/>
+ <name name="supports" arity="1" clause_i="4" since="OTP 22.0"/>
+ <name name="supports" arity="2" clause_i="1" since="OTP 22.0"/>
+ <name name="supports" arity="2" clause_i="2" since="OTP 22.0"/>
+ <name name="supports" arity="2" clause_i="3" since="OTP 22.0"/>
+ <name name="supports" arity="2" clause_i="4" since="OTP 22.0"/>
+ <name name="supports" arity="2" clause_i="5" since="OTP 22.0"/>
+ <name name="supports" arity="2" clause_i="6" since="OTP 22.0"/>
+ <name name="supports" arity="2" clause_i="7" since="OTP 22.0"/>
+ <name name="supports" arity="3" clause_i="1" since="OTP 22.0"/>
+ <name name="supports" arity="3" clause_i="2" since="OTP 22.0"/>
+ <name name="supports" arity="3" clause_i="3" since="OTP 22.0"/>
+ <name name="supports" arity="3" clause_i="4" since="OTP 22.0"/>
+ <name name="supports" arity="3" clause_i="5" since="OTP 22.0"/>
+ <name name="supports" arity="3" clause_i="6" since="OTP 22.0"/>
+ <name name="supports" arity="3" clause_i="7" since="OTP 22.0"/>
<fsummary>Report info about what the platform supports.</fsummary>
<desc>
<p>This function intends to retreive information about what the
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 8e93e53003..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
*/
@@ -3272,7 +3292,7 @@ erts_current_reductions(Process *c_p, Process *p)
} else {
reds_left = c_p->fcalls;
}
- return REDS_IN(c_p) - reds_left;
+ return REDS_IN(c_p) - reds_left - erts_proc_sched_data(p)->virtual_reds;
}
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_guard.c b/erts/emulator/beam/erl_bif_guard.c
index c921b66a7e..09757e473b 100644
--- a/erts/emulator/beam/erl_bif_guard.c
+++ b/erts/emulator/beam/erl_bif_guard.c
@@ -306,7 +306,7 @@ Eterm erts_trapping_length_1(Process* p, Eterm* args)
* We reached the end of the list successfully. Bump reductions
* and return result.
*/
- BUMP_REDS(p, saved_max_iter / 16);
+ BUMP_REDS(p, (saved_max_iter - max_iter) / 16);
return make_small(i);
}
diff --git a/erts/emulator/beam/erl_nif.h b/erts/emulator/beam/erl_nif.h
index cc389a093f..c250e8f683 100644
--- a/erts/emulator/beam/erl_nif.h
+++ b/erts/emulator/beam/erl_nif.h
@@ -69,7 +69,7 @@
* If you're not on the OTP team, you should use a placeholder like
* erts-@MyName@ instead.
*/
-#define ERL_NIF_MIN_ERTS_VERSION "erts-@OTP-15095:OTP-15640@"
+#define ERL_NIF_MIN_ERTS_VERSION "erts-10.4"
/*
* The emulator will refuse to load a nif-lib with a major version
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/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/vsn.mk b/erts/vsn.mk
index fafcdf3b28..53dce5e815 100644
--- a/erts/vsn.mk
+++ b/erts/vsn.mk
@@ -18,7 +18,7 @@
# %CopyrightEnd%
#
-VSN = 10.3.4
+VSN = 10.4.2
# Port number 4365 in 4.2
# Port number 4366 in 4.3
diff --git a/lib/asn1/doc/src/notes.xml b/lib/asn1/doc/src/notes.xml
index 22ca7840de..284a2b4ce5 100644
--- a/lib/asn1/doc/src/notes.xml
+++ b/lib/asn1/doc/src/notes.xml
@@ -32,6 +32,32 @@
<p>This document describes the changes made to the asn1 application.</p>
+<section><title>Asn1 5.0.9</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ <item>
+ <p>Corrected problems with the following value
+ definitions:</p> <list> <item>value of SEQUENCE OF CHOICE
+ with extensions</item> <item>value of CHOICE with
+ extensions</item> <item>DEFAULT used with OCTET
+ STRING</item> </list>
+ <p>
+ Own Id: OTP-15697 Aux Id: PR-2159 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Asn1 5.0.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/asn1/vsn.mk b/lib/asn1/vsn.mk
index 69f1af28e8..018beda307 100644
--- a/lib/asn1/vsn.mk
+++ b/lib/asn1/vsn.mk
@@ -1 +1 @@
-ASN1_VSN = 5.0.8
+ASN1_VSN = 5.0.9
diff --git a/lib/common_test/doc/src/ct_hooks.xml b/lib/common_test/doc/src/ct_hooks.xml
index ff9969ebc3..100be85cac 100644
--- a/lib/common_test/doc/src/ct_hooks.xml
+++ b/lib/common_test/doc/src/ct_hooks.xml
@@ -109,7 +109,7 @@
</func>
<func>
- <name since="OTP @OTP-14746@">Module:post_groups(SuiteName, GroupDefs) -&gt; NewGroupDefs</name>
+ <name since="OTP 21.3.8">Module:post_groups(SuiteName, GroupDefs) -&gt; NewGroupDefs</name>
<fsummary>Called after groups/0.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -165,7 +165,7 @@
</func>
<func>
- <name since="OTP @OTP-14746@">Module:post_all(SuiteName, Return, GroupDefs) -&gt; NewReturn</name>
+ <name since="OTP 21.3.8">Module:post_all(SuiteName, Return, GroupDefs) -&gt; NewReturn</name>
<fsummary>Called after all/0.</fsummary>
<type>
<v>SuiteName = atom()</v>
@@ -787,5 +787,3 @@
</funcs>
</erlref>
-
-
diff --git a/lib/common_test/doc/src/notes.xml b/lib/common_test/doc/src/notes.xml
index a68cc3cca7..a64818da7b 100644
--- a/lib/common_test/doc/src/notes.xml
+++ b/lib/common_test/doc/src/notes.xml
@@ -33,6 +33,95 @@
<file>notes.xml</file>
</header>
+<section><title>Common_Test 1.17.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Use <c>ssh</c> instead of <c>rsh</c> as the default
+ remote shell. </p>
+ <p>
+ Own Id: OTP-15633 Aux Id: PR-1787 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Common_Test 1.17.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The test result when a hook function fails is in general
+ the same as if the function that the hook is associated
+ with fails. For example, if <c>post_init_per_testcase</c>
+ fails the result is that the test case is skipped, as is
+ the case when <c>init_per_testcase</c> fails.This,
+ however, was earlier not true for timetrap timeouts or
+ other error situations where the process running the hook
+ function was killed. This is now corrected, so the error
+ handling should be the same no matter how the hook
+ function fails.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15717 Aux Id: ERIERL-334 </p>
+ </item>
+ <item>
+ <p>
+ In some rare cases, when two common_test nodes used the
+ same log directory, a timing problem could occur which
+ caused common_test to crash because it's log cache file
+ was unexpectedly empty. This is now corrected.</p>
+ <p>
+ Own Id: OTP-15758 Aux Id: ERIERL-342 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Two new common_test hook functions are introduced:</p>
+ <p>
+ <c>post_groups/2</c>, which is called after
+ <c>Suite:groups/0</c><br/> <c>post_all/3</c>, which is
+ called after <c>Suite:all/0</c></p>
+ <p>
+ These functions allow modifying the return values from
+ the <c>groups/0</c> and <c>all/0</c> functions,
+ respectively.</p>
+ <p>
+ A new term, <c>{testcase,TestCase,RepeatProperties}</c>
+ is now also allowed in the return from <c>all/0</c>. This
+ can be used for repeating a single test case a specific
+ number of times, or until it fails or succeeds once.</p>
+ <p>
+ Own Id: OTP-14746 Aux Id: ERIERL-143 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Common_Test 1.17.1</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/common_test/vsn.mk b/lib/common_test/vsn.mk
index 14a3622a00..8dcb69c1c6 100644
--- a/lib/common_test/vsn.mk
+++ b/lib/common_test/vsn.mk
@@ -1 +1 @@
-COMMON_TEST_VSN = 1.17.1
+COMMON_TEST_VSN = 1.17.3
diff --git a/lib/compiler/doc/src/notes.xml b/lib/compiler/doc/src/notes.xml
index d45dfef8f3..f0d869381b 100644
--- a/lib/compiler/doc/src/notes.xml
+++ b/lib/compiler/doc/src/notes.xml
@@ -32,6 +32,176 @@
<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>
+ <list>
+ <item>
+ <p><c>record_info/2</c> is a pseudo-function that
+ requires literal arguments known at compile time.
+ Therefore, the following usage is illegal: <c>fun
+ record/info/2</c>. The compiler would crash when during
+ compilation of that kind of code. Corrected to issue a
+ compilation error.</p>
+ <p>
+ Own Id: OTP-15760 Aux Id: ERL-907 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The compiler has been rewritten to internally use an
+ intermediate representation based on Static Single
+ Assignment (SSA). The new intermediate representation
+ makes more optimizations possible.</p>
+ <p>
+ Most noticeable is that the binary matching optimizations
+ are now applicable in many more circumstances than
+ before.</p>
+ <p>
+ Another noticeable change is that type optimizations are
+ now applied across local function calls, and will remove
+ a lot more redundant type tests than before.</p>
+ <p>
+ Own Id: OTP-14894 Aux Id: ERL-714 </p>
+ </item>
+ <item>
+ <p>Funs are no longer created when they are only used
+ locally, greatly improving the performance of named funs
+ and "fun-wrapped" macros.</p>
+ <p>
+ Own Id: OTP-15273 Aux Id: ERL-639 </p>
+ </item>
+ <item>
+ <p>All compiler options that can be given in the source
+ file can now also be given in the option list or from the
+ command line for <c>erlc</c>.</p>
+ <p>Specifically, the option
+ <c>{nowarn_deprecated_function,MFAs}</c> was only
+ recognized when given in the file with the attribute
+ <c>-compile()</c>. The option
+ <c>{nowarn_unused_function,FAs}</c> was incorrectly
+ documented to only work in a file, but it also worked
+ when given in the option list.</p>
+ <p>
+ Own Id: OTP-15456</p>
+ </item>
+ <item>
+ <p> Do not allow function specifications for functions
+ residing in other modules. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15563 Aux Id: ERL-845, OTP-15562 </p>
+ </item>
+ <item>
+ <p>Internal documentation has now been added to the
+ <em>Erts</em> and <em>Compiler</em> applications.</p>
+ <p>The internal documents for <em>Erts</em> describe
+ miscellaneous interesting implementation details. Those
+ details can change at any time.</p>
+ <p>The internal documentation for <em>Compiler</em>
+ documents the API for the Core Erlang modules. While we
+ will not change those APIs without good reason, we don't
+ give the same guarantees about backward compatibility as
+ for the rest of the APIs in OTP.</p>
+ <p>
+ Own Id: OTP-15715</p>
+ </item>
+ <item>
+ <p> There are new compiler options <c>nowarn_removed</c>
+ and <c>{nowarn_removed,Items}</c> to suppress warnings
+ for functions and modules that have been removed from
+ OTP.</p>
+ <p>
+ Own Id: OTP-15749 Aux Id: ERL-904 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Compiler 7.3.2</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..9af72afca7 100644
--- a/lib/compiler/src/beam_ssa_pre_codegen.erl
+++ b/lib/compiler/src/beam_ssa_pre_codegen.erl
@@ -1415,12 +1415,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..a741ebbdf9 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) ->
@@ -492,6 +494,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 a523627384..508bbc902c 100644
--- a/lib/compiler/vsn.mk
+++ b/lib/compiler/vsn.mk
@@ -1 +1 @@
-COMPILER_VSN = 7.3.2
+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 195c9d029d..5f47981855 100644
--- a/lib/crypto/doc/src/notes.xml
+++ b/lib/crypto/doc/src/notes.xml
@@ -31,6 +31,154 @@
</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>
+ <list>
+ <item>
+ <p>
+ Fixed a bug in error return for <c>crypto:poly1305/2</c>.
+ It returned the atom <c>notsup</c> instead of the
+ exception <c>notsup</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15677</p>
+ </item>
+ <item>
+ <p>
+ The cipher chacha20 was introduced in OpenSSL 1.1.0.
+ However, it could in a very odd situation, fail for
+ versions less than OpenSSL 1.1.0d. It is therefore
+ disabled for those versions.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15678</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> A new <c>rand</c> module algorithm, <c>exro928ss</c>
+ (Xoroshiro928**), has been implemented. It has got a
+ really long period and good statistical quality for all
+ output bits, while still being only about 50% slower than
+ the default algorithm. </p><p> The same generator is also
+ used as a long period counter in a new <c>crypto</c>
+ plugin for the <c>rand</c> module, algorithm
+ <c>crypto_aes</c>. This plugin uses AES-256 to scramble
+ the counter which buries any detectable statistical
+ artifacts. Scrambling is done in chunks which are cached
+ to get good amortized speed (about half of the default
+ algorithm). </p>
+ <p>
+ Own Id: OTP-14461 Aux Id: PR-1857 </p>
+ </item>
+ <item>
+ <p>
+ Crypto's single C-file is split into multiple files. The
+ different coding styles in the different parts are
+ unified into a single style.</p>
+ <p>
+ Own Id: OTP-14732 Aux Id: PR-2068, PR-2095 </p>
+ </item>
+ <item>
+ <p>
+ Build configuration of the <c>crypto</c> application has
+ been moved from the <c>erts</c> application into the
+ <c>crypto</c> application.</p>
+ <p>
+ Own Id: OTP-15129</p>
+ </item>
+ <item>
+ <p>
+ Adds two hash functions <c>blake2b</c> and <c>blake2s</c>
+ (64 bit hash and 32 bit hash respectively). These are
+ modern and standard hash functions used in blockchains
+ and encrypted communication protocols. The hash functions
+ are available in OpenSSL since version 1.1.1.</p>
+ <p>
+ Own Id: OTP-15564 Aux Id: PR-2129 </p>
+ </item>
+ <item>
+ <p>
+ A new API is implemented in crypto. See the CRYPTO user's
+ guide, chapter <i>New and Old API</i> for more
+ information.</p>
+ <p>
+ The old api with the <c>crypto:block_*</c> and
+ <c>crypto:stream_*</c> interfaces are kept for
+ compatibility, but implemented with the new api. Please
+ note that since the error checking is more thorough,
+ there <i>might</i> be arguments with for example faulty
+ lengths that are no longer accepted.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15644 Aux Id: OTP-14732 , OTP-15451, PR-1857
+ , PR-2068, PR-2095 </p>
+ </item>
+ <item>
+ <p>
+ The new hash_info/1 and cipher_info/1 functions returns
+ maps with information about the hash or cipher in the
+ argument.</p>
+ <p>
+ Own Id: OTP-15655 Aux Id: PR-2173, ERL-864, PR-2186 </p>
+ </item>
+ <item>
+ <p>
+ Obey additional OpenSSL configure flags when compiling
+ the C-part of the CRYPTO application: <c>no-bf</c>,
+ <c>no-blake2</c>, <c>no-chacha</c>, <c>no-cmac</c>,
+ <c>no-dh</c>, <c>no-dsa</c>, <c>no-md4</c>,
+ <c>no-poly1305</c>, <c>no-rc2</c>, <c>no-rc4</c> and
+ <c>no-rmd160</c>.</p>
+ <p>
+ Own Id: OTP-15683</p>
+ </item>
+ <item>
+ <p>
+ A new function <c>crypto:supports/1</c> is introduced.
+ The single argument takes an atom as argument:
+ <c>hashes</c>, <c>public_keys</c>, <c>ciphers</c>,
+ <c>macs</c>, <c>curves</c> or <c>rsa_opts</c>. The return
+ value is a list of supported algorithms.</p>
+ <p>
+ The difference with the existing <c>crypto:supports/0</c>
+ is, apart from the argument and the return value, that
+ the old function reports what is supported by the old
+ api, and the new function reports algorithms in the new
+ api.</p>
+ <p>
+ Own Id: OTP-15771</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Crypto 4.4.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/crypto/vsn.mk b/lib/crypto/vsn.mk
index 0a3d9f45e4..2315cb3c48 100644
--- a/lib/crypto/vsn.mk
+++ b/lib/crypto/vsn.mk
@@ -1 +1 @@
-CRYPTO_VSN = 4.4.2
+CRYPTO_VSN = 4.5.1
diff --git a/lib/debugger/doc/src/notes.xml b/lib/debugger/doc/src/notes.xml
index 395b69973d..795b46d467 100644
--- a/lib/debugger/doc/src/notes.xml
+++ b/lib/debugger/doc/src/notes.xml
@@ -33,6 +33,23 @@
<p>This document describes the changes made to the Debugger
application.</p>
+<section><title>Debugger 4.2.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Debugger 4.2.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/debugger/vsn.mk b/lib/debugger/vsn.mk
index a3cbb497f8..daecc7594c 100644
--- a/lib/debugger/vsn.mk
+++ b/lib/debugger/vsn.mk
@@ -1 +1 @@
-DEBUGGER_VSN = 4.2.6
+DEBUGGER_VSN = 4.2.7
diff --git a/lib/dialyzer/doc/src/dialyzer.xml b/lib/dialyzer/doc/src/dialyzer.xml
index f5e8337eb1..443de7b0dd 100644
--- a/lib/dialyzer/doc/src/dialyzer.xml
+++ b/lib/dialyzer/doc/src/dialyzer.xml
@@ -4,7 +4,7 @@
<erlref>
<header>
<copyright>
- <year>2006</year><year>2017</year>
+ <year>2006</year><year>2019</year>
<holder>Ericsson AB. All Rights Reserved.</holder>
</copyright>
<legalnotice>
@@ -85,7 +85,7 @@ dialyzer --help</code>
dialyzer [--add_to_plt] [--apps applications] [--build_plt]
[--check_plt] [-Ddefine]* [-Dname] [--dump_callgraph file]
[files_or_dirs] [--fullpath] [--get_warnings] [--gui] [--help]
- [-I include_dir]* [--no_check_plt] [--no_native]
+ [-I include_dir]* [--no_check_plt] [--no_indentation] [--no_native]
[--no_native_cache] [-o outfile] [--output_plt file] [-pa dir]*
[--plt plt] [--plt_info] [--plts plt*] [--quiet] [-r dirs]
[--raw] [--remove_from_plt] [--shell] [--src] [--statistics]
@@ -181,6 +181,11 @@ dialyzer --apps inets ssl ./ebin ../other_lib/ebin/my_module.beam</code>
<p>Skip the PLT check when running Dialyzer. This is useful when
working with installed PLTs that never change.</p>
</item>
+ <tag><c>--no_indentation</c></tag>
+ <item>
+ <p>Do not insert line breaks in types, contracts, and Erlang
+ Code when formatting warnings.</p>
+ </item>
<tag><c>--no_native</c> (or <c>-nn</c>)</tag>
<item>
<p>Bypass the native code compilation of some key files that
@@ -485,6 +490,23 @@ dialyzer --plts plt_1 ... plt_n -- files_to_analyze</code>
</func>
<func>
+ <name since="">format_warning(Msg, Options) -> string()</name>
+ <fsummary>Get the string version of a warning message.</fsummary>
+ <type>
+ <v>Msg = {Tag, Id, msg()}</v>
+ <d>See <c>run/1</c>.</d>
+ <v>Options = [{indent_opt, boolean()}]</v>
+ </type>
+ <desc>
+ <p>Get a string from warnings as returned by
+ <seealso marker="#run/1"><c>run/1</c></seealso>.</p>
+ <p>If <c>indent_opt</c> is set to <c>true</c> (default),
+ line breaks are inserted in types, contracts, and Erlang
+ code to improve readability.</p>
+ </desc>
+ </func>
+
+ <func>
<name since="">gui() -> ok | {error, Msg}</name>
<name since="">gui(OptList) -> ok | {error, Msg}</name>
<fsummary>Dialyzer GUI version.</fsummary>
diff --git a/lib/dialyzer/doc/src/notes.xml b/lib/dialyzer/doc/src/notes.xml
index bc422c43a0..0930f79840 100644
--- a/lib/dialyzer/doc/src/notes.xml
+++ b/lib/dialyzer/doc/src/notes.xml
@@ -32,6 +32,68 @@
<p>This document describes the changes made to the Dialyzer
application.</p>
+<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>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> By default Dialyzer inserts line breaks in types,
+ contracts, and Erlang Code when formatting results. Use
+ the new <c>--no_indentation</c> option to get the old
+ behavior of not inserting line breaks. </p>
+ <p>
+ Own Id: OTP-15135</p>
+ </item>
+ <item>
+ <p> Use bit syntax in warnings instead of Core Erlang
+ syntax, for readability. </p>
+ <p>
+ Own Id: OTP-15752</p>
+ </item>
+ <item>
+ <p> The format of the raw analysis result tagged with
+ <c>fun_app_args</c> is changed to <c>{fun_app_args,
+ [ArgNs, Args, Type]}</c>. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15779</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Dialyzer 3.3.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl
index a168b3c8c5..d4fe064edd 100644
--- a/lib/dialyzer/src/dialyzer.erl
+++ b/lib/dialyzer/src/dialyzer.erl
@@ -280,20 +280,23 @@ cl_check_log(Output) ->
format_warning(W) ->
format_warning(W, basename).
--spec format_warning(raw_warning() | dial_warning(), fopt()) -> string().
-
-format_warning({Tag, {File, Line, _MFA}, Msg}, FOpt) ->
- format_warning({Tag, {File, Line}, Msg}, FOpt);
-format_warning({_Tag, {File, Line}, Msg}, FOpt) when is_list(File),
+-spec format_warning(raw_warning() | dial_warning(),
+ fopt() | proplists:proplist()) -> string().
+
+format_warning(RawWarning, FOpt) when is_atom(FOpt) ->
+ format_warning(RawWarning, [{filename_opt, FOpt}]);
+format_warning({Tag, {File, Line, _MFA}, Msg}, Opts) ->
+ format_warning({Tag, {File, Line}, Msg}, Opts);
+format_warning({_Tag, {File, Line}, Msg}, Opts) when is_list(File),
is_integer(Line) ->
- F = case FOpt of
+ F = case proplists:get_value(filename_opt, Opts, basename) of
fullpath -> File;
basename -> filename:basename(File)
end,
- String = lists:flatten(message_to_string(Msg)),
+ Indent = proplists:get_value(indent_opt, Opts, ?INDENT_OPT),
+ String = message_to_string(Msg, Indent),
lists:flatten(io_lib:format("~ts:~w: ~ts", [F, Line, String])).
-
%%-----------------------------------------------------------------------------
%% Message classification and pretty-printing below. Messages appear in
%% categories and in more or less alphabetical ordering within each category.
@@ -301,55 +304,60 @@ format_warning({_Tag, {File, Line}, Msg}, FOpt) when is_list(File),
%%----- Warnings for general discrepancies ----------------
message_to_string({apply, [Args, ArgNs, FailReason,
- SigArgs, SigRet, Contract]}) ->
- io_lib:format("Fun application with arguments ~ts ", [Args]) ++
- call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract);
-message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]}) ->
+ SigArgs, SigRet, Contract]}, I) ->
+ io_lib:format("Fun application with arguments ~ts ", [a(Args, I)]) ++
+ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract, I);
+message_to_string({app_call, [M, F, Args, Culprit, ExpectedType, FoundType]},
+ I) ->
io_lib:format("The call ~s:~ts~ts requires that ~ts is of type ~ts not ~ts\n",
- [M, F, Args, Culprit, ExpectedType, FoundType]);
-message_to_string({bin_construction, [Culprit, Size, Seg, Type]}) ->
+ [M, F, a(Args, I), c(Culprit, I),
+ t(ExpectedType, I), t(FoundType, I)]);
+message_to_string({bin_construction, [Culprit, Size, Seg, Type]}, I) ->
io_lib:format("Binary construction will fail since the ~s field ~s in"
- " segment ~s has type ~s\n", [Culprit, Size, Seg, Type]);
+ " segment ~s has type ~s\n",
+ [Culprit, c(Size, I), c(Seg, I), t(Type, I)]);
message_to_string({call, [M, F, Args, ArgNs, FailReason,
- SigArgs, SigRet, Contract]}) ->
- io_lib:format("The call ~w:~tw~ts ", [M, F, Args]) ++
- call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract);
-message_to_string({call_to_missing, [M, F, A]}) ->
+ SigArgs, SigRet, Contract]}, I) ->
+ io_lib:format("The call ~w:~tw~ts ", [M, F, a(Args, I)]) ++
+ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet, Contract, I);
+message_to_string({call_to_missing, [M, F, A]}, _I) ->
io_lib:format("Call to missing or unexported function ~w:~tw/~w\n",
[M, F, A]);
-message_to_string({exact_eq, [Type1, Op, Type2]}) ->
+message_to_string({exact_eq, [Type1, Op, Type2]}, I) ->
io_lib:format("The test ~ts ~s ~ts can never evaluate to 'true'\n",
- [Type1, Op, Type2]);
-message_to_string({fun_app_args, [ArgNs, Args, Type]}) ->
+ [t(Type1, I), Op, t(Type2, I)]);
+message_to_string({fun_app_args, [ArgNs, Args, Type]}, I) ->
PositionString = form_position_string(ArgNs),
io_lib:format("Fun application with arguments ~ts will fail"
" since the function has type ~ts,"
" which differs in the ~s argument\n",
- [Args, Type, PositionString]);
-message_to_string({fun_app_no_fun, [Op, Type, Arity]}) ->
+ [a(Args, I), t(Type, I), PositionString]);
+message_to_string({fun_app_no_fun, [Op, Type, Arity]}, I) ->
io_lib:format("Fun application will fail since ~ts :: ~ts"
- " is not a function of arity ~w\n", [Op, Type, Arity]);
-message_to_string({guard_fail, []}) ->
+ " is not a function of arity ~w\n", [Op, t(Type, I), Arity]);
+message_to_string({guard_fail, []}, _I) ->
"Clause guard cannot succeed.\n";
-message_to_string({guard_fail, [Arg1, Infix, Arg2]}) ->
- io_lib:format("Guard test ~ts ~s ~ts can never succeed\n", [Arg1, Infix, Arg2]);
-message_to_string({map_update, [Type, Key]}) ->
+message_to_string({guard_fail, [Arg1, Infix, Arg2]}, I) ->
+ io_lib:format("Guard test ~ts ~s ~ts can never succeed\n",
+ [a(Arg1, I), Infix, a(Arg2, I)]); % a/2 rather than c/2
+message_to_string({map_update, [Type, Key]}, I) ->
io_lib:format("A key of type ~ts cannot exist "
- "in a map of type ~ts\n", [Key, Type]);
-message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}) ->
+ "in a map of type ~ts\n", [t(Key, I), t(Type, I)]);
+message_to_string({neg_guard_fail, [Arg1, Infix, Arg2]}, I) ->
io_lib:format("Guard test not(~ts ~s ~ts) can never succeed\n",
- [Arg1, Infix, Arg2]);
-message_to_string({guard_fail, [Guard, Args]}) ->
- io_lib:format("Guard test ~w~ts can never succeed\n", [Guard, Args]);
-message_to_string({neg_guard_fail, [Guard, Args]}) ->
- io_lib:format("Guard test not(~w~ts) can never succeed\n", [Guard, Args]);
-message_to_string({guard_fail_pat, [Pat, Type]}) ->
+ [a(Arg1, I), Infix, a(Arg2, I)]); % a/2 rather than c/2
+message_to_string({guard_fail, [Guard, Args]}, I) ->
+ io_lib:format("Guard test ~s~ts can never succeed\n", [Guard, a(Args, I)]);
+message_to_string({neg_guard_fail, [Guard, Args]}, I) ->
+ io_lib:format("Guard test not(~s~ts) can never succeed\n",
+ [Guard, a(Args, I)]);
+message_to_string({guard_fail_pat, [Pat, Type]}, I) ->
io_lib:format("Clause guard cannot succeed. The ~ts was matched"
- " against the type ~ts\n", [Pat, Type]);
-message_to_string({improper_list_constr, [TlType]}) ->
+ " against the type ~ts\n", [ps(Pat, I), t(Type, I)]);
+message_to_string({improper_list_constr, [TlType]}, I) ->
io_lib:format("Cons will produce an improper list"
- " since its 2nd argument is ~ts\n", [TlType]);
-message_to_string({no_return, [Type|Name]}) ->
+ " since its 2nd argument is ~ts\n", [t(TlType, I)]);
+message_to_string({no_return, [Type|Name]}, _I) ->
NameString =
case Name of
[] -> "The created fun ";
@@ -361,135 +369,150 @@ message_to_string({no_return, [Type|Name]}) ->
only_normal -> NameString ++ "has no local return\n";
both -> NameString ++ "has no local return\n"
end;
-message_to_string({record_constr, [RecConstr, FieldDiffs]}) ->
+message_to_string({record_constr, [RecConstr, FieldDiffs]}, I) ->
io_lib:format("Record construction ~ts violates the"
- " declared type of field ~ts\n", [RecConstr, FieldDiffs]);
-message_to_string({record_constr, [Name, Field, Type]}) ->
+ " declared type of field ~ts\n",
+ [t(RecConstr, I), field_diffs(FieldDiffs, I)]);
+message_to_string({record_constr, [Name, Field, Type]}, I) ->
io_lib:format("Record construction violates the declared type for #~tw{}"
- " since ~ts cannot be of type ~ts\n", [Name, Field, Type]);
-message_to_string({record_matching, [String, Name]}) ->
+ " since ~ts cannot be of type ~ts\n",
+ [Name, ps(Field, I), t(Type, I)]);
+message_to_string({record_matching, [String, Name]}, I) ->
io_lib:format("The ~ts violates the"
- " declared type for #~tw{}\n", [String, Name]);
-message_to_string({record_match, [Pat, Type]}) ->
+ " declared type for #~tw{}\n", [rec_type(String, I), Name]);
+message_to_string({record_match, [Pat, Type]}, I) ->
io_lib:format("Matching of ~ts tagged with a record name violates"
- " the declared type of ~ts\n", [Pat, Type]);
-message_to_string({pattern_match, [Pat, Type]}) ->
- io_lib:format("The ~ts can never match the type ~ts\n", [Pat, Type]);
-message_to_string({pattern_match_cov, [Pat, Type]}) ->
+ " the declared type of ~ts\n", [ps(Pat, I), t(Type, I)]);
+message_to_string({pattern_match, [Pat, Type]}, I) ->
+ io_lib:format("The ~ts can never match the type ~ts\n",
+ [ps(Pat, I), t(Type, I)]);
+message_to_string({pattern_match_cov, [Pat, Type]}, I) ->
io_lib:format("The ~ts can never match since previous"
" clauses completely covered the type ~ts\n",
- [Pat, Type]);
-message_to_string({unmatched_return, [Type]}) ->
+ [ps(Pat, I), t(Type, I)]);
+message_to_string({unmatched_return, [Type]}, I) ->
io_lib:format("Expression produces a value of type ~ts,"
- " but this value is unmatched\n", [Type]);
-message_to_string({unused_fun, [F, A]}) ->
+ " but this value is unmatched\n", [t(Type, I)]);
+message_to_string({unused_fun, [F, A]}, _I) ->
io_lib:format("Function ~tw/~w will never be called\n", [F, A]);
%%----- Warnings for specs and contracts -------------------
-message_to_string({contract_diff, [M, F, _A, Contract, Sig]}) ->
- io_lib:format("Type specification ~w:~tw~ts"
- " is not equal to the success typing: ~w:~tw~ts\n",
- [M, F, Contract, M, F, Sig]);
-message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}) ->
- io_lib:format("Type specification ~w:~tw~ts"
- " is a subtype of the success typing: ~w:~tw~ts\n",
- [M, F, Contract, M, F, Sig]);
-message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}) ->
- io_lib:format("Type specification ~w:~tw~ts"
- " is a supertype of the success typing: ~w:~tw~ts\n",
- [M, F, Contract, M, F, Sig]);
-message_to_string({contract_range, [Contract, M, F, ArgStrings, Line, CRet]}) ->
- io_lib:format("The contract ~w:~tw~ts cannot be right because the inferred"
+message_to_string({contract_diff, [M, F, _A, Contract, Sig]}, I) ->
+ io_lib:format("Type specification ~ts"
+ " is not equal to the success typing: ~ts\n",
+ [con(M, F, Contract, I), con(M, F, Sig, I)]);
+message_to_string({contract_subtype, [M, F, _A, Contract, Sig]}, I) ->
+ io_lib:format("Type specification ~ts"
+ " is a subtype of the success typing: ~ts\n",
+ [con(M, F, Contract, I), con(M, F, Sig, I)]);
+message_to_string({contract_supertype, [M, F, _A, Contract, Sig]}, I) ->
+ io_lib:format("Type specification ~ts"
+ " is a supertype of the success typing: ~ts\n",
+ [con(M, F, Contract, I), con(M, F, Sig, I)]);
+message_to_string({contract_range, [Contract, M, F, ArgStrings, Line, CRet]},
+ I) ->
+ io_lib:format("The contract ~ts cannot be right because the inferred"
" return for ~tw~ts on line ~w is ~ts\n",
- [M, F, Contract, F, ArgStrings, Line, CRet]);
-message_to_string({invalid_contract, [M, F, A, Sig]}) ->
+ [con(M, F, Contract, I), F, a(ArgStrings, I), Line, t(CRet, I)]);
+message_to_string({invalid_contract, [M, F, A, Sig]}, I) ->
io_lib:format("Invalid type specification for function ~w:~tw/~w."
- " The success typing is ~ts\n", [M, F, A, Sig]);
-message_to_string({contract_with_opaque, [M, F, A, OpaqueType, SigType]}) ->
+ " The success typing is ~ts\n", [M, F, A, sig(Sig, I)]);
+message_to_string({contract_with_opaque, [M, F, A, OpaqueType, SigType]},
+ I) ->
io_lib:format("The specification for ~w:~tw/~w"
" has an opaque subtype ~ts which is violated by the"
- " success typing ~ts\n", [M, F, A, OpaqueType, SigType]);
-message_to_string({extra_range, [M, F, A, ExtraRanges, SigRange]}) ->
+ " success typing ~ts\n",
+ [M, F, A, t(OpaqueType, I), sig(SigType, I)]);
+message_to_string({extra_range, [M, F, A, ExtraRanges, SigRange]}, I) ->
io_lib:format("The specification for ~w:~tw/~w states that the function"
" might also return ~ts but the inferred return is ~ts\n",
- [M, F, A, ExtraRanges, SigRange]);
-message_to_string({missing_range, [M, F, A, ExtraRanges, ContrRange]}) ->
+ [M, F, A, t(ExtraRanges, I), t(SigRange, I)]);
+message_to_string({missing_range, [M, F, A, ExtraRanges, ContrRange]}, I) ->
io_lib:format("The success typing for ~w:~tw/~w implies that the function"
" might also return ~ts but the specification return is ~ts\n",
- [M, F, A, ExtraRanges, ContrRange]);
-message_to_string({overlapping_contract, [M, F, A]}) ->
+ [M, F, A, t(ExtraRanges, I), t(ContrRange, I)]);
+message_to_string({overlapping_contract, [M, F, A]}, _I) ->
io_lib:format("Overloaded contract for ~w:~tw/~w has overlapping domains;"
" such contracts are currently unsupported and are simply ignored\n",
[M, F, A]);
-message_to_string({spec_missing_fun, [M, F, A]}) ->
+message_to_string({spec_missing_fun, [M, F, A]}, _I) ->
io_lib:format("Contract for function that does not exist: ~w:~tw/~w\n",
[M, F, A]);
%%----- Warnings for opaque type violations -------------------
-message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}) ->
+message_to_string({call_with_opaque, [M, F, Args, ArgNs, ExpArgs]}, I) ->
io_lib:format("The call ~w:~tw~ts contains ~ts when ~ts\n",
- [M, F, Args, form_positions(ArgNs), form_expected(ExpArgs)]);
-message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}) ->
+ [M, F, a(Args, I), form_positions(ArgNs),
+ form_expected(ExpArgs, I)]);
+message_to_string({call_without_opaque, [M, F, Args, ExpectedTriples]}, I) ->
io_lib:format("The call ~w:~tw~ts does not have ~ts\n",
- [M, F, Args, form_expected_without_opaque(ExpectedTriples)]);
-message_to_string({opaque_eq, [Type, _Op, OpaqueType]}) ->
+ [M, F, a(Args, I),
+ form_expected_without_opaque(ExpectedTriples, I)]);
+message_to_string({opaque_eq, [Type, _Op, OpaqueType]}, I) ->
io_lib:format("Attempt to test for equality between a term of type ~ts"
- " and a term of opaque type ~ts\n", [Type, OpaqueType]);
-message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}) ->
+ " and a term of opaque type ~ts\n",
+ [t(Type, I), t(OpaqueType, I)]);
+message_to_string({opaque_guard, [Arg1, Infix, Arg2, ArgNs]}, I) ->
io_lib:format("Guard test ~ts ~s ~ts contains ~s\n",
- [Arg1, Infix, Arg2, form_positions(ArgNs)]);
-message_to_string({opaque_guard, [Guard, Args]}) ->
+ [a(Arg1, I), Infix, a(Arg2, I), form_positions(ArgNs)]);
+message_to_string({opaque_guard, [Guard, Args]}, I) ->
io_lib:format("Guard test ~w~ts breaks the opacity of its argument\n",
- [Guard, Args]);
-message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}) ->
+ [Guard, a(Args, I)]);
+message_to_string({opaque_match, [Pat, OpaqueType, OpaqueTerm]}, I) ->
Term = if OpaqueType =:= OpaqueTerm -> "the term";
- true -> OpaqueTerm
+ true -> t(OpaqueTerm, I)
end,
- io_lib:format("The attempt to match a term of type ~s against the ~ts"
- " breaks the opacity of ~ts\n", [OpaqueType, Pat, Term]);
-message_to_string({opaque_neq, [Type, _Op, OpaqueType]}) ->
+ io_lib:format("The attempt to match a term of type ~ts against the ~ts"
+ " breaks the opacity of ~ts\n",
+ [t(OpaqueType, I), ps(Pat, I), Term]);
+message_to_string({opaque_neq, [Type, _Op, OpaqueType]}, I) ->
io_lib:format("Attempt to test for inequality between a term of type ~ts"
- " and a term of opaque type ~ts\n", [Type, OpaqueType]);
-message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}) ->
+ " and a term of opaque type ~ts\n",
+ [t(Type, I), t(OpaqueType, I)]);
+message_to_string({opaque_type_test, [Fun, Args, Arg, ArgType]}, I) ->
io_lib:format("The type test ~ts~ts breaks the opacity of the term ~ts~ts\n",
- [Fun, Args, Arg, ArgType]);
-message_to_string({opaque_size, [SizeType, Size]}) ->
+ [Fun, a(Args, I), Arg, t(ArgType, I)]);
+message_to_string({opaque_size, [SizeType, Size]}, I) ->
io_lib:format("The size ~ts breaks the opacity of ~ts\n",
- [SizeType, Size]);
-message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}) ->
+ [t(SizeType, I), c(Size, I)]);
+message_to_string({opaque_call, [M, F, Args, Culprit, OpaqueType]}, I) ->
io_lib:format("The call ~s:~ts~ts breaks the opacity of the term ~ts :: ~ts\n",
- [M, F, Args, Culprit, OpaqueType]);
+ [M, F, a(Args, I), c(Culprit, I), t(OpaqueType, I)]);
%%----- Warnings for concurrency errors --------------------
-message_to_string({race_condition, [M, F, Args, Reason]}) ->
- io_lib:format("The call ~w:~tw~ts ~ts\n", [M, F, Args, Reason]);
+message_to_string({race_condition, [M, F, Args, Reason]}, I) ->
+ %% There is a possibly huge type in Reason.
+ io_lib:format("The call ~w:~tw~ts ~ts\n", [M, F, a(Args, I), Reason]);
%%----- Warnings for behaviour errors --------------------
-message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}) ->
- io_lib:format("The inferred return type of ~tw/~w (~ts) has nothing in"
+message_to_string({callback_type_mismatch, [B, F, A, ST, CT]}, I) ->
+ io_lib:format("The inferred return type of ~tw/~w ~ts has nothing in"
" common with ~ts, which is the expected return type for"
- " the callback of the ~w behaviour\n", [F, A, ST, CT, B]);
-message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}) ->
+ " the callback of the ~w behaviour\n",
+ [F, A, t("("++ST++")", I), t(CT, I), B]);
+message_to_string({callback_arg_type_mismatch, [B, F, A, N, ST, CT]}, I) ->
io_lib:format("The inferred type for the ~s argument of ~tw/~w (~ts) is"
" not a supertype of ~ts, which is expected type for this"
" argument in the callback of the ~w behaviour\n",
- [ordinal(N), F, A, ST, CT, B]);
-message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}) ->
+ [ordinal(N), F, A, t(ST, I), t(CT, I), B]);
+message_to_string({callback_spec_type_mismatch, [B, F, A, ST, CT]}, I) ->
io_lib:format("The return type ~ts in the specification of ~tw/~w is not a"
" subtype of ~ts, which is the expected return type for the"
- " callback of the ~w behaviour\n", [ST, F, A, CT, B]);
-message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]}) ->
+ " callback of the ~w behaviour\n",
+ [t(ST, I), F, A, t(CT, I), B]);
+message_to_string({callback_spec_arg_type_mismatch, [B, F, A, N, ST, CT]},
+ I) ->
io_lib:format("The specified type for the ~ts argument of ~tw/~w (~ts) is"
" not a supertype of ~ts, which is expected type for this"
" argument in the callback of the ~w behaviour\n",
- [ordinal(N), F, A, ST, CT, B]);
-message_to_string({callback_missing, [B, F, A]}) ->
+ [ordinal(N), F, A, t(ST, I), t(CT, I), B]);
+message_to_string({callback_missing, [B, F, A]}, _I) ->
io_lib:format("Undefined callback function ~tw/~w (behaviour ~w)\n",
[F, A, B]);
-message_to_string({callback_info_missing, [B]}) ->
+message_to_string({callback_info_missing, [B]}, _I) ->
io_lib:format("Callback info about the ~w behaviour is not available\n", [B]);
%%----- Warnings for unknown functions, types, and behaviours -------------
-message_to_string({unknown_type, {M, F, A}}) ->
+message_to_string({unknown_type, {M, F, A}}, _I) ->
io_lib:format("Unknown type ~w:~tw/~w", [M, F, A]);
-message_to_string({unknown_function, {M, F, A}}) ->
+message_to_string({unknown_function, {M, F, A}}, _I) ->
io_lib:format("Unknown function ~w:~tw/~w", [M, F, A]);
-message_to_string({unknown_behaviour, B}) ->
+message_to_string({unknown_behaviour, B}, _I) ->
io_lib:format("Unknown behaviour ~w", [B]).
%%-----------------------------------------------------------------------------
@@ -497,7 +520,7 @@ message_to_string({unknown_behaviour, B}) ->
%%-----------------------------------------------------------------------------
call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
- {IsOverloaded, Contract}) ->
+ {IsOverloaded, Contract}, I) ->
PositionString = form_position_string(ArgNs),
case FailReason of
only_sig ->
@@ -505,24 +528,25 @@ call_or_apply_to_string(ArgNs, FailReason, SigArgs, SigRet,
true ->
%% We do not know which argument(s) caused the failure
io_lib:format("will never return since the success typing arguments"
- " are ~ts\n", [SigArgs]);
+ " are ~ts\n", [t(SigArgs, I)]);
false ->
io_lib:format("will never return since it differs in the ~s argument"
" from the success typing arguments: ~ts\n",
- [PositionString, SigArgs])
+ [PositionString, t(SigArgs, I)])
end;
only_contract ->
case (ArgNs =:= []) orelse IsOverloaded of
true ->
%% We do not know which arguments caused the failure
- io_lib:format("breaks the contract ~ts\n", [Contract]);
+ io_lib:format("breaks the contract ~ts\n", [sig(Contract, I)]);
false ->
io_lib:format("breaks the contract ~ts in the ~s argument\n",
- [Contract, PositionString])
+ [sig(Contract, I), PositionString])
end;
both ->
io_lib:format("will never return since the success typing is ~ts -> ~ts"
- " and the contract is ~ts\n", [SigArgs, SigRet, Contract])
+ " and the contract is ~ts\n",
+ [t(SigArgs, I), t(SigRet, I), sig(Contract, I)])
end.
form_positions(ArgNs) ->
@@ -537,24 +561,27 @@ form_positions(ArgNs) ->
%% We know which positions N are to blame;
%% the list of triples will never be empty.
-form_expected_without_opaque([{N, T, TStr}]) ->
+form_expected_without_opaque([{N, T, TStr}], I) ->
case erl_types:t_is_opaque(T) of
true ->
- io_lib:format("an opaque term of type ~ts as ", [TStr]);
+ io_lib:format("an opaque term of type ~ts as ", [t(TStr, I)]);
false ->
- io_lib:format("a term of type ~ts (with opaque subterms) as ", [TStr])
+ io_lib:format("a term of type ~ts (with opaque subterms) as ",
+ [t(TStr, I)])
end ++ form_position_string([N]) ++ " argument";
-form_expected_without_opaque(ExpectedTriples) -> %% TODO: can do much better here
+form_expected_without_opaque(ExpectedTriples, _I) -> %% TODO: can do much better here
{ArgNs, _Ts, _TStrs} = lists:unzip3(ExpectedTriples),
"opaque terms as " ++ form_position_string(ArgNs) ++ " arguments".
-form_expected(ExpectedArgs) ->
+form_expected(ExpectedArgs, I) ->
case ExpectedArgs of
[T] ->
TS = erl_types:t_to_string(T),
case erl_types:t_is_opaque(T) of
- true -> io_lib:format("an opaque term of type ~ts is expected", [TS]);
- false -> io_lib:format("a structured term of type ~ts is expected", [TS])
+ true -> io_lib:format("an opaque term of type ~ts is expected",
+ [t(TS, I)]);
+ false -> io_lib:format("a structured term of type ~ts is expected",
+ [t(TS, I)])
end;
[_,_|_] -> "terms of different types are expected in these positions"
end.
@@ -574,3 +601,161 @@ ordinal(1) -> "1st";
ordinal(2) -> "2nd";
ordinal(3) -> "3rd";
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).
+
+-define(IND, 10).
+
+con(M, F, Src, I) ->
+ S = sig(Src, I),
+ io_lib:format("~w:~tw~ts", [M, F, S]).
+
+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).
+
+%% Argument(list)s are a mix of types and Erlang code. Note: sometimes
+%% (contract_range, call_without_opaque, opaque_type_test), the initial
+%% newline is a bit out of place.
+a(""=Args, _I) ->
+ Args;
+a(Args, I) ->
+ t(Args, I).
+
+c(Cerl, _I) ->
+ Cerl.
+
+field_diffs(Src, false) ->
+ Src;
+field_diffs(Src, true) ->
+ Fields = string:split(Src, " and ", all),
+ lists:join(" and ", [field_diff(Field) || Field <- Fields]).
+
+field_diff(Field) ->
+ [F | Ts] = string:split(Field, "::", all),
+ F ++ " ::" ++ t(lists:flatten(lists:join("::", Ts)), true).
+
+rec_type("record "++Src, I) ->
+ "record " ++ t(Src, I).
+
+%% "variable"/"pattern" ++ cerl
+ps("pattern "++Src, I) ->
+ "pattern " ++ t(Src, I);
+ps("variable "++_=Src, _I) ->
+ Src;
+ps("record field"++Rest, I) ->
+ [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.
+t(Src, false) ->
+ Src;
+t("("++_=Src, true) ->
+ ts(Src);
+t(Src, true) ->
+ %% Binary types and products both start with a $<.
+ try parse_type_or_literal(Src) of
+ TypeOrLiteral ->
+ indentation(?IND) ++ pp_type(TypeOrLiteral)
+ catch
+ _:_ ->
+ ts(Src)
+ end.
+
+ts(Src) ->
+ Ind = indentation(?IND),
+ [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.
+
+indentation(I) ->
+ [$\n | lists:duplicate(I, $\s)].
+
+pp_type(Type) ->
+ Form = {attribute, erl_anno:new(0), type, {t, Type, []}},
+ TypeDef = erl_pp:form(Form, [{quote_singleton_atom_types, true}]),
+ {match, [S]} = re:run(TypeDef, <<"::\\s*(.*)\\.\\n*">>,
+ [{capture, all_but_first, list}, dotall]),
+ S.
+
+pp_spec(Spec) ->
+ Form = {attribute, erl_anno:new(0), spec, {{a,b,0}, Spec}},
+ Sig = erl_pp:form(Form, [{quote_singleton_atom_types, true}]),
+ {match, [S]} = re:run(Sig, <<"-spec a:b\\s*(.*)\\.\\n*">>,
+ [{capture, all_but_first, list}, dotall]),
+ S.
+
+parse_types_and_literals(Src) ->
+ {ok, Tokens, _EndLocation} = erl_scan:string(Src),
+ [parse_a_type_or_literal(Ts) || Ts <- types(Tokens)].
+
+parse_type_or_literal(Src) ->
+ {ok, Tokens, _EndLocation} = erl_scan:string(Src),
+ parse_a_type_or_literal(Tokens).
+
+parse_a_type_or_literal(Ts0) ->
+ L = erl_anno:new(1),
+ Ts = Ts0 ++ [{dot,L}],
+ Tokens = [{'-',L}, {atom,L,type}, {atom,L,t}, {'(',L}, {')',L},
+ {'::',L}] ++ Ts,
+ case erl_parse:parse_form(Tokens) of
+ {ok, {attribute, _, type, {t, Type, []}}} ->
+ Type;
+ {error, _} ->
+ %% literal
+ {ok, [T]} = erl_parse:parse_exprs(Ts),
+ T
+ end.
+
+types([]) -> [];
+types(Ts) ->
+ {Ts0, Ts1} = one_type(Ts, [], []),
+ [Ts0 | types(Ts1)].
+
+one_type([], [], Ts) ->
+ {lists:reverse(Ts), []};
+one_type([{',', _Lc}|Toks], [], Ts0) ->
+ {lists:reverse(Ts0), Toks};
+one_type([{')', Lrp}|Toks], [], Ts0) ->
+ {lists:reverse(Ts0), [{')', Lrp}|Toks]};
+one_type([{'(', Llp}|Toks], E, Ts0) ->
+ one_type(Toks, [')'|E], [{'(', Llp}|Ts0]);
+one_type([{'<<', Lls}|Toks], E, Ts0) ->
+ one_type(Toks, ['>>'|E], [{'<<', Lls}|Ts0]);
+one_type([{'[', Lls}|Toks], E, Ts0) ->
+ one_type(Toks, [']'|E], [{'[', Lls}|Ts0]);
+one_type([{'{', Llc}|Toks], E, Ts0) ->
+ one_type(Toks, ['}'|E], [{'{', Llc}|Ts0]);
+one_type([{Rb, Lrb}|Toks], [Rb|E], Ts0) ->
+ one_type(Toks, E, [{Rb, Lrb}|Ts0]);
+one_type([T|Toks], E, Ts0) ->
+ one_type(Toks, E, [T|Ts0]).
diff --git a/lib/dialyzer/src/dialyzer.hrl b/lib/dialyzer/src/dialyzer.hrl
index e7cf2860b7..4a12b9b671 100644
--- a/lib/dialyzer/src/dialyzer.hrl
+++ b/lib/dialyzer/src/dialyzer.hrl
@@ -108,6 +108,7 @@
-type dial_options() :: [dial_option()].
-type fopt() :: 'basename' | 'fullpath'.
-type format() :: 'formatted' | 'raw'.
+-type iopt() :: boolean().
-type label() :: non_neg_integer().
-type dial_warn_tags():: ordsets:ordset(dial_warn_tag()).
-type rep_mode() :: 'quiet' | 'normal' | 'verbose'.
@@ -119,6 +120,8 @@
%% Record declarations used by various files
%%--------------------------------------------------------------------
+-define(INDENT_OPT, true).
+
-type doc_plt() :: 'undefined' | dialyzer_plt:plt().
-record(analysis, {analysis_pid :: pid() | 'undefined',
@@ -154,6 +157,7 @@
output_file = none :: 'none' | file:filename(),
output_format = formatted :: format(),
filename_opt = basename :: fopt(),
+ indent_opt = ?INDENT_OPT :: iopt(),
callgraph_file = "" :: file:filename(),
check_plt = true :: boolean(),
solvers = [] :: [solver()]}).
diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl
index 1e06d6e974..f887f661bd 100644
--- a/lib/dialyzer/src/dialyzer_cl.erl
+++ b/lib/dialyzer/src/dialyzer_cl.erl
@@ -40,6 +40,7 @@
output = standard_io :: io:device(),
output_format = formatted :: format(),
filename_opt = basename :: fopt(),
+ indent_opt = ?INDENT_OPT :: iopt(),
output_plt = none :: 'none' | file:filename(),
plt_info = none :: 'none' | dialyzer_plt:plt_info(),
report_mode = normal :: rep_mode(),
@@ -588,8 +589,11 @@ new_state() ->
init_output(State0, #options{output_file = OutFile,
output_format = OutFormat,
- filename_opt = FOpt}) ->
- State = State0#cl_state{output_format = OutFormat, filename_opt = FOpt},
+ filename_opt = FOpt,
+ indent_opt = IOpt}) ->
+ State = State0#cl_state{output_format = OutFormat,
+ filename_opt = FOpt,
+ indent_opt = IOpt},
case OutFile =:= none of
true ->
State;
@@ -818,6 +822,7 @@ print_warnings(#cl_state{stored_warnings = []}) ->
print_warnings(#cl_state{output = Output,
output_format = Format,
filename_opt = FOpt,
+ indent_opt = IOpt,
stored_warnings = Warnings}) ->
PrWarnings = process_warnings(Warnings),
case PrWarnings of
@@ -825,7 +830,8 @@ print_warnings(#cl_state{output = Output,
[_|_] ->
S = case Format of
formatted ->
- [dialyzer:format_warning(W, FOpt) || W <- PrWarnings];
+ Opts = [{filename_opt, FOpt}, {indent_opt, IOpt}],
+ [dialyzer:format_warning(W, Opts) || W <- PrWarnings];
raw ->
[io_lib:format("~tp. \n",
[W]) || W <- set_warning_id(PrWarnings)]
diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl
index f21eaed087..280cae81d5 100644
--- a/lib/dialyzer/src/dialyzer_cl_parse.erl
+++ b/lib/dialyzer/src/dialyzer_cl_parse.erl
@@ -134,6 +134,9 @@ cl(["--raw"|T]) ->
cl(["--fullpath"|T]) ->
put(dialyzer_filename_opt, fullpath),
cl(T);
+cl(["--no_indentation"|T]) ->
+ put(dialyzer_indent_opt, false),
+ cl(T);
cl(["-pa", Path|T]) ->
case code:add_patha(Path) of
true -> cl(T);
@@ -254,6 +257,7 @@ init() ->
put(dialyzer_options_files, DefaultOpts#options.files),
put(dialyzer_output_format, formatted),
put(dialyzer_filename_opt, basename),
+ put(dialyzer_indent_opt, ?INDENT_OPT),
put(dialyzer_options_check_plt, DefaultOpts#options.check_plt),
put(dialyzer_timing, DefaultOpts#options.timing),
put(dialyzer_solvers, DefaultOpts#options.solvers),
@@ -295,6 +299,7 @@ cl_options() ->
{output_file, get(dialyzer_output)},
{output_format, get(dialyzer_output_format)},
{filename_opt, get(dialyzer_filename_opt)},
+ {indent_opt, get(dialyzer_indent_opt)},
{analysis_type, get(dialyzer_options_analysis_type)},
{get_warnings, get(dialyzer_options_get_warnings)},
{timing, get(dialyzer_timing)},
@@ -361,7 +366,7 @@ help_message() ->
[--build_plt] [--add_to_plt] [--remove_from_plt]
[--check_plt] [--no_check_plt] [--plt_info] [--get_warnings]
[--dump_callgraph file] [--no_native] [--fullpath]
- [--statistics] [--no_native_cache]
+ [--no_indentation] [--statistics] [--no_native_cache]
Options:
files_or_dirs (for backwards compatibility also as: -c files_or_dirs)
Use Dialyzer from the command line to detect defects in the
@@ -473,6 +478,9 @@ Options:
caching.
--fullpath
Display the full path names of files for which warnings are emitted.
+ --no_indentation
+ Do not indent contracts and success typings. Note that this option has
+ no effect when combined with the --raw option.
--gui
Use the GUI.
diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl
index 9c36d745c3..17b2168852 100644
--- a/lib/dialyzer/src/dialyzer_contracts.erl
+++ b/lib/dialyzer/src/dialyzer_contracts.erl
@@ -911,7 +911,6 @@ is_remote_types_related(Contract, CSig, Sig, MFA, RecDict) ->
t_from_forms_without_remote([{FType, []}], MFA, RecDict) ->
Site = {spec, MFA},
- %% FIXME
Type1 = erl_types:t_from_form_without_remote(FType, Site, RecDict),
{ok, erl_types:subst_all_vars_to_any(Type1)};
t_from_forms_without_remote([{_FType, _Constrs}], _MFA, _RecDict) ->
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl
index f7aa167f5c..55c77814f8 100644
--- a/lib/dialyzer/src/dialyzer_dataflow.erl
+++ b/lib/dialyzer/src/dialyzer_dataflow.erl
@@ -3644,14 +3644,15 @@ format_args(ArgList0, TypeList, State) ->
"(" ++ format_args_1(ArgList, TypeList, State) ++ ")".
format_args_1([Arg], [Type], State) ->
- format_arg(Arg) ++ format_type(Type, State);
+ format_arg_1(Arg, Type, State);
format_args_1([Arg|Args], [Type|Types], State) ->
- String =
- case cerl:is_literal(Arg) of
- true -> format_cerl(Arg);
- false -> format_arg(Arg) ++ format_type(Type, State)
- end,
- String ++ "," ++ format_args_1(Args, Types, State).
+ format_arg_1(Arg, Type, State) ++ "," ++ format_args_1(Args, Types, State).
+
+format_arg_1(Arg, Type, State) ->
+ case cerl:is_literal(Arg) of
+ true -> format_cerl(Arg);
+ false -> format_arg(Arg) ++ format_type(Type, State)
+ end.
format_arg(Arg) ->
Default = "",
diff --git a/lib/dialyzer/src/dialyzer_gui_wx.erl b/lib/dialyzer/src/dialyzer_gui_wx.erl
index b8414b7d8b..f47d90b91f 100644
--- a/lib/dialyzer/src/dialyzer_gui_wx.erl
+++ b/lib/dialyzer/src/dialyzer_gui_wx.erl
@@ -1135,7 +1135,9 @@ handle_help(State, Title, Txt) ->
add_warnings(#gui_state{warnings_box = WarnBox,
rawWarnings = RawWarns} = State, Warnings) ->
NewRawWarns = RawWarns ++ Warnings,
- WarnList = [dialyzer:format_warning(W) -- "\n" || W <- NewRawWarns],
+ %% The indentation cannot be turned off.
+ WarnList = [string:trim(dialyzer:format_warning(W), trailing) ||
+ W <- NewRawWarns],
wxListBox:set(WarnBox, WarnList),
State#gui_state{rawWarnings = NewRawWarns}.
diff --git a/lib/dialyzer/src/dialyzer_options.erl b/lib/dialyzer/src/dialyzer_options.erl
index 03040f8e38..3b30036c1c 100644
--- a/lib/dialyzer/src/dialyzer_options.erl
+++ b/lib/dialyzer/src/dialyzer_options.erl
@@ -177,6 +177,8 @@ build_options([{OptionName, Value} = Term|Rest], Options) ->
filename_opt ->
assert_filename_opt(Value),
build_options(Rest, Options#options{filename_opt = Value});
+ indent_opt ->
+ build_options(Rest, Options#options{indent_opt = Value});
output_plt ->
assert_filename(Value),
build_options(Rest, Options#options{output_plt = Value});
diff --git a/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options b/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options
index 365b4798c5..83d4a0ec35 100644
--- a/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/behaviour_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, []}.
+{dialyzer_options, [{indent_opt, false}]}.
{time_limit, 5}.
diff --git a/lib/dialyzer/test/callgraph_SUITE_data/dialyzer_options b/lib/dialyzer/test/callgraph_SUITE_data/dialyzer_options
index 50991c9bc5..8413436b67 100644
--- a/lib/dialyzer/test/callgraph_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/callgraph_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, []}.
+{dialyzer_options, [{indent_opt, false}]}.
diff --git a/lib/dialyzer/test/dialyzer_common.erl b/lib/dialyzer/test/dialyzer_common.erl
index 1a8403f486..beaf126344 100644
--- a/lib/dialyzer/test/dialyzer_common.erl
+++ b/lib/dialyzer/test/dialyzer_common.erl
@@ -147,7 +147,8 @@ check(TestCase, Opts, Dir, OutDir) ->
try dialyzer:run([{files, Files},{from, src_code},{init_plt, PltFilename},
{check_plt, false}|ProperOpts]) of
RawWarns ->
- Warns = lists:sort([dialyzer:format_warning(W) || W <- RawWarns]),
+ Warns = lists:sort([dialyzer:format_warning(W, ProperOpts) ||
+ W <- RawWarns]),
case Warns of
[] -> ok;
_ ->
diff --git a/lib/dialyzer/test/indent2_SUITE_data/dialyzer_options b/lib/dialyzer/test/indent2_SUITE_data/dialyzer_options
new file mode 100644
index 0000000000..ee07090337
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/dialyzer_options
@@ -0,0 +1 @@
+{dialyzer_options, [{warnings, [no_unused, no_return, specdiffs]}]}.
diff --git a/lib/dialyzer/test/indent2_SUITE_data/results/arr b/lib/dialyzer/test/indent2_SUITE_data/results/arr
new file mode 100644
index 0000000000..77e67f0cab
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/results/arr
@@ -0,0 +1,15 @@
+
+arr.erl:14: Type specification arr:test2
+ (array:array(T), non_neg_integer(), T) -> array:array(T) is a supertype of the success typing: arr:test2
+ (array:array(_), pos_integer(), _) -> array:array(_)
+arr.erl:24: Type specification arr:test4
+ (array:array(T), non_neg_integer(), _) -> array:array(T) is a supertype of the success typing: arr:test4
+ (array:array(_), pos_integer(), _) -> array:array(_)
+arr.erl:29: Type specification arr:test5
+ (array:array(T), non_neg_integer(), T) -> array:array(T) is a supertype of the success typing: arr:test5
+ (array:array(_), non_neg_integer(), integer()) ->
+ array:array(_)
+arr.erl:37: Type specification arr:test6
+ (array:array(integer()), non_neg_integer(), integer()) ->
+ array:array(any()) is not equal to the success typing: arr:test6
+ (array:array(_), non_neg_integer(), _) -> array:array(_)
diff --git a/lib/dialyzer/test/indent2_SUITE_data/results/iodata b/lib/dialyzer/test/indent2_SUITE_data/results/iodata
new file mode 100644
index 0000000000..d95551d330
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/results/iodata
@@ -0,0 +1,24 @@
+
+iodata.erl:7: The specification for iodata:encode/2 states that the function might also return
+ binary() but the inferred return is
+ nonempty_maybe_improper_list(<<_:8, _:_*8>> |
+ nonempty_maybe_improper_list(<<_:8,
+ _:_*8>> |
+ nonempty_maybe_improper_list(any(),
+ <<_:8,
+ _:_*8>> |
+ []) |
+ byte(),
+ <<_:8,
+ _:_*8>> |
+ []) |
+ integer(),
+ <<_:8, _:_*8>> | []) |
+ integer()
+iodata.erl:7: The success typing for iodata:encode/2 implies that the function might also return
+ integer() but the specification return is
+ binary() |
+ maybe_improper_list(binary() |
+ maybe_improper_list(any(), binary() | []) |
+ byte(),
+ binary() | [])
diff --git a/lib/dialyzer/test/indent2_SUITE_data/results/remote b/lib/dialyzer/test/indent2_SUITE_data/results/remote
new file mode 100644
index 0000000000..6decec6c6a
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/results/remote
@@ -0,0 +1,25 @@
+
+remotes1.erl:17: The specification for remotes1:foo5/1 states that the function might also return
+ 'ko' but the inferred return is
+ 'ok'
+remotes1.erl:20: Type specification remotes1:foo6
+ ('ok' | 'ko') -> 'ok' is a supertype of the success typing: remotes1:foo6
+ ('ok') -> 'ok'
+remotes1.erl:25: The specification for remotes1:foo7/1 states that the function might also return
+ 'ko' but the inferred return is
+ 'ok'
+remotes1.erl:28: Type specification remotes1:foo8
+ (local_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo8
+ ('ok') -> 'ok'
+remotes1.erl:33: The specification for remotes1:foo9/1 states that the function might also return
+ 'ko' but the inferred return is
+ 'ok'
+remotes1.erl:36: Type specification remotes1:foo10
+ (local_and_known_remote_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo10
+ ('ok') -> 'ok'
+remotes1.erl:49: Type specification remotes1:foo13
+ ('ok') -> local_and_unknown_remote_type_42() is a supertype of the success typing: remotes1:foo13
+ ('ok') -> 'ok'
+remotes1.erl:52: Type specification remotes1:foo14
+ (local_and_unknown_remote_type_42()) -> 'ok' is a supertype of the success typing: remotes1:foo14
+ ('ok') -> 'ok'
diff --git a/lib/dialyzer/test/indent2_SUITE_data/src/arr.erl b/lib/dialyzer/test/indent2_SUITE_data/src/arr.erl
new file mode 100644
index 0000000000..3b265ccec2
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/src/arr.erl
@@ -0,0 +1,41 @@
+-module(arr).
+
+%% http://erlang.org/pipermail/erlang-questions/2014-August/080445.html
+
+-define(A, array).
+
+-export([test/3, test2/3, test3/3, test4/3, test5/3, test6/3]).
+
+-spec test(?A:array(T), non_neg_integer(), T) -> ?A:array(T).
+
+test(Array, N, Value) ->
+ ?A:set(N, Value, Array).
+
+-spec test2(?A:array(T), non_neg_integer(), T) -> ?A:array(T).
+
+test2(Array, N, Value) when N > 0 ->
+ ?A:set(N, Value, Array).
+
+-spec test3(?A:array(T), non_neg_integer(), _) -> ?A:array(T).
+
+test3(Array, N, Value) ->
+ ?A:set(N, Value, Array).
+
+-spec test4(?A:array(T), non_neg_integer(), _) -> ?A:array(T).
+
+test4(Array, N, Value) when N > 0 ->
+ ?A:set(N, Value, Array).
+
+-spec test5(?A:array(T), non_neg_integer(), T) -> ?A:array(T).
+
+test5(Array, N, Value) when is_integer(Value) ->
+ ?A:set(N, Value, Array).
+
+%% One would ideally want a warning also for test6(), but the current
+%% analysis of parametrized opaque types is not strong enough to
+%% discover this.
+-spec test6(?A:array(integer()), non_neg_integer(), integer()) ->
+ ?A:array(any()).
+
+test6(Array, N, Value) ->
+ ?A:set(N, Value, Array).
diff --git a/lib/dialyzer/test/indent2_SUITE_data/src/iodata.erl b/lib/dialyzer/test/indent2_SUITE_data/src/iodata.erl
new file mode 100644
index 0000000000..caa44f6c91
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/src/iodata.erl
@@ -0,0 +1,41 @@
+-module(iodata).
+
+%% A small part of beam_asm.
+
+-export([encode/2]).
+
+-spec encode(non_neg_integer(), integer()) -> iodata(). % extra range binary()
+
+encode(Tag, N) when Tag >= 0, N < 0 ->
+ encode1(Tag, negative_to_bytes(N));
+encode(Tag, N) when Tag >= 0, N < 16 ->
+ (N bsl 4) bor Tag; % not in the specification
+encode(Tag, N) when Tag >= 0, N < 16#800 ->
+ [((N bsr 3) band 2#11100000) bor Tag bor 2#00001000, N band 16#ff];
+encode(Tag, N) when Tag >= 0 ->
+ encode1(Tag, to_bytes(N)).
+
+encode1(Tag, Bytes) ->
+ case iolist_size(Bytes) of
+ Num when 2 =< Num, Num =< 8 ->
+ [((Num-2) bsl 5) bor 2#00011000 bor Tag| Bytes];
+ Num when 8 < Num ->
+ [2#11111000 bor Tag, encode(0, Num-9)| Bytes]
+ end.
+
+to_bytes(N) ->
+ Bin = binary:encode_unsigned(N),
+ case Bin of
+ <<0:1,_/bits>> -> Bin;
+ <<1:1,_/bits>> -> [0,Bin]
+ end.
+
+negative_to_bytes(N) when N >= -16#8000 ->
+ <<N:16>>;
+negative_to_bytes(N) ->
+ Bytes = byte_size(binary:encode_unsigned(-N)),
+ Bin = <<N:Bytes/unit:8>>,
+ case Bin of
+ <<0:1,_/bits>> -> [16#ff,Bin];
+ <<1:1,_/bits>> -> Bin
+ end.
diff --git a/lib/dialyzer/test/indent2_SUITE_data/src/remote/remotes1.erl b/lib/dialyzer/test/indent2_SUITE_data/src/remote/remotes1.erl
new file mode 100644
index 0000000000..b722495095
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/src/remote/remotes1.erl
@@ -0,0 +1,61 @@
+-module(remotes1).
+
+-compile(export_all).
+
+-spec foo1(some_unknown_remote:type42()) -> ok.
+foo1(ok) -> ok.
+
+-spec foo2(ok) -> some_unknown_remote:type42().
+foo2(ok) -> ok.
+
+-spec foo3(some_known_remote:type42()) -> ok.
+foo3(ok) -> ok.
+
+-spec foo4(ok) -> some_known_remote:type42().
+foo4(ok) -> ok.
+
+-spec foo5(ok|ko) -> ok|ko.
+foo5(ok) -> ok.
+
+-spec foo6(ok|ko) -> ok.
+foo6(ok) -> ok.
+
+-type local_type_42() :: ok | ko.
+
+-spec foo7(ok) -> local_type_42().
+foo7(ok) -> ok.
+
+-spec foo8(local_type_42()) -> ok.
+foo8(ok) -> ok.
+
+-type local_and_known_remote_type_42() :: some_known_remote:type42() | ok | ko.
+
+-spec foo9(ok) -> local_and_known_remote_type_42().
+foo9(ok) -> ok.
+
+-spec foo10(local_and_known_remote_type_42()) -> ok.
+foo10(ok) -> ok.
+
+-type local_and_ok_known_remote_type_42() :: some_known_remote:type42() | ok.
+
+-spec foo11(ok) -> local_and_ok_known_remote_type_42().
+foo11(ok) -> ok.
+
+-spec foo12(local_and_ok_known_remote_type_42()) -> ok.
+foo12(ok) -> ok.
+
+-type local_and_unknown_remote_type_42() :: some_unknown_remote:type42() | ok | ko.
+
+-spec foo13(ok) -> local_and_unknown_remote_type_42().
+foo13(ok) -> ok.
+
+-spec foo14(local_and_unknown_remote_type_42()) -> ok.
+foo14(ok) -> ok.
+
+-type local_and_ok_unknown_remote_type_42() :: some_unknown_remote:type42() | ok.
+
+-spec foo15(ok) -> local_and_ok_unknown_remote_type_42().
+foo15(ok) -> ok.
+
+-spec foo16(local_and_ok_unknown_remote_type_42()) -> ok.
+foo16(ok) -> ok.
diff --git a/lib/dialyzer/test/indent2_SUITE_data/src/remote/some_known_remote.erl b/lib/dialyzer/test/indent2_SUITE_data/src/remote/some_known_remote.erl
new file mode 100644
index 0000000000..437f1e7826
--- /dev/null
+++ b/lib/dialyzer/test/indent2_SUITE_data/src/remote/some_known_remote.erl
@@ -0,0 +1,5 @@
+-module(some_known_remote).
+
+-export_type([type42/0]).
+
+-type type42() :: ok | ko.
diff --git a/lib/dialyzer/test/indent_SUITE_data/dialyzer_options b/lib/dialyzer/test/indent_SUITE_data/dialyzer_options
new file mode 100644
index 0000000000..7c088f9a65
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/dialyzer_options
@@ -0,0 +1 @@
+{dialyzer_options, [{warnings, [no_unused, no_return, race_conditions]}]}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/abs b/lib/dialyzer/test/indent_SUITE_data/results/abs
new file mode 100644
index 0000000000..ac663a4e80
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/abs
@@ -0,0 +1,13 @@
+
+abs.erl:16: The pattern
+ 'true' can never match the type
+ 'false'
+abs.erl:27: The pattern
+ 'true' can never match the type
+ 'false'
+abs.erl:37: The pattern
+ 'true' can never match the type
+ 'false'
+abs.erl:49: The pattern
+ 'true' can never match the type
+ 'false'
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/app_call b/lib/dialyzer/test/indent_SUITE_data/results/app_call
new file mode 100644
index 0000000000..729587b5c6
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/app_call
@@ -0,0 +1,9 @@
+
+app_call.erl:6: The call M:'foo'
+ () requires that M is of type
+ atom() not
+ 42
+app_call.erl:9: The call 'mod':F
+ () requires that F is of type
+ atom() not
+ {'gazonk', []}
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/arr b/lib/dialyzer/test/indent_SUITE_data/results/arr
new file mode 100644
index 0000000000..9497d12eec
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/arr
@@ -0,0 +1,4 @@
+
+arr.erl:14: Type specification arr:test2(array:array(T),non_neg_integer(),T) -> array:array(T) is a supertype of the success typing: arr:test2(array:array(_),pos_integer(),_) -> array:array(_)
+arr.erl:24: Type specification arr:test4(array:array(T),non_neg_integer(),_) -> array:array(T) is a supertype of the success typing: arr:test4(array:array(_),pos_integer(),_) -> array:array(_)
+arr.erl:29: Type specification arr:test5(array:array(T),non_neg_integer(),T) -> array:array(T) is a supertype of the success typing: arr:test5(array:array(_),non_neg_integer(),integer()) -> array:array(_)
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/blame_contract_range b/lib/dialyzer/test/indent_SUITE_data/results/blame_contract_range
new file mode 100644
index 0000000000..287d23d91f
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/blame_contract_range
@@ -0,0 +1,8 @@
+
+blame_contract_range.erl:14: The contract blame_contract_range:bar
+ (atom()) -> 'a' cannot be right because the inferred return for bar
+ ('b') on line 12 is
+ 'b'
+blame_contract_range.erl:15: The pattern
+ 'a' can never match the type
+ 'b'
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/bs_fail_constr b/lib/dialyzer/test/indent_SUITE_data/results/bs_fail_constr
new file mode 100644
index 0000000000..86f1329bf3
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/bs_fail_constr
@@ -0,0 +1,9 @@
+
+bs_fail_constr.erl:12: Binary construction will fail since the size field S in segment 42:S has type
+ neg_integer()
+bs_fail_constr.erl:15: Binary construction will fail since the value field V in segment V/utf32 has type
+ float()
+bs_fail_constr.erl:6: Binary construction will fail since the value field V in segment V has type
+ float()
+bs_fail_constr.erl:9: Binary construction will fail since the value field V in segment V/binary has type
+ atom()
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs b/lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs
new file mode 100644
index 0000000000..dd9d3397d2
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/callbacks_and_specs
@@ -0,0 +1,23 @@
+
+my_callbacks_wrong.erl:26: The return type
+ #state{parent :: pid(),
+ status :: 'closed' | 'init' | 'open',
+ subscribe :: [{pid(), integer()}],
+ counter :: integer()} in the specification of callback_init/1 is not a subtype of
+ {'ok', _}, which is the expected return type for the callback of the my_behaviour behaviour
+my_callbacks_wrong.erl:28: The inferred return type of callback_init/1
+ (#state{parent :: pid(),
+ status :: 'init',
+ subscribe :: [],
+ counter :: 1}) has nothing in common with
+ {'ok', _}, which is the expected return type for the callback of the my_behaviour behaviour
+my_callbacks_wrong.erl:30: The return type
+ {'reply',
+ #state{parent :: pid(),
+ status :: 'closed' | 'init' | 'open',
+ subscribe :: [{pid(), integer()}],
+ counter :: integer()}} in the specification of callback_cast/3 is not a subtype of
+ {'noreply', _}, which is the expected return type for the callback of the my_behaviour behaviour
+my_callbacks_wrong.erl:39: The specified type for the 2nd argument of callback_call/3 (
+ atom()) is not a supertype of
+ pid(), which is expected type for this argument in the callback of the my_behaviour behaviour
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/contract3 b/lib/dialyzer/test/indent_SUITE_data/results/contract3
new file mode 100644
index 0000000000..6e111f87d9
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/contract3
@@ -0,0 +1,3 @@
+
+contract3.erl:17: Overloaded contract for contract3:t1/1 has overlapping domains; such contracts are currently unsupported and are simply ignored
+contract3.erl:29: Overloaded contract for contract3:t3/3 has overlapping domains; such contracts are currently unsupported and are simply ignored
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes b/lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes
new file mode 100644
index 0000000000..737959a49d
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/contracts_with_subtypes
@@ -0,0 +1,142 @@
+
+contracts_with_subtypes.erl:106: The call contracts_with_subtypes:rec_arg
+ ({'a', 'b'}) breaks the contract
+ (Arg) -> 'ok'
+ when
+ Arg :: {'a', A} | {'b', B},
+ A :: 'a' | {'b', B},
+ B :: 'b' | {'a', A}
+contracts_with_subtypes.erl:107: The call contracts_with_subtypes:rec_arg
+ ({'b', 'a'}) breaks the contract
+ (Arg) -> 'ok'
+ when
+ Arg :: {'a', A} | {'b', B},
+ A :: 'a' | {'b', B},
+ B :: 'b' | {'a', A}
+contracts_with_subtypes.erl:109: The call contracts_with_subtypes:rec_arg
+ ({'b', {'a', 'b'}}) breaks the contract
+ (Arg) -> 'ok'
+ when
+ Arg :: {'a', A} | {'b', B},
+ A :: 'a' | {'b', B},
+ B :: 'b' | {'a', A}
+contracts_with_subtypes.erl:135: The call contracts_with_subtypes:rec2
+ ({'a', 'b'}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:136: The call contracts_with_subtypes:rec2
+ ({'b', 'a'}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:137: The call contracts_with_subtypes:rec2
+ ({'a', {'b', 'a'}}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:138: The call contracts_with_subtypes:rec2
+ ({'b', {'a', 'b'}}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:139: The call contracts_with_subtypes:rec2
+ ({'a', {'b', {'a', 'b'}}}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:140: The call contracts_with_subtypes:rec2
+ ({'b', {'a', {'b', 'a'}}}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:141: The call contracts_with_subtypes:rec2
+ ({'a', {'b', {'a', {'b', 'a'}}}}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:142: The call contracts_with_subtypes:rec2
+ ({'b', {'a', {'b', {'a', 'b'}}}}) breaks the contract
+ (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:175: The pattern
+ 1 can never match the type
+ string()
+contracts_with_subtypes.erl:178: The pattern
+ 'alpha' can never match the type
+ {'ok', _} | {'ok', _, string()}
+contracts_with_subtypes.erl:180: The pattern
+ 42 can never match the type
+ {'ok', _} | {'ok', _, string()}
+contracts_with_subtypes.erl:196: The pattern
+ 'alpha' can never match the type
+ {'ok', _}
+contracts_with_subtypes.erl:198: The pattern
+ 42 can never match the type
+ {'ok', _}
+contracts_with_subtypes.erl:216: The pattern
+ 'alpha' can never match the type
+ {'ok', _}
+contracts_with_subtypes.erl:218: The pattern
+ 42 can never match the type
+ {'ok', _}
+contracts_with_subtypes.erl:235: The pattern
+ 1 can never match the type
+ string()
+contracts_with_subtypes.erl:238: The pattern
+ {'ok', _} can never match the type
+ {'ok', _, string()}
+contracts_with_subtypes.erl:239: The pattern
+ 'alpha' can never match the type
+ {'ok', _, string()}
+contracts_with_subtypes.erl:23: Invalid type specification for function contracts_with_subtypes:extract2/0. The success typing is
+ () -> 'something'
+contracts_with_subtypes.erl:240: The pattern
+ {'ok', 42} can never match the type
+ {'ok', _, string()}
+contracts_with_subtypes.erl:241: The pattern
+ 42 can never match the type
+ {'ok', _, string()}
+contracts_with_subtypes.erl:268: The call contracts_with_subtypes:flat_ets_new
+ (12,
+ []) breaks the contract
+ (Name, Options) -> atom()
+ when
+ Name :: atom(),
+ Options :: [Option],
+ Option ::
+ 'set' | 'ordered_set' | 'bag' | 'duplicate_bag' |
+ 'public' | 'protected' | 'private' |
+ 'named_table' |
+ {'keypos', integer()} |
+ {'heir', pid(), term()} |
+ {'heir', 'none'} |
+ {'write_concurrency', boolean()} |
+ {'read_concurrency', boolean()} |
+ 'compressed'
+contracts_with_subtypes.erl:295: The call contracts_with_subtypes:factored_ets_new
+ (12,
+ []) breaks the contract
+ (Name, Options) -> atom()
+ when
+ Name :: atom(),
+ Options :: [Option],
+ Option ::
+ Type | Access | 'named_table' |
+ {'keypos', Pos} |
+ {'heir', Pid :: pid(), HeirData} |
+ {'heir', 'none'} |
+ Tweaks,
+ Type :: type(),
+ Access :: access(),
+ Tweaks ::
+ {'write_concurrency', boolean()} |
+ {'read_concurrency', boolean()} |
+ 'compressed',
+ Pos :: pos_integer(),
+ HeirData :: term()
+contracts_with_subtypes.erl:77: The call contracts_with_subtypes:foo1
+ (5) breaks the contract
+ (Arg1) -> Res when Arg1 :: atom(), Res :: atom()
+contracts_with_subtypes.erl:78: The call contracts_with_subtypes:foo2
+ (5) breaks the contract
+ (Arg1) -> Res when Arg1 :: Arg2, Arg2 :: atom(), Res :: atom()
+contracts_with_subtypes.erl:79: The call contracts_with_subtypes:foo3
+ (5) breaks the contract
+ (Arg1) -> Res when Arg2 :: atom(), Arg1 :: Arg2, Res :: atom()
+contracts_with_subtypes.erl:7: Invalid type specification for function contracts_with_subtypes:extract/0. The success typing is
+ () -> 'something'
+contracts_with_subtypes.erl:80: The call contracts_with_subtypes:foo4
+ (5) breaks the contract
+ (Type) -> Type when Type :: atom()
+contracts_with_subtypes.erl:81: The call contracts_with_subtypes:foo5
+ (5) breaks the contract
+ (Type :: atom()) -> Type :: atom()
+contracts_with_subtypes.erl:82: The call contracts_with_subtypes:foo6
+ (5) breaks the contract
+ (Type) -> Type when Type :: atom()
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/dict_use b/lib/dialyzer/test/indent_SUITE_data/results/dict_use
new file mode 100644
index 0000000000..c6863d057e
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/dict_use
@@ -0,0 +1,48 @@
+
+dict_use.erl:41: The attempt to match a term of type
+ dict:dict(_, _) against the pattern
+ 'gazonk' breaks the opacity of the term
+dict_use.erl:45: The attempt to match a term of type
+ dict:dict(_, _) against the pattern
+ [] breaks the opacity of the term
+dict_use.erl:46: The attempt to match a term of type
+ dict:dict(_, _) against the pattern
+ 42 breaks the opacity of the term
+dict_use.erl:51: The attempt to match a term of type
+ dict:dict(_, _) against the pattern
+ [] breaks the opacity of the term
+dict_use.erl:52: The attempt to match a term of type
+ dict:dict(_, _) against the pattern
+ 42 breaks the opacity of the term
+dict_use.erl:58: Attempt to test for equality between a term of type
+ maybe_improper_list() and a term of opaque type
+ dict:dict(_, _)
+dict_use.erl:60: Attempt to test for inequality between a term of type
+ atom() and a term of opaque type
+ dict:dict(_, _)
+dict_use.erl:64: Guard test length
+ (D :: dict:dict(_, _)) breaks the opacity of its argument
+dict_use.erl:65: Guard test is_atom
+ (D :: dict:dict(_, _)) breaks the opacity of its argument
+dict_use.erl:66: Guard test is_list
+ (D :: dict:dict(_, _)) breaks the opacity of its argument
+dict_use.erl:70: The type test is_list
+ (dict:dict(_, _)) breaks the opacity of the term
+ dict:dict(_, _)
+dict_use.erl:73: The call dict:fetch
+ ('foo',
+ [1, 2, 3]) does not have an opaque term of type
+ dict:dict(_, _) as 2nd argument
+dict_use.erl:76: The call dict:merge
+ (Fun :: any(),
+ 42,
+ [1, 2]) does not have opaque terms as 2nd and 3rd arguments
+dict_use.erl:79: The call dict:store
+ (42,
+ 'elli',
+ {'dict', 0, 16, 16, 8, 80, 48,
+ {[], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
+ []},
+ {{[], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
+ []}}}) does not have an opaque term of type
+ dict:dict(_, _) as 3rd argument
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/fun_app b/lib/dialyzer/test/indent_SUITE_data/results/fun_app
new file mode 100644
index 0000000000..d4a3caf749
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/fun_app
@@ -0,0 +1,7 @@
+
+fun_app.erl:37: Fun application will fail since F ::
+ fun((_, _, _) -> 'ok' | 'true') is not a function of arity 1
+fun_app.erl:38: Fun application will fail since F ::
+ fun((_, _, _) -> 'ok' | 'true') is not a function of arity 2
+fun_app.erl:40: Fun application will fail since F ::
+ fun((_, _, _) -> 'ok' | 'true') is not a function of arity 4
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/fun_app_args b/lib/dialyzer/test/indent_SUITE_data/results/fun_app_args
new file mode 100644
index 0000000000..ac1bbb62b8
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/fun_app_args
@@ -0,0 +1,5 @@
+
+fun_app_args.erl:12: Fun application with arguments
+ ('b',
+ []) will fail since the function has type
+ 'c' | fun(('a', []) -> any()), which differs in the 1st argument
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/guard_update b/lib/dialyzer/test/indent_SUITE_data/results/guard_update
new file mode 100644
index 0000000000..bd0e8cd5dd
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/guard_update
@@ -0,0 +1,6 @@
+
+guard_update.erl:6: The call guard_update:f
+ (#{'a' => 2}) will never return since it differs in the 1st argument from the success typing arguments:
+ (#{'b' := _, _ => _})
+guard_update.erl:8: Clause guard cannot succeed. The variable M was matched against the type
+ #{'a' := 2}
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/guard_warnings b/lib/dialyzer/test/indent_SUITE_data/results/guard_warnings
new file mode 100644
index 0000000000..a6cb54ff9c
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/guard_warnings
@@ -0,0 +1,134 @@
+
+guard_warnings.erl:100: Guard test not
+ ('true') can never succeed
+guard_warnings.erl:102: Guard test
+ X :: 'true' =:=
+ 'false' can never succeed
+guard_warnings.erl:104: Guard test
+ X :: 'true' ==
+ 'false' can never succeed
+guard_warnings.erl:106: Guard test
+ X :: 'true' =/=
+ 'true' can never succeed
+guard_warnings.erl:12: Guard test
+ X :: 'true' =:=
+ 'false' can never succeed
+guard_warnings.erl:14: Guard test
+ X :: 'false' =:=
+ 'true' can never succeed
+guard_warnings.erl:16: Guard test not
+ (X :: 'true') can never succeed
+guard_warnings.erl:18: Guard test and
+ ('true',
+ X :: none()) can never succeed
+guard_warnings.erl:20: Guard test not
+ (X :: 'true') can never succeed
+guard_warnings.erl:22: Guard test and
+ ('true',
+ X :: none()) can never succeed
+guard_warnings.erl:28: Guard test not(not
+ (X :: 'false')) can never succeed
+guard_warnings.erl:30: Guard test not(or
+ ('false',
+ X :: none())) can never succeed
+guard_warnings.erl:32: Guard test not(not
+ (X :: 'false')) can never succeed
+guard_warnings.erl:34: Guard test not(or
+ ('false',
+ X :: none())) can never succeed
+guard_warnings.erl:36: Guard test and
+ ('true',
+ 'false') can never succeed
+guard_warnings.erl:38: Guard test and
+ ('false',
+ any()) can never succeed
+guard_warnings.erl:40: Guard test and
+ (X :: 'true',
+ 'false') can never succeed
+guard_warnings.erl:42: Guard test and
+ ('false',
+ X :: any()) can never succeed
+guard_warnings.erl:44: Guard test and
+ (X :: 'true',
+ 'false') can never succeed
+guard_warnings.erl:46: Guard test and
+ ('false',
+ X :: any()) can never succeed
+guard_warnings.erl:48: Guard test not(or
+ ('true',
+ any())) can never succeed
+guard_warnings.erl:50: Guard test not(or
+ ('false',
+ 'true')) can never succeed
+guard_warnings.erl:52: Guard test not(or
+ ('true',
+ X :: any())) can never succeed
+guard_warnings.erl:54: Guard test not(or
+ (X :: 'false',
+ 'true')) can never succeed
+guard_warnings.erl:56: Guard test not(or
+ ('true',
+ X :: any())) can never succeed
+guard_warnings.erl:58: Guard test not(or
+ (X :: 'false',
+ 'true')) can never succeed
+guard_warnings.erl:60: Guard test and
+ ('false',
+ any()) can never succeed
+guard_warnings.erl:62: Guard test and
+ ('true',
+ 'false') can never succeed
+guard_warnings.erl:64: Guard test and
+ ('false',
+ X :: any()) can never succeed
+guard_warnings.erl:66: Guard test and
+ (X :: 'true',
+ 'false') can never succeed
+guard_warnings.erl:68: Guard test and
+ ('false',
+ X :: any()) can never succeed
+guard_warnings.erl:70: Guard test and
+ (X :: 'true',
+ 'false') can never succeed
+guard_warnings.erl:72: Guard test and
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:74: Guard test and
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:76: Guard test not(and
+ ('true',
+ 'true')) can never succeed
+guard_warnings.erl:78: Guard test and
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:80: Guard test not(and
+ ('true',
+ 'true')) can never succeed
+guard_warnings.erl:82: Guard test or
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:84: Guard test or
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:86: Guard test or
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:88: Guard test or
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:90: Guard test or
+ ('false',
+ 'false') can never succeed
+guard_warnings.erl:92: Guard test
+ 'true' =:=
+ 'false' can never succeed
+guard_warnings.erl:94: Guard test
+ 'true' ==
+ 'false' can never succeed
+guard_warnings.erl:96: Guard test
+ 'true' =:=
+ 'false' can never succeed
+guard_warnings.erl:98: Guard test not(
+ 'true' ==
+ 'true') can never succeed
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/map_galore b/lib/dialyzer/test/indent_SUITE_data/results/map_galore
new file mode 100644
index 0000000000..1b63e28ace
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/map_galore
@@ -0,0 +1,713 @@
+
+map_galore.erl:1000: A key of type
+ 42 cannot exist in a map of type
+ #{1 := 'a',
+ 2 := 'b',
+ 4 := 'd',
+ 5 := 'e',
+ float() => 'c' | 'v'}
+map_galore.erl:1080: A key of type
+ 'nonexisting' cannot exist in a map of type
+ #{10 := 'a0',
+ 11 := 'a1',
+ 12 := 'a2',
+ 13 := 'a3',
+ 14 := 'a4',
+ 15 := 'a5',
+ 16 := 'a6',
+ 17 := 'a7',
+ 18 := 'a8',
+ 19 := 'a9',
+ 20 := 'b0',
+ 21 := 'b1',
+ 22 := 'b2',
+ 23 := 'b3',
+ 24 := 'b4',
+ 25 := 'b5',
+ 26 := 'b6',
+ 27 := 'b7',
+ 28 := 'b8',
+ 29 := 'b9',
+ 30 := [48 | 99, ...],
+ 31 := [49 | 99, ...],
+ 32 := [50 | 99, ...],
+ 33 := [51 | 99, ...],
+ 34 := [52 | 99, ...],
+ 35 := [53 | 99, ...],
+ 36 := [54 | 99, ...],
+ 37 := [55 | 99, ...],
+ 38 := [56 | 99, ...],
+ 39 := [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...],
+ ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [54 | 99, ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...],
+ ...]} =>
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
+ 100 | 101,
+ ...]} =>
+ atom() | [1..255, ...]}
+map_galore.erl:1082: A key of type
+ 42 cannot exist in a map of type
+ #{10 := 'a0',
+ 11 := 'a1',
+ 12 := 'a2',
+ 13 := 'a3',
+ 14 := 'a4',
+ 15 := 'a5',
+ 16 := 'a6',
+ 17 := 'a7',
+ 18 := 'a8',
+ 19 := 'a9',
+ 20 := 'b0',
+ 21 := 'b1',
+ 22 := 'b2',
+ 23 := 'b3',
+ 24 := 'b4',
+ 25 := 'b5',
+ 26 := 'b6',
+ 27 := 'b7',
+ 28 := 'b8',
+ 29 := 'b9',
+ 30 := [48 | 99, ...],
+ 31 := [49 | 99, ...],
+ 32 := [50 | 99, ...],
+ 33 := [51 | 99, ...],
+ 34 := [52 | 99, ...],
+ 35 := [53 | 99, ...],
+ 36 := [54 | 99, ...],
+ 37 := [55 | 99, ...],
+ 38 := [56 | 99, ...],
+ 39 := [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...],
+ ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [54 | 99, ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...],
+ ...]} =>
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
+ 100 | 101,
+ ...]} =>
+ atom() | [1..255, ...]}
+map_galore.erl:1140: The call map_galore:map_guard_sequence_1
+ (#{seq => 6, val => "e"}) will never return since it differs in the 1st argument from the success typing arguments:
+ (#{'seq' := 1 | 2 | 3 | 4 | 5,
+ 'val' := [97 | 98 | 99 | 100 | 101, ...],
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[any(), ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [any(), ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [any(), ...],
+ 31 => [any(), ...],
+ 32 => [any(), ...],
+ 33 => [any(), ...],
+ 34 => [any(), ...],
+ 35 => [any(), ...],
+ 36 => [any(), ...],
+ 37 => [any(), ...],
+ 38 => [any(), ...],
+ 39 => [any(), ...],
+ <<_:16>> | [any(), ...] | {_} => [any(), ...]} =>
+ atom() | [1..255, ...]})
+map_galore.erl:1141: The call map_galore:map_guard_sequence_2
+ (#{'b' => 5}) will never return since it differs in the 1st argument from the success typing arguments:
+ (#{'a' := 'gg' | 'kk' | 'sc' | 3 | 4,
+ 'b' => 'other' | 3 | 4 | 5,
+ 'c' => 'sc2',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[any(), ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [any(), ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [any(), ...],
+ 31 => [any(), ...],
+ 32 => [any(), ...],
+ 33 => [any(), ...],
+ 34 => [any(), ...],
+ 35 => [any(), ...],
+ 36 => [any(), ...],
+ 37 => [any(), ...],
+ 38 => [any(), ...],
+ 39 => [any(), ...],
+ <<_:16>> | [any(), ...] | {_} => [any(), ...]} =>
+ atom() | [1..255, ...]})
+map_galore.erl:1209: The call map_galore:map_guard_sequence_1
+ (#{'seq' := 6,
+ 'val' := [101, ...],
+ 10 := 'a0',
+ 11 := 'a1',
+ 12 := 'a2',
+ 13 := 'a3',
+ 14 := 'a4',
+ 15 := 'a5',
+ 16 := 'a6',
+ 17 := 'a7',
+ 18 := 'a8',
+ 19 := 'a9',
+ 20 := 'b0',
+ 21 := 'b1',
+ 22 := 'b2',
+ 23 := 'b3',
+ 24 := 'b4',
+ 25 := 'b5',
+ 26 := 'b6',
+ 27 := 'b7',
+ 28 := 'b8',
+ 29 := 'b9',
+ 30 := [48 | 99, ...],
+ 31 := [49 | 99, ...],
+ 32 := [50 | 99, ...],
+ 33 := [51 | 99, ...],
+ 34 := [52 | 99, ...],
+ 35 := [53 | 99, ...],
+ 36 := [54 | 99, ...],
+ 37 := [55 | 99, ...],
+ 38 := [56 | 99, ...],
+ 39 := [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] | 3,
+ ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [54 | 99, ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...],
+ ...]} =>
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
+ 100 | 101,
+ ...]} =>
+ atom() | [1..255, ...]}) will never return since it differs in the 1st argument from the success typing arguments:
+ (#{'seq' := 1 | 2 | 3 | 4 | 5,
+ 'val' := [97 | 98 | 99 | 100 | 101, ...],
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[any(), ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [any(), ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [any(), ...],
+ 31 => [any(), ...],
+ 32 => [any(), ...],
+ 33 => [any(), ...],
+ 34 => [any(), ...],
+ 35 => [any(), ...],
+ 36 => [any(), ...],
+ 37 => [any(), ...],
+ 38 => [any(), ...],
+ 39 => [any(), ...],
+ <<_:16>> | [any(), ...] | {_} => [any(), ...]} =>
+ atom() | [1..255, ...]})
+map_galore.erl:1210: The call map_galore:map_guard_sequence_2
+ (#{'b' := 5,
+ 10 := 'a0',
+ 11 := 'a1',
+ 12 := 'a2',
+ 13 := 'a3',
+ 14 := 'a4',
+ 15 := 'a5',
+ 16 := 'a6',
+ 17 := 'a7',
+ 18 := 'a8',
+ 19 := 'a9',
+ 20 := 'b0',
+ 21 := 'b1',
+ 22 := 'b2',
+ 23 := 'b3',
+ 24 := 'b4',
+ 25 := 'b5',
+ 26 := 'b6',
+ 27 := 'b7',
+ 28 := 'b8',
+ 29 := 'b9',
+ 30 := [48 | 99, ...],
+ 31 := [49 | 99, ...],
+ 32 := [50 | 99, ...],
+ 33 := [51 | 99, ...],
+ 34 := [52 | 99, ...],
+ 35 := [53 | 99, ...],
+ 36 := [54 | 99, ...],
+ 37 := [55 | 99, ...],
+ 38 := [56 | 99, ...],
+ 39 := [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] | 3,
+ ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [54 | 99, ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...],
+ ...]} =>
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 |
+ 100 | 101,
+ ...]} =>
+ atom() | [1..255, ...]}) will never return since it differs in the 1st argument from the success typing arguments:
+ (#{'a' := 'gg' | 'kk' | 'sc' | 3 | 4,
+ 'b' => 'other' | 3 | 4 | 5,
+ 'c' => 'sc2',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [48 | 99, ...],
+ 31 => [49 | 99, ...],
+ 32 => [50 | 99, ...],
+ 33 => [51 | 99, ...],
+ 34 => [52 | 99, ...],
+ 35 => [53 | 99, ...],
+ 36 => [54 | 99, ...],
+ 37 => [55 | 99, ...],
+ 38 => [56 | 99, ...],
+ 39 => [57 | 99, ...],
+ <<_:16>> |
+ [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57, ...] |
+ float() |
+ {[any(), ...]} |
+ #{'k16' => 'a6',
+ 'k26' => 'b6',
+ 'k36' => [any(), ...],
+ 'map' => 'key',
+ 'one' => 'small',
+ 'second' => 'small',
+ 'third' => 'small',
+ 10 => 'a0',
+ 11 => 'a1',
+ 12 => 'a2',
+ 13 => 'a3',
+ 14 => 'a4',
+ 15 => 'a5',
+ 16 => 'a6',
+ 17 => 'a7',
+ 18 => 'a8',
+ 19 => 'a9',
+ 20 => 'b0',
+ 21 => 'b1',
+ 22 => 'b2',
+ 23 => 'b3',
+ 24 => 'b4',
+ 25 => 'b5',
+ 26 => 'b6',
+ 27 => 'b7',
+ 28 => 'b8',
+ 29 => 'b9',
+ 30 => [any(), ...],
+ 31 => [any(), ...],
+ 32 => [any(), ...],
+ 33 => [any(), ...],
+ 34 => [any(), ...],
+ 35 => [any(), ...],
+ 36 => [any(), ...],
+ 37 => [any(), ...],
+ 38 => [any(), ...],
+ 39 => [any(), ...],
+ <<_:16>> | [any(), ...] | {_} => [any(), ...]} =>
+ atom() | [1..255, ...]})
+map_galore.erl:1418: Fun application with arguments
+ (#{'s' => 'none', 'v' => 'none'}) will never return since it differs in the 1st argument from the success typing arguments:
+ (#{'s' := 'l' | 't' | 'v',
+ 'v' :=
+ 'none' |
+ <<_:16>> |
+ [<<_:16>>, ...] |
+ {<<_:16>>, <<_:16>>}})
+map_galore.erl:1491: The test
+ #{} =:=
+ #{'a' := 1} can never evaluate to 'true'
+map_galore.erl:1492: The test
+ #{'a' := 1} =:=
+ #{} can never evaluate to 'true'
+map_galore.erl:1495: The test
+ #{'a' := 1} =:=
+ #{'a' := 2} can never evaluate to 'true'
+map_galore.erl:1496: The test
+ #{'a' := 2} =:=
+ #{'a' := 1} can never evaluate to 'true'
+map_galore.erl:1497: The test
+ #{'a' := 2, 'b' := 1} =:=
+ #{'a' := 1, 'b' := 3} can never evaluate to 'true'
+map_galore.erl:1498: The test
+ #{'a' := 1, 'b' := 1} =:=
+ #{'a' := 1, 'b' := 3} can never evaluate to 'true'
+map_galore.erl:1762: The call maps:get
+ ({1, 1},
+ #{{1, float()} => [101 | 108 | 112 | 116 | 117, ...]}) will never return since the success typing arguments are
+ (any(),
+ map())
+map_galore.erl:1763: The call maps:get
+ ('a',
+ #{}) will never return since the success typing arguments are
+ (any(),
+ map())
+map_galore.erl:1765: The call maps:get
+ ('a',
+ #{'b' => 1, 'c' => 2}) will never return since the success typing arguments are
+ (any(),
+ map())
+map_galore.erl:186: The pattern
+ #{'x' := 2} can never match the type
+ #{'x' := 3}
+map_galore.erl:187: The pattern
+ #{'x' := 3} can never match the type
+ {'a', 'b', 'c'}
+map_galore.erl:188: The pattern
+ #{'x' := 3} can never match the type
+ #{'y' := 3}
+map_galore.erl:189: The pattern
+ #{'x' := 3} can never match the type
+ #{'x' := [101 | 104 | 114 | 116, ...]}
+map_galore.erl:2280: Cons will produce an improper list since its 2nd argument is
+ {'b', 'a'}
+map_galore.erl:2280: The call maps:from_list
+ ([{'a', 'b'} | {'b', 'a'}]) will never return since it differs in the 1st argument from the success typing arguments:
+ ([{_, _}])
+map_galore.erl:2281: The call maps:from_list
+ ('a') will never return since it differs in the 1st argument from the success typing arguments:
+ ([{_, _}])
+map_galore.erl:2282: The call maps:from_list
+ (42) will never return since it differs in the 1st argument from the success typing arguments:
+ ([{_, _}])
+map_galore.erl:997: A key of type
+ 'nonexisting' cannot exist in a map of type
+ #{}
+map_galore.erl:998: A key of type
+ 'nonexisting' cannot exist in a map of type
+ #{1 := 'a', 2 := 'b', 4 := 'd', 5 := 'e', float() => 'c'}
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/order b/lib/dialyzer/test/indent_SUITE_data/results/order
new file mode 100644
index 0000000000..5b0030d7b1
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/order
@@ -0,0 +1,23 @@
+
+order.erl:14: Guard test is_integer
+ (Int :: 'b') can never succeed
+order.erl:16: The variable _Else can never match since previous clauses completely covered the type
+ 'b'
+order.erl:21: Guard test is_integer
+ (Int :: 'b') can never succeed
+order.erl:23: The variable _Else can never match since previous clauses completely covered the type
+ 'b'
+order.erl:30: The variable _Else can never match since previous clauses completely covered the type
+ 'b' | 1
+order.erl:36: The variable Atom can never match since previous clauses completely covered the type
+ 1
+order.erl:37: The variable _Else can never match since previous clauses completely covered the type
+ 1
+order.erl:42: Guard test is_integer
+ (Int :: 'b') can never succeed
+order.erl:44: The variable _Else can never match since previous clauses completely covered the type
+ 'b'
+order.erl:7: Guard test is_integer
+ (Int :: 'b') can never succeed
+order.erl:9: The variable _Else can never match since previous clauses completely covered the type
+ 'b'
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/queue_use b/lib/dialyzer/test/indent_SUITE_data/results/queue_use
new file mode 100644
index 0000000000..b6604e5320
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/queue_use
@@ -0,0 +1,34 @@
+
+queue_use.erl:18: The call queue:is_empty
+ ({[], []}) does not have an opaque term of type
+ queue:queue(_) as 1st argument
+queue_use.erl:22: The call queue:in
+ (42,
+ Q0 :: {[], []}) does not have an opaque term of type
+ queue:queue(_) as 2nd argument
+queue_use.erl:27: The attempt to match a term of type
+ queue:queue(_) against the pattern
+ {"*", Q2} breaks the opacity of the term
+queue_use.erl:33: Attempt to test for equality between a term of type
+ {[42, ...], []} and a term of opaque type
+ queue:queue(_)
+queue_use.erl:36: The attempt to match a term of type
+ queue:queue(_) against the pattern
+ {F, _R} breaks the opacity of the term
+queue_use.erl:40: The call queue:out
+ ({"*", []}) does not have an opaque term of type
+ queue:queue(_) as 1st argument
+queue_use.erl:51: The call queue_use:is_in_queue
+ (E :: 42,
+ DB :: #db{p :: [], q :: queue:queue(_)}) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+queue_use.erl:56: The attempt to match a term of type
+ #db{p :: [], q :: queue:queue(_)} against the pattern
+ {'db', _, {L1, L2}} breaks the opacity of
+ queue:queue(_)
+queue_use.erl:62: The call queue_use:tuple_queue
+ ({42, 'gazonk'}) does not have a term of type
+ {_, queue:queue(_)} (with opaque subterms) as 1st argument
+queue_use.erl:65: The call queue:in
+ (F :: 42,
+ Q :: 'gazonk') does not have an opaque term of type
+ queue:queue(_) as 2nd argument
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/rec b/lib/dialyzer/test/indent_SUITE_data/results/rec
new file mode 100644
index 0000000000..5938b18be0
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/rec
@@ -0,0 +1,15 @@
+
+rec_use.erl:17: The attempt to match a term of type
+ rec_adt:rec() against the pattern
+ {'rec', _, 42} breaks the opacity of the term
+rec_use.erl:18: Guard test tuple_size
+ (R :: rec_adt:rec()) breaks the opacity of its argument
+rec_use.erl:23: The call rec_adt:get_a
+ (R :: tuple()) does not have an opaque term of type
+ rec_adt:rec() as 1st argument
+rec_use.erl:27: Attempt to test for equality between a term of type
+ {'rec', 'gazonk', 42} and a term of opaque type
+ rec_adt:rec()
+rec_use.erl:30: The call erlang:tuple_size
+ (rec_adt:rec()) contains an opaque term as 1st argument when a structured term of type
+ tuple() is expected
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_construct b/lib/dialyzer/test/indent_SUITE_data/results/record_construct
new file mode 100644
index 0000000000..a1268de690
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_construct
@@ -0,0 +1,11 @@
+
+record_construct.erl:16: Record construction
+ #r_opa{b :: gb_sets:set(_), c :: 42, e :: 'false'} violates the declared type of field c ::
+ boolean()
+record_construct.erl:21: Record construction
+ #r_rem{a :: 'gazonk'} violates the declared type of field a ::
+ string()
+record_construct.erl:7: Record construction
+ #r_loc{a :: 'gazonk', b :: 42} violates the declared type of field a ::
+ integer() and b ::
+ atom()
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_creation_diffs b/lib/dialyzer/test/indent_SUITE_data/results/record_creation_diffs
new file mode 100644
index 0000000000..9b5f9489db
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_creation_diffs
@@ -0,0 +1,4 @@
+
+record_creation_diffs.erl:11: Record construction
+ #bar{some_list :: {'this', 'is', 'a', 'tuple'}} violates the declared type of field some_list ::
+ [any()]
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_match b/lib/dialyzer/test/indent_SUITE_data/results/record_match
new file mode 100644
index 0000000000..4738a4b0c9
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_match
@@ -0,0 +1,4 @@
+
+record_match.erl:17: Matching of pattern
+ {'b_literal', 'undefined'} tagged with a record name violates the declared type of
+ #b_local{} | #b_remote{}
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_pat b/lib/dialyzer/test/indent_SUITE_data/results/record_pat
new file mode 100644
index 0000000000..cf9247d5a8
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_pat
@@ -0,0 +1,4 @@
+
+record_pat.erl:14: Matching of pattern
+ {'foo', 'baz'} tagged with a record name violates the declared type of
+ #foo{bar :: integer()}
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_send_test b/lib/dialyzer/test/indent_SUITE_data/results/record_send_test
new file mode 100644
index 0000000000..51d8e1a852
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_send_test
@@ -0,0 +1,6 @@
+
+record_send_test.erl:30: The call erlang:'!'
+ (Rec1 :: #rec1{a :: 'a', b :: 'b', c :: 'c'},
+ 'hello_again') will never return since it differs in the 1st argument from the success typing arguments:
+ (atom() | pid() | port() | {atom(), atom()},
+ any())
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_test b/lib/dialyzer/test/indent_SUITE_data/results/record_test
new file mode 100644
index 0000000000..1574459578
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_test
@@ -0,0 +1,6 @@
+
+record_test.erl:19: Matching of pattern
+ {'foo', _} tagged with a record name violates the declared type of
+ 'foo'
+record_test.erl:21: The variable _ can never match since previous clauses completely covered the type
+ 'foo'
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/record_update b/lib/dialyzer/test/indent_SUITE_data/results/record_update
new file mode 100644
index 0000000000..6e4124552e
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/record_update
@@ -0,0 +1,3 @@
+
+record_update.erl:7: Invalid type specification for function record_update:quux/2. The success typing is
+ (#foo{bar :: atom()}, atom()) -> #foo{bar :: atom()}
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour b/lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour
new file mode 100644
index 0000000000..f0e41d024a
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/sample_behaviour
@@ -0,0 +1,23 @@
+
+sample_callback_wrong.erl:16: The inferred return type of sample_callback_2/0
+ (42) has nothing in common with
+ atom(), which is the expected return type for the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:17: The inferred return type of sample_callback_3/0
+ ('fair') has nothing in common with
+ 'fail' | {'ok', 1..255}, which is the expected return type for the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:18: The inferred return type of sample_callback_4/1
+ ('fail') has nothing in common with
+ 'ok', which is the expected return type for the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:20: The inferred return type of sample_callback_5/1
+ (string()) has nothing in common with
+ 'fail' | 'ok', which is the expected return type for the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:20: The inferred type for the 1st argument of sample_callback_5/1 (
+ atom()) is not a supertype of
+ 1..255, which is expected type for this argument in the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:22: The inferred return type of sample_callback_6/3
+ ({'okk', number()}) has nothing in common with
+ 'fail' | {'ok', 1..255}, which is the expected return type for the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:22: The inferred type for the 3rd argument of sample_callback_6/3 (
+ atom()) is not a supertype of
+ string(), which is expected type for this argument in the callback of the sample_behaviour behaviour
+sample_callback_wrong.erl:4: Undefined callback function sample_callback_1/0 (behaviour sample_behaviour)
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/simple b/lib/dialyzer/test/indent_SUITE_data/results/simple
new file mode 100644
index 0000000000..bafe334405
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/simple
@@ -0,0 +1,289 @@
+
+exact_api.erl:17: The call exact_api:set_type
+ (A ::
+ #digraph{vtab :: 'notable',
+ etab :: 'notable',
+ ntab :: 'notable',
+ cyclic :: 'true'}) does not have an opaque term of type
+ digraph:graph() as 1st argument
+exact_api.erl:23: The call digraph:delete
+ (G ::
+ #digraph{vtab :: 'notable',
+ etab :: 'notable',
+ ntab :: 'notable',
+ cyclic :: 'true'}) does not have an opaque term of type
+ digraph:graph() as 1st argument
+exact_api.erl:55: The attempt to match a term of type
+ exact_adt:exact_adt() against the pattern
+ {'exact_adt'} breaks the opacity of the term
+exact_api.erl:59: The call exact_adt:exact_adt_set_type2
+ (A :: #exact_adt{}) does not have an opaque term of type
+ exact_adt:exact_adt() as 1st argument
+is_rec.erl:10: The call erlang:is_record
+ (simple1_adt:d1(),
+ 'r',
+ 2) contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:15: The call erlang:is_record
+ (A :: simple1_adt:d1(),
+ 'r',
+ I :: 1 | 2 | 3) contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:19: Guard test is_record
+ (A :: simple1_adt:d1(),
+ 'r',
+ 2) breaks the opacity of its argument
+is_rec.erl:23: Guard test is_record
+ ({simple1_adt:d1(), 1},
+ 'r',
+ 2) breaks the opacity of its argument
+is_rec.erl:41: The call erlang:is_record
+ (A :: simple1_adt:d1(),
+ R :: 'a') contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:45: The call erlang:is_record
+ (A :: simple1_adt:d1(),
+ A :: simple1_adt:d1(),
+ 1) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+is_rec.erl:49: The call erlang:is_record
+ (A :: simple1_adt:d1(),
+ any(),
+ 1) contains an opaque term as 1st argument when terms of different types are expected in these positions
+is_rec.erl:53: The call erlang:is_record
+ (A :: simple1_adt:d1(),
+ A :: simple1_adt:d1(),
+ any()) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+is_rec.erl:57: Guard test is_record
+ (A :: simple1_adt:d1(),
+ 'r',
+ 2) breaks the opacity of its argument
+is_rec.erl:61: The record
+ #r{f1 :: simple1_adt:d1()} violates the declared type for #r{}
+is_rec.erl:65: The call erlang:is_record
+ ({simple1_adt:d1(), 1},
+ 'r',
+ 2) contains an opaque term as 1st argument when terms of different types are expected in these positions
+rec_api.erl:104: Matching of pattern
+ {'r2', 10} tagged with a record name violates the declared type of
+ #r2{f1 :: 10}
+rec_api.erl:113: The attempt to match a term of type
+ #r3{f1 :: queue:queue(_)} against the pattern
+ {'r3', 'a'} breaks the opacity of
+ queue:queue(_)
+rec_api.erl:118: Record construction
+ #r3{f1 :: 10} violates the declared type of field f1 ::
+ queue:queue(_)
+rec_api.erl:123: The attempt to match a term of type
+ #r3{f1 :: 10} against the pattern
+ {'r3', 10} breaks the opacity of
+ queue:queue(_)
+rec_api.erl:24: Record construction
+ #r1{f1 :: 10} violates the declared type of field f1 ::
+ rec_api:a()
+rec_api.erl:29: Matching of pattern
+ {'r1', 10} tagged with a record name violates the declared type of
+ #r1{f1 :: 10}
+rec_api.erl:33: The attempt to match a term of type
+ rec_adt:r1() against the pattern
+ {'r1', 'a'} breaks the opacity of the term
+rec_api.erl:35: Invalid type specification for function rec_api:adt_t1/1. The success typing is
+ (#r1{f1 :: 'a'}) -> #r1{f1 :: 'a'}
+rec_api.erl:40: The specification for rec_api:adt_r1/0 has an opaque subtype
+ rec_adt:r1() which is violated by the success typing
+ () -> #r1{f1 :: 'a'}
+rec_api.erl:85: The attempt to match a term of type
+ rec_adt:f() against the record field 'f' declared to be of type
+ rec_api:f() breaks the opacity of the term
+rec_api.erl:99: Record construction
+ #r2{f1 :: 10} violates the declared type of field f1 ::
+ rec_api:a()
+simple1_api.erl:113: The test
+ simple1_api:d1() =:=
+ simple1_api:d2() can never evaluate to 'true'
+simple1_api.erl:118: Guard test
+ simple1_api:d2() =:=
+ A :: simple1_api:d1() can never succeed
+simple1_api.erl:142: Attempt to test for equality between a term of type
+ simple1_adt:o2() and a term of opaque type
+ simple1_adt:o1()
+simple1_api.erl:148: Guard test
+ simple1_adt:o2() =:=
+ A :: simple1_adt:o1() contains opaque terms as 1st and 2nd arguments
+simple1_api.erl:154: Attempt to test for inequality between a term of type
+ simple1_adt:o2() and a term of opaque type
+ simple1_adt:o1()
+simple1_api.erl:160: Attempt to test for inequality between a term of type
+ simple1_adt:o2() and a term of opaque type
+ simple1_adt:o1()
+simple1_api.erl:165: Attempt to test for equality between a term of type
+ simple1_adt:c2() and a term of opaque type
+ simple1_adt:c1()
+simple1_api.erl:181: Guard test
+ A :: simple1_adt:d1() =<
+ B :: simple1_adt:d2() contains opaque terms as 1st and 2nd arguments
+simple1_api.erl:185: Guard test
+ 'a' =<
+ B :: simple1_adt:d2() contains an opaque term as 2nd argument
+simple1_api.erl:189: Guard test
+ A :: simple1_adt:d1() =<
+ 'd' contains an opaque term as 1st argument
+simple1_api.erl:197: The type test is_integer
+ (A :: simple1_adt:d1()) breaks the opacity of the term A::
+ simple1_adt:d1()
+simple1_api.erl:221: Guard test
+ A :: simple1_api:i1() >
+ 3 can never succeed
+simple1_api.erl:225: Guard test
+ A :: simple1_adt:i1() >
+ 3 contains an opaque term as 1st argument
+simple1_api.erl:233: Guard test
+ A :: simple1_adt:i1() <
+ 3 contains an opaque term as 1st argument
+simple1_api.erl:239: Guard test
+ A :: 1 >
+ 3 can never succeed
+simple1_api.erl:243: Guard test
+ A :: 1 >
+ 3 can never succeed
+simple1_api.erl:257: Guard test is_function
+ (T :: simple1_api:o1()) can never succeed
+simple1_api.erl:265: Guard test is_function
+ (T :: simple1_adt:o1()) breaks the opacity of its argument
+simple1_api.erl:269: The type test is_function
+ (T :: simple1_adt:o1()) breaks the opacity of the term T::
+ simple1_adt:o1()
+simple1_api.erl:274: Guard test is_function
+ (T :: simple1_api:o1(),
+ A :: simple1_api:i1()) can never succeed
+simple1_api.erl:284: Guard test is_function
+ (T :: simple1_adt:o1(),
+ A :: simple1_adt:i1()) breaks the opacity of its argument
+simple1_api.erl:289: The type test is_function
+ (T :: simple1_adt:o1(),
+ A :: simple1_adt:i1()) breaks the opacity of the term T::
+ simple1_adt:o1()
+simple1_api.erl:294: The call erlang:is_function
+ (T :: simple1_api:o1(),
+ A :: simple1_adt:i1()) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+simple1_api.erl:300: The type test is_function
+ (T :: simple1_adt:o1(),
+ A :: simple1_api:i1()) breaks the opacity of the term T::
+ simple1_adt:o1()
+simple1_api.erl:306: Guard test
+ B :: simple1_api:b2() =:=
+ 'true' can never succeed
+simple1_api.erl:315: Guard test
+ A :: simple1_api:b1() =:=
+ 'false' can never succeed
+simple1_api.erl:319: Guard test not(and
+ ('true',
+ 'true')) can never succeed
+simple1_api.erl:337: Clause guard cannot succeed.
+simple1_api.erl:342: Guard test
+ B :: simple1_adt:b2() =:=
+ 'true' contains an opaque term as 1st argument
+simple1_api.erl:347: Guard test
+ A :: simple1_adt:b1() =:=
+ 'true' contains an opaque term as 1st argument
+simple1_api.erl:355: Invalid type specification for function simple1_api:bool_adt_t6/1. The success typing is
+ ('true') -> 1
+simple1_api.erl:365: Clause guard cannot succeed.
+simple1_api.erl:368: Invalid type specification for function simple1_api:bool_adt_t8/2. The success typing is
+ (boolean(), boolean()) -> 1
+simple1_api.erl:378: Clause guard cannot succeed.
+simple1_api.erl:381: Invalid type specification for function simple1_api:bool_adt_t9/2. The success typing is
+ ('false', 'false') -> 1
+simple1_api.erl:407: The size
+ simple1_adt:i1() breaks the opacity of A
+simple1_api.erl:418: The attempt to match a term of type
+ non_neg_integer() against the variable A breaks the opacity of
+ simple1_adt:i1()
+simple1_api.erl:425: The attempt to match a term of type
+ non_neg_integer() against the variable B breaks the opacity of
+ simple1_adt:i1()
+simple1_api.erl:432: The pattern
+ <<_:B>> can never match the type
+ any()
+simple1_api.erl:448: The attempt to match a term of type
+ non_neg_integer() against the variable Sz breaks the opacity of
+ simple1_adt:i1()
+simple1_api.erl:460: The attempt to match a term of type
+ simple1_adt:bit1() against the pattern
+ <<_/binary>> breaks the opacity of the term
+simple1_api.erl:478: The call 'foo':A
+ (A :: simple1_adt:a()) breaks the opacity of the term A ::
+ simple1_adt:a()
+simple1_api.erl:486: The call A:'foo'
+ (A :: simple1_adt:a()) breaks the opacity of the term A ::
+ simple1_adt:a()
+simple1_api.erl:499: The call 'foo':A
+ (A :: simple1_api:i()) requires that A is of type
+ atom() not
+ simple1_api:i()
+simple1_api.erl:503: The call 'foo':A
+ (A :: simple1_adt:i()) requires that A is of type
+ atom() not
+ simple1_adt:i()
+simple1_api.erl:507: The call A:'foo'
+ (A :: simple1_api:i()) requires that A is of type
+ atom() not
+ simple1_api:i()
+simple1_api.erl:511: The call A:'foo'
+ (A :: simple1_adt:i()) requires that A is of type
+ atom() not
+ simple1_adt:i()
+simple1_api.erl:519: Guard test
+ A :: simple1_adt:d2() ==
+ B :: simple1_adt:d1() contains opaque terms as 1st and 2nd arguments
+simple1_api.erl:534: Guard test
+ A :: simple1_adt:d1() >=
+ 3 contains an opaque term as 1st argument
+simple1_api.erl:536: Guard test
+ A :: simple1_adt:d1() ==
+ 3 contains an opaque term as 1st argument
+simple1_api.erl:538: Guard test
+ A :: simple1_adt:d1() =:=
+ 3 contains an opaque term as 1st argument
+simple1_api.erl:548: The call erlang:'<'
+ (A :: simple1_adt:d1(),
+ 3) contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple1_api.erl:558: The call erlang:'=<'
+ (A :: simple1_adt:d1(),
+ B :: simple1_adt:d2()) contains opaque terms as 1st and 2nd arguments when terms of different types are expected in these positions
+simple1_api.erl:565: Guard test
+ {digraph:graph(), 3} >
+ {digraph:graph(), atom() | ets:tid()} contains an opaque term as 2nd argument
+simple1_api.erl:91: The specification for simple1_api:tup/0 has an opaque subtype
+ simple1_adt:tuple1() which is violated by the success typing
+ () -> {'a', 'b'}
+simple2_api.erl:100: The call lists:flatten
+ (A :: simple1_adt:tuple1()) contains an opaque term as 1st argument when a structured term of type
+ [any()] is expected
+simple2_api.erl:116: The call lists:flatten
+ ({simple1_adt:tuple1()}) will never return since it differs in the 1st argument from the success typing arguments:
+ ([any()])
+simple2_api.erl:121: Guard test
+ {simple1_adt:d1(), 3} >
+ {simple1_adt:d1(), simple1_adt:tuple1()} contains an opaque term as 2nd argument
+simple2_api.erl:125: The call erlang:tuple_to_list
+ (B :: simple1_adt:tuple1()) contains an opaque term as 1st argument when a structured term of type
+ tuple() is expected
+simple2_api.erl:31: The call erlang:'!'
+ (A :: simple1_adt:d1(),
+ 'foo') contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple2_api.erl:35: The call erlang:send
+ (A :: simple1_adt:d1(),
+ 'foo') contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple2_api.erl:51: The call erlang:'<'
+ (A :: simple1_adt:d1(),
+ 3) contains an opaque term as 1st argument when terms of different types are expected in these positions
+simple2_api.erl:59: The call lists:keysearch
+ (1,
+ A :: simple1_adt:d1(),
+ []) contains an opaque term as 2nd argument when terms of different types are expected in these positions
+simple2_api.erl:67: The call lists:keysearch
+ ('key',
+ 1,
+ A :: simple1_adt:tuple1()) contains an opaque term as 3rd argument when terms of different types are expected in these positions
+simple2_api.erl:96: The call lists:keyreplace
+ ('a',
+ 1,
+ [{1, 2}],
+ A :: simple1_adt:tuple1()) contains an opaque term as 4th argument when terms of different types are expected in these positions
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/suppress_request b/lib/dialyzer/test/indent_SUITE_data/results/suppress_request
new file mode 100644
index 0000000000..c08f1798c1
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/suppress_request
@@ -0,0 +1,11 @@
+
+suppress_request.erl:21: Expression produces a value of type
+ {'a', 'b'}, but this value is unmatched
+suppress_request.erl:25: Expression produces a value of type
+ {'a', 'b'}, but this value is unmatched
+suppress_request.erl:39: Guard test
+ 2 =:=
+ A :: fun((none()) -> no_return()) can never succeed
+suppress_request.erl:7: Type specification suppress_request:test1
+ ('a' | 'b') -> 'ok' is a subtype of the success typing: suppress_request:test1
+ ('a' | 'b' | 'c') -> 'ok'
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/trec b/lib/dialyzer/test/indent_SUITE_data/results/trec
new file mode 100644
index 0000000000..f19f728750
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/trec
@@ -0,0 +1,10 @@
+
+trec.erl:29: The call trec:mk_foo_loc
+ (42,
+ any()) will never return since it differs in the 1st argument from the success typing arguments:
+ ('undefined',
+ atom())
+trec.erl:32: Record construction violates the declared type for #foo{} since variable A cannot be of type
+ atom()
+trec.erl:39: Record construction violates the declared type for #foo{} since variable A cannot be of type
+ atom()
diff --git a/lib/dialyzer/test/indent_SUITE_data/results/whereis_control_flow1 b/lib/dialyzer/test/indent_SUITE_data/results/whereis_control_flow1
new file mode 100644
index 0000000000..0e733fced6
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/results/whereis_control_flow1
@@ -0,0 +1,4 @@
+
+whereis_control_flow1.erl:13: The call erlang:register
+ (AnAtom :: atom(),
+ Pid :: pid()) might fail due to a possible race condition caused by its combination with the erlang:whereis(AnAtom::any()) call in whereis_control_flow1.erl on line 8
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/abs.erl b/lib/dialyzer/test/indent_SUITE_data/src/abs.erl
new file mode 100644
index 0000000000..0e38c3dbb7
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/abs.erl
@@ -0,0 +1,78 @@
+-module(abs).
+
+%% OTP-12948. erlang:abs/1 bug fix.
+
+-export([t/0]).
+
+t() ->
+ Fs = [fun i1/0, fun i2/0, fun i3/0, fun i4/0, fun f1/0, fun erl_551/0],
+ _ = [catch F() || F <- Fs],
+ ok.
+
+i1() ->
+ A = int(),
+ I1 = i1(A),
+ true = I1 < 2,
+ true = I1 < 1. % can never match
+
+-spec i1(neg_integer()) -> non_neg_integer().
+
+i1(A) when is_integer(A), A < 0 ->
+ abs(A).
+
+i2() ->
+ A = int(),
+ I2 = i2(A),
+ true = I2 < 1,
+ true = I2 < 0. % can never match
+
+-spec i2(non_neg_integer()) -> non_neg_integer().
+
+i2(A) when is_integer(A), A >= 0 ->
+ abs(A).
+
+i3() ->
+ A = int(),
+ I3 = i3(A),
+ true = I3 < -1,
+ true = I3 < 0. % can never match
+
+-spec i3(integer()) -> non_neg_integer().
+
+i3(A) when is_integer(A) ->
+ abs(A).
+
+i4() ->
+ A = int(),
+ I4 = i4(A),
+ true = I4 =:= 0 orelse I4 =:= 1,
+ true = I4 < 0 orelse I4 > 1. % can never match
+
+-spec i4(integer()) -> number().
+
+i4(A) when A =:= -1; A =:= 0; A =:= 1 ->
+ abs(A).
+
+f1() ->
+ F1 = f1(float()),
+ math:sqrt(F1).
+
+f1(A) ->
+ abs(A).
+
+erl_551() ->
+ accept(9),
+ accept(-3).
+
+accept(Number) when abs(Number) >= 8 -> first;
+accept(_Number) -> second.
+
+-spec int() -> integer().
+
+int() ->
+ foo:bar().
+
+-spec float() -> float().
+
+float() ->
+ math:sqrt(1.0).
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/app_call.erl b/lib/dialyzer/test/indent_SUITE_data/src/app_call.erl
new file mode 100644
index 0000000000..54d178d29a
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/app_call.erl
@@ -0,0 +1,17 @@
+-module(app_call).
+-export([test/1]).
+
+test(m) ->
+ M = get_mod(),
+ M:foo();
+test(f) ->
+ F = get_fun(),
+ mod:F();
+test(_) ->
+ ok.
+
+get_mod() ->
+ 42.
+
+get_fun() ->
+ {gazonk, []}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/blame_contract_range.erl b/lib/dialyzer/test/indent_SUITE_data/src/blame_contract_range.erl
new file mode 100644
index 0000000000..efd3332b44
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/blame_contract_range.erl
@@ -0,0 +1,16 @@
+%%-----------------------------------------------------------------------
+%% A test where the contract is wrongly specified by the programmer;
+%% however, this is found only by refinement.
+%% Dialyzer in R14B01 and prior gave a confusing (if not bogus) warning
+%% for this case. Corrected in R14B02.
+%%-----------------------------------------------------------------------
+-module(blame_contract_range).
+
+-export([foo/0]).
+
+foo() ->
+ bar(b).
+
+-spec bar(atom()) -> a.
+bar(a) -> a;
+bar(b) -> b.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/bs_fail_constr.erl b/lib/dialyzer/test/indent_SUITE_data/src/bs_fail_constr.erl
new file mode 100644
index 0000000000..8c1f8c009a
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/bs_fail_constr.erl
@@ -0,0 +1,15 @@
+-module(bs_fail_constr).
+
+-export([w1/1, w2/1, w3/1, w4/1]).
+
+w1(V) when is_float(V) ->
+ <<V/integer>>.
+
+w2(V) when is_atom(V) ->
+ <<V/binary>>.
+
+w3(S) when is_integer(S), S < 0 ->
+ <<42:S/integer>>.
+
+w4(V) when is_float(V) ->
+ <<V/utf32>>.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_behaviour.erl b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_behaviour.erl
new file mode 100644
index 0000000000..c4e5203448
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_behaviour.erl
@@ -0,0 +1,11 @@
+-module(my_behaviour).
+
+-callback callback_init(Parent :: pid()) -> {'ok', State::term()}.
+
+-callback callback_cast(State::term(), From::pid(), Msg::term()) ->
+ {'noreply', NewState::term()}.
+
+-callback callback_call(State::term(), From::pid(), Msg::term()) ->
+ {'reply', NewState::term(), Reply::term()}.
+
+-callback callback_exit(State::term()) -> 'ok'.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_correct.erl b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_correct.erl
new file mode 100644
index 0000000000..041b4ac56c
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_correct.erl
@@ -0,0 +1,59 @@
+-module(my_callbacks_correct).
+
+-export([
+ callback_init/1
+ , callback_call/3
+ , callback_cast/3
+ , callback_exit/1
+ ]).
+
+-record(state, {
+ parent :: pid(),
+ status = init :: 'init' | 'open' | 'closed',
+ subscribe = [] :: list({pid(), integer()}),
+ counter = 1 :: integer()
+ }).
+
+-type state() :: #state{}.
+
+-type cast_message() :: 'open' | 'closed'.
+
+-type call_message() :: 'subscribe' | 'unsubscribe'.
+-type call_reply() :: 'accepted' | 'rejected'.
+
+-spec callback_init(Parent::pid()) -> {'ok', state()}.
+
+callback_init(Parent) ->
+ {ok, #state{parent = Parent}}.
+
+-spec callback_cast(state(), pid(), cast_message()) -> {'noreply', state()}.
+
+callback_cast(#state{parent = Pid} = State, Pid, Message)
+ when Message =:= 'open'; Message =:= 'close' ->
+ {noreply, State#state{status = Message}};
+callback_cast(State, _Pid, _Message) ->
+ {noreply, State}.
+
+-spec callback_call(state(), pid(), call_message()) ->
+ {'reply', state(), call_reply()}.
+
+callback_call(#state{status = open, subscribe = Subscribers} = State,
+ Pid, Message)
+ when Message =:= 'subscribe';
+ Message =:= 'unsubscribe' ->
+ NewState =
+ case Message of
+ subscribe ->
+ N = State#state.counter,
+ State#state{subscribe = [{Pid, N}|Subscribers], counter = N+1};
+ unsubscribe ->
+ State#state{subscribe = lists:keydelete(Pid, 1, Subscribers)}
+ end,
+ {reply, NewState, accepted};
+callback_call(State, _Pid, _Message) ->
+ {reply, State, rejected}.
+
+-spec callback_exit(state()) -> 'ok'.
+
+callback_exit(_State) ->
+ ok.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
new file mode 100644
index 0000000000..0459622dc1
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/callbacks_and_specs/my_callbacks_wrong.erl
@@ -0,0 +1,61 @@
+-module(my_callbacks_wrong).
+
+-export([
+ callback_init/1
+ , callback_call/3
+ , callback_cast/3
+ , callback_exit/1
+ ]).
+
+-behaviour(my_behaviour).
+
+-record(state, {
+ parent :: pid(),
+ status = init :: 'init' | 'open' | 'closed',
+ subscribe = [] :: list({pid(), integer()}),
+ counter = 1 :: integer()
+ }).
+
+-type state() :: #state{}.
+
+-type cast_message() :: 'open' | 'closed'.
+
+-type call_message() :: 'subscribe' | 'unsubscribe'.
+-type call_reply() :: 'accepted' | 'rejected'.
+
+-spec callback_init(Parent::pid()) -> state(). %% Wrong return spec
+
+callback_init(Parent) -> #state{parent = Parent}. %% Wrong return
+
+-spec callback_cast(state(), pid() | atom(), cast_message()) ->
+ {'noreply' | 'reply', state()}. %% More generic spec
+
+callback_cast(#state{parent = Pid} = State, Pid, Message)
+ when Message =:= 'open'; Message =:= 'close' ->
+ {noreply, State#state{status = Message}};
+callback_cast(State, _Pid, _Message) ->
+ {noreply, State}.
+
+-spec callback_call(state(), atom(), call_message()) -> %% Wrong arg spec
+ {'reply', state(), call_reply()}.
+
+callback_call(#state{status = open, subscribe = Subscribers} = State,
+ Pid, Message)
+ when Message =:= 'subscribe';
+ Message =:= 'unsubscribe' ->
+ NewState =
+ case Message of
+ subscribe ->
+ N = State#state.counter,
+ State#state{subscribe = [{Pid, N}|Subscribers], counter = N+1};
+ unsubscribe ->
+ State#state{subscribe = lists:keydelete(Pid, 1, Subscribers)}
+ end,
+ {reply, NewState, accepted};
+callback_call(State, _Pid, _Message) ->
+ {reply, State, rejected}.
+
+-spec callback_exit(state()) -> ok.
+
+callback_exit(_State) ->
+ ok.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/contract3.erl b/lib/dialyzer/test/indent_SUITE_data/src/contract3.erl
new file mode 100644
index 0000000000..a6ce91882e
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/contract3.erl
@@ -0,0 +1,40 @@
+%%%-------------------------------------------------------------------
+%%% File : contract3.erl
+%%% Author : Tobias Lindahl <[email protected]>
+%%% Description : Check overloaded domains
+%%%
+%%% Created : 2 Nov 2007 by Tobias Lindahl <[email protected]>
+%%%-------------------------------------------------------------------
+-module(contract3).
+
+-export([t/3]).
+
+t(X, Y, Z) ->
+ t1(X),
+ t2(X, Y),
+ t3(X, Y, Z).
+
+-spec t1(atom()|integer()) -> integer();
+ (atom()|list()) -> atom().
+
+t1(X) ->
+ f(X).
+
+-spec t2(atom(), integer()) -> integer();
+ (atom(), list()) -> atom().
+
+t2(X, Y) ->
+ g(X, Y).
+
+-spec t3(atom(), integer(), list()) -> integer();
+ (X, integer(), list()) -> X.
+
+t3(X, Y, Z) ->
+ X.
+
+%% dummy functions below
+
+f(X) -> X.
+
+g(X, Y) when is_atom(X), is_integer(Y) -> Y;
+g(X, Y) when is_atom(X), is_list(Y) -> X.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/contracts_with_subtypes.erl b/lib/dialyzer/test/indent_SUITE_data/src/contracts_with_subtypes.erl
new file mode 100644
index 0000000000..dbabd904c2
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/contracts_with_subtypes.erl
@@ -0,0 +1,300 @@
+-module(contracts_with_subtypes).
+
+-compile(export_all).
+
+%===============================================================================
+
+-spec extract() -> 'ok'.
+
+extract() ->
+ case dz_extract() of
+ {ok, Val} -> Val;
+ error -> exit(boom)
+ end.
+
+-spec dz_extract() -> RetValue when
+ FileList :: something,
+ RetValue :: {ok, FileList} | error.
+
+dz_extract() -> get(foo).
+
+%-------------------------------------------------------------------------------
+
+-spec extract2() -> 'ok'.
+
+extract2() ->
+ case dz_extract2() of
+ {ok, Val} -> Val;
+ error -> exit(boom)
+ end.
+
+-spec dz_extract2() -> RetValue when
+ RetValue :: {ok, FileList} | error,
+ FileList :: something.
+
+dz_extract2() -> get(foo).
+
+%===============================================================================
+
+-spec foo1(Arg1) -> Res when
+ Arg1 :: atom(),
+ Res :: atom().
+
+foo1(X) -> X.
+
+-spec foo2(Arg1) -> Res when
+ Arg1 :: Arg2,
+ Arg2 :: atom(),
+ Res :: atom().
+
+foo2(X) -> X.
+
+-spec foo3(Arg1) -> Res when
+ Arg2 :: atom(),
+ Arg1 :: Arg2,
+ Res :: atom().
+
+foo3(X) -> X.
+
+-spec foo4(Type) -> Type when is_subtype(Type, atom()).
+
+foo4(X) -> X.
+
+-spec foo5(Type :: atom()) -> Type :: atom().
+
+foo5(X) -> X.
+
+-spec foo6(Type) -> Type when Type :: atom().
+
+foo6(X) -> X.
+
+-spec foo7(Type) -> Type.
+
+foo7(X) -> X.
+
+%-------------------------------------------------------------------------------
+
+bar(1) -> foo1(5);
+bar(2) -> foo2(5);
+bar(3) -> foo3(5);
+bar(4) -> foo4(5);
+bar(5) -> foo5(5);
+bar(6) -> foo6(5);
+bar(7) -> foo7(5).
+
+wrong_foo6() ->
+ b = foo6(a).
+
+%===============================================================================
+
+-spec rec_arg(Arg) -> ok when
+ Arg :: {a, A} | {b, B},
+ A :: a | {b, B},
+ B :: b | {a, A}.
+
+rec_arg(X) -> get(X).
+
+c(aa) -> rec_arg({a, a});
+c(bb) -> rec_arg({b, b});
+c(abb) -> rec_arg({a, {b, b}});
+c(baa) -> rec_arg({b, {a, a}});
+c(abaa) -> rec_arg({a, {b, {a, a}}});
+c(babb) -> rec_arg({b, {a, {b, b}}});
+c(ababb) -> rec_arg({a, {b, {a, {b, b}}}});
+c(babaa) -> rec_arg({b, {a, {b, {a, a}}}}).
+
+w(ab) -> rec_arg({a, b}); % breaks the contract
+w(ba) -> rec_arg({b, a}); % breaks the contract
+w(aba) -> rec_arg({a, {b, a}}); % no longer breaks the contract
+w(bab) -> rec_arg({b, {a, b}}); % breaks the contract
+w(abab) -> rec_arg({a, {b, {a, b}}}); % no longer breaks the contract
+w(baba) -> rec_arg({b, {a, {b, a}}}); % no longer breaks the contract
+w(ababa) -> rec_arg({a, {b, {a, {b, a}}}});
+w(babab) -> rec_arg({b, {a, {b, {a, b}}}}).
+
+%% For comparison: the same thing with types
+
+-type ab() :: {a, a()} | {b, b()}.
+-type a() :: a | {b, b()}.
+-type b() :: b | {a, a()}.
+
+-spec rec2(Arg) -> ok when
+ Arg :: ab().
+
+rec2(X) -> get(X).
+
+d(aa) -> rec2({a, a});
+d(bb) -> rec2({b, b});
+d(abb) -> rec2({a, {b, b}});
+d(baa) -> rec2({b, {a, a}});
+d(abaa) -> rec2({a, {b, {a, a}}});
+d(babb) -> rec2({b, {a, {b, b}}});
+d(ababb) -> rec2({a, {b, {a, {b, b}}}});
+d(babaa) -> rec2({b, {a, {b, {a, a}}}}).
+
+q(ab) -> rec2({a, b}); % breaks the contract
+q(ba) -> rec2({b, a}); % breaks the contract
+q(aba) -> rec2({a, {b, a}}); % breaks the contract
+q(bab) -> rec2({b, {a, b}}); % breaks the contract
+q(abab) -> rec2({a, {b, {a, b}}}); % breaks the contract
+q(baba) -> rec2({b, {a, {b, a}}}); % breaks the contract
+q(ababa) -> rec2({a, {b, {a, {b, a}}}}); % breaks the contract
+q(babab) -> rec2({b, {a, {b, {a, b}}}}); % breaks the contract
+q(ababab) -> rec2({a, {b, {a, {b, {a, b}}}}});
+q(bababa) -> rec2({b, {a, {b, {a, {b, a}}}}});
+q(abababa) -> rec2({a, {b, {a, {b, {a, {b, a}}}}}});
+q(bababab) -> rec2({b, {a, {b, {a, {b, {a, b}}}}}}).
+
+%===============================================================================
+
+-type dublo(X) :: {X, X}.
+
+-type weird(X,Y) :: {X, Y, X, X}.
+
+-spec forfun(dublo(Var)) -> ok when Var :: atom().
+
+forfun(_) -> ok.
+
+-spec forfun2(weird(Var, Var)) -> ok when Var :: atom().
+
+forfun2(_) -> ok.
+
+%===============================================================================
+
+-spec shallow(X) -> {ok, X} | {ok, X, file:filename()} | err1 | err2.
+
+shallow(X) -> get(X).
+
+st(X) when is_atom(X) ->
+ case shallow(X) of
+ err1 -> ok;
+ err2 -> ok;
+ {ok, X} -> ok;
+ {ok, X, Res} ->
+ case Res of
+ 1 -> bad;
+ _Other -> ok
+ end;
+ alpha -> bad;
+ {ok, 42} -> ok;
+ 42 -> bad
+ end.
+
+%-------------------------------------------------------------------------------
+
+-spec deep(X) -> Ret when
+ Ret :: {ok, X} | Err,
+ Err :: err1 | err2.
+
+deep(X) -> get(X).
+
+dt(X) when is_atom(X) ->
+ case deep(X) of
+ err1 -> ok;
+ err2 -> ok;
+ {ok, X} -> ok;
+ alpha -> bad;
+ {ok, 42} -> ok;
+ 42 -> bad
+ end.
+
+%-------------------------------------------------------------------------------
+
+-type local_errors() :: err1 | err2.
+
+-spec deep2(X) -> Ret when
+ Ret :: {ok, X} | Err,
+ Err :: local_errors().
+
+deep2(X) -> get(X).
+
+dt2(X) when is_atom(X) ->
+ case deep2(X) of
+ err1 -> ok;
+ err2 -> ok;
+ {ok, X} -> ok;
+ alpha -> bad;
+ {ok, 42} -> ok;
+ 42 -> bad
+ end.
+
+%-------------------------------------------------------------------------------
+
+-spec deep3(X) -> Ret when
+ Ret :: {ok, X, file:filename()} | Err,
+ Err :: local_errors().
+
+deep3(X) -> get(X).
+
+dt3(X) when is_atom(X) ->
+ case deep3(X) of
+ err1 -> ok;
+ err2 -> ok;
+ {ok, X, Res} ->
+ case Res of
+ 1 -> bad;
+ _Other -> ok
+ end;
+ {ok, X} -> bad;
+ alpha -> bad;
+ {ok, 42} -> bad;
+ 42 -> bad
+ end.
+
+%===============================================================================
+
+-spec flat_ets_new(Name, Options) -> atom() when
+ Name :: atom(),
+ Options :: [Option],
+ Option :: set
+ | ordered_set
+ | bag
+ | duplicate_bag
+ | public
+ | protected
+ | private
+ | named_table
+ | {keypos, integer()}
+ | {heir, pid(), term()}
+ | {heir, none}
+ | {write_concurrency, boolean()}
+ | {read_concurrency, boolean()}
+ | compressed.
+
+flat_ets_new(Name, Options) ->
+ get({Name, Options}).
+
+flat_ets_new_t() ->
+ flat_ets_new(12,[]),
+ flat_ets_new({a,b},[]),
+ flat_ets_new(name,[foo]),
+ flat_ets_new(name,{bag}),
+ flat_ets_new(name,bag),
+ ok.
+
+-type access() :: public | protected | private.
+-type type() :: set | ordered_set | bag | duplicate_bag.
+
+-spec factored_ets_new(Name, Options) -> atom() when
+ Name :: atom(),
+ Options :: [Option],
+ Option :: Type | Access | named_table | {keypos,Pos}
+ | {heir, Pid :: pid(), HeirData} | {heir, none} | Tweaks,
+ Type :: type(),
+ Access :: access(),
+ Tweaks :: {write_concurrency, boolean()}
+ | {read_concurrency, boolean()}
+ | compressed,
+ Pos :: pos_integer(),
+ HeirData :: term().
+
+factored_ets_new(Name, Options) ->
+ get({Name, Options}).
+
+factored_ets_new_t() ->
+ factored_ets_new(12,[]),
+ factored_ets_new({a,b},[]),
+ factored_ets_new(name,[foo]),
+ factored_ets_new(name,{bag}),
+ factored_ets_new(name,bag),
+ ok.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/dict_use.erl b/lib/dialyzer/test/indent_SUITE_data/src/dict_use.erl
new file mode 100644
index 0000000000..2527f166f2
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/dict_use.erl
@@ -0,0 +1,82 @@
+-module(dict_use).
+
+-export([ok1/0, ok2/0, ok3/0, ok4/0, ok5/0, ok6/0]).
+-export([middle/0]).
+-export([w1/0, w2/0, w3/0, w4/1, w5/0, w6/0, w7/0, w8/1, w9/0]).
+
+-define(DICT, dict).
+
+%%---------------------------------------------------------------------
+%% Cases that are OK
+%%---------------------------------------------------------------------
+
+ok1() ->
+ dict:new().
+
+ok2() ->
+ case dict:new() of X -> X end.
+
+ok3() ->
+ Dict1 = dict:new(),
+ Dict2 = dict:new(),
+ Dict1 =:= Dict2.
+
+ok4() ->
+ dict:fetch(foo, dict:new()).
+
+ok5() -> % this is OK since some_mod:new/0 might be returning a dict:dict()
+ dict:fetch(foo, some_mod:new()).
+
+ok6() ->
+ dict:store(42, elli, dict:new()).
+
+middle() ->
+ {w1(), w2()}.
+
+%%---------------------------------------------------------------------
+%% Cases that are problematic w.r.t. opacity of types
+%%---------------------------------------------------------------------
+
+w1() ->
+ gazonk = dict:new().
+
+w2() ->
+ case dict:new() of
+ [] -> nil;
+ 42 -> weird
+ end.
+
+w3() ->
+ try dict:new() of
+ [] -> nil;
+ 42 -> weird
+ catch
+ _:_ -> exception
+ end.
+
+w4(Dict) when is_list(Dict) ->
+ Dict =:= dict:new();
+w4(Dict) when is_atom(Dict) ->
+ Dict =/= dict:new().
+
+w5() ->
+ case dict:new() of
+ D when length(D) =/= 42 -> weird;
+ D when is_atom(D) -> weirder;
+ D when is_list(D) -> gazonk
+ end.
+
+w6() ->
+ is_list(dict:new()).
+
+w7() ->
+ dict:fetch(foo, [1,2,3]).
+
+w8(Fun) ->
+ dict:merge(Fun, 42, [1,2]).
+
+w9() ->
+ dict:store(42, elli,
+ {dict,0,16,16,8,80,48,
+ {[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},
+ {{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}).
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/fun_app.erl b/lib/dialyzer/test/indent_SUITE_data/src/fun_app.erl
new file mode 100644
index 0000000000..20b6138d26
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/fun_app.erl
@@ -0,0 +1,41 @@
+%% This is taken from the code of distel.
+
+-module(fun_app).
+-export([html_index/2]). % , lines/3, curry/2]).
+
+html_index(file,Dir) ->
+ fold_file(curry(fun lines/3,Dir),[],filename:join([Dir,"doc","man_index.html"])).
+
+fold_file(Fun,Acc0,File) ->
+ {ok, FD} = file:open(File, [read]),
+ Acc = fold_file_lines(FD,Fun,Acc0),
+ file:close(FD),
+ Acc.
+
+fold_file_lines(FD,Fun,Acc) ->
+ case io:get_line(FD, "") of
+ eof -> Acc;
+ Line -> fold_file_lines(FD,Fun,Fun(trim_nl(Line),Acc))
+ end.
+
+trim_nl(Str) -> lists:reverse(tl(lists:reverse(Str))).
+
+lines(Line,_,Dir) ->
+ case string:tokens(Line, "<> \"") of
+ ["TD", "A", "HREF=", "../"++Href, M|_] ->
+ case filename:basename(Href, ".html") of
+ "index" -> ok;
+ M -> e_set({file,M}, filename:join([Dir,Href]))
+ end;
+ _ -> ok
+ end.
+
+e_set(Key,Val) -> ets:insert(?MODULE, {Key,Val}).
+
+curry(F, Arg) ->
+ case erlang:fun_info(F,arity) of
+ {_,1} -> fun() -> F(Arg) end;
+ {_,2} -> fun(A) -> F(A,Arg) end;
+ {_,3} -> fun(A,B) -> F(A,B,Arg) end;
+ {_,4} -> fun(A,B,C) -> F(A,B,C,Arg) end
+ end.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/fun_app_args.erl b/lib/dialyzer/test/indent_SUITE_data/src/fun_app_args.erl
new file mode 100644
index 0000000000..b4409bc550
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/fun_app_args.erl
@@ -0,0 +1,12 @@
+-module(fun_app_args).
+
+-export([t/1]).
+
+-type ft() :: fun((a, []) -> any()).
+
+-record(r, {
+ h = c :: c | ft()
+}).
+
+t(#r{h = H}) ->
+ fun(_) -> (H)(b, []) end.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/guard_update.erl b/lib/dialyzer/test/indent_SUITE_data/src/guard_update.erl
new file mode 100644
index 0000000000..19d0089401
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/guard_update.erl
@@ -0,0 +1,18 @@
+-module(guard_update).
+
+-export([t/0, t2/0]).
+
+t() ->
+ f(#{a=>2}). %% Illegal
+
+f(M)
+ when M#{b := 7} =/= q
+ -> ok.
+
+t2() ->
+ f2(#{a=>2}). %% Legal!
+
+f2(M)
+ when M#{b := 7} =/= q;
+ M =/= p
+ -> ok.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/guard_warnings.erl b/lib/dialyzer/test/indent_SUITE_data/src/guard_warnings.erl
new file mode 100644
index 0000000000..6ab13eef9a
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/guard_warnings.erl
@@ -0,0 +1,118 @@
+%%
+%% A couple of tests for booleans in guards.
+%% Tests with suffix w have incomplete results due to weak dataflow.
+%% Tests with suffix ww have incomplete results due to weak dialyzer.
+%% Tests with suffix x should not give warnings.
+%%
+
+-module(and_bug).
+
+-compile(export_all).
+
+test1(X) when X and not X -> never.
+
+test2(X) when not X and X -> never.
+
+test3(X) when (X and not X) =:= true -> never.
+
+test4(X) when (not X and X) =:= true -> never.
+
+test5(X) when (X and not X) == true -> never.
+
+test6(X) when (not X and X) == true -> never.
+
+test7_w(X) when not (X or not X) -> never.
+
+test8_w(X) when not (not X or X) -> never.
+
+test9(X) when (X or not X) =:= false -> never.
+
+test10(X) when (not X or X) =:= false -> never.
+
+test11(X) when (X or not X) == false -> never.
+
+test12(X) when (not X or X) == false -> never.
+
+test13(X) when X and false -> never.
+
+test14(X) when false and X -> never.
+
+test15(X) when (X and false) =:= true -> never.
+
+test16(X) when (false and X) =:= true -> never.
+
+test17(X) when (X and false) == true -> never.
+
+test18(X) when (false and X) == true -> never.
+
+test19(X) when not (true or X) -> never.
+
+test20(X) when not (X or true) -> never.
+
+test21(X) when (true or X) =:= false -> never.
+
+test22(X) when (X or true) =:= false -> never.
+
+test23(X) when (true or X) == false -> never.
+
+test24(X) when (X or true) == false -> never.
+
+test25(X) when (false and X) -> never.
+
+test26(X) when (X and false) -> never.
+
+test27(X) when (false and X) =:= true -> never.
+
+test28(X) when (X and false) =:= true -> never.
+
+test29(X) when (false and X) == true -> never.
+
+test30(X) when (X and false) == true -> never.
+
+test31() when false and false -> never.
+
+test32() when (false and false) =:= true -> never.
+
+test33() when not (true and true) =:= true -> never.
+
+test34() when (false and false) == true -> never.
+
+test35() when not (true and true) == true -> never.
+
+test36() when false or false -> never.
+
+test37() when (false or false) =:= true -> never.
+
+test38() when not (false or false) =:= false -> never.
+
+test39() when (false or false) == true -> never.
+
+test40() when not (false or false) == false -> never.
+
+test41() when true =:= false -> never.
+
+test42() when true == false -> never.
+
+test43() when not (true =:= true) -> never.
+
+test44() when not (true == true) -> never.
+
+test45() when not (not (not (not (not (not (not true)))))) -> never.
+
+test46(X) when (X =:= true) and (X =:= false) -> never.
+
+test47(X) when (X == true) and (X == false) -> never.
+
+test48(X) when is_boolean(X) and (X =:= true) and (X =/= true) -> never.
+
+test49_x(X) when not (X or X) -> maybe.
+
+test50_x(X) when not (X and X) -> maybe.
+
+test51_x(X) when not (not X) -> maybe.
+
+test52_w(X) when is_boolean(X) and (X =/= true) and (X =:= true) -> never.
+
+test53_ww(X) when is_boolean(X) and (X =/= true) and (X =/= false) -> never.
+
+test54_w(X) when is_boolean(X) and not ((X =:= true) or (X =:= false)) -> never.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl b/lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl
new file mode 100644
index 0000000000..99eb73a5f6
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/map_galore.erl
@@ -0,0 +1,2800 @@
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2013. All Rights Reserved.
+%%
+%% Licensed under the Apache License, Version 2.0 (the "License");
+%% you may not use this file except in compliance with the License.
+%% You may obtain a copy of the License at
+%%
+%% http://www.apache.org/licenses/LICENSE-2.0
+%%
+%% Unless required by applicable law or agreed to in writing, software
+%% distributed under the License is distributed on an "AS IS" BASIS,
+%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+%% See the License for the specific language governing permissions and
+%% limitations under the License.
+%%
+%% %CopyrightEnd%
+%%
+-module(map_galore).
+-export([all/0, suite/0,groups/0,init_per_suite/1, end_per_suite/1,
+ init_per_group/2,end_per_group/2
+ ]).
+
+-export([
+ t_build_and_match_literals/1, t_build_and_match_literals_large/1,
+ t_update_literals/1, t_update_literals_large/1,
+ t_match_and_update_literals/1, t_match_and_update_literals_large/1,
+ t_update_map_expressions/1,
+ t_update_assoc/1, t_update_assoc_large/1,
+ t_update_exact/1, t_update_exact_large/1,
+ t_guard_bifs/1,
+ t_guard_sequence/1, t_guard_sequence_large/1,
+ t_guard_update/1, t_guard_update_large/1,
+ t_guard_receive/1, t_guard_receive_large/1,
+ t_guard_fun/1,
+ t_update_deep/1,
+ t_list_comprehension/1,
+ t_map_sort_literals/1,
+ t_map_equal/1,
+ t_map_compare/1,
+ t_map_size/1,
+ t_is_map/1,
+
+ %% Specific Map BIFs
+ t_bif_map_get/1,
+ t_bif_map_find/1,
+ t_bif_map_is_key/1,
+ t_bif_map_keys/1,
+ t_bif_map_merge/1,
+ t_bif_map_new/1,
+ t_bif_map_put/1,
+ t_bif_map_remove/1,
+ t_bif_map_update/1,
+ t_bif_map_values/1,
+ t_bif_map_to_list/1,
+ t_bif_map_from_list/1,
+
+ %% erlang
+ t_erlang_hash/1,
+ t_map_encode_decode/1,
+
+ %% non specific BIF related
+ t_bif_build_and_check/1,
+ t_bif_merge_and_check/1,
+
+ %% maps module not bifs
+ t_maps_fold/1,
+ t_maps_map/1,
+ t_maps_size/1,
+ t_maps_without/1,
+
+ %% misc
+ t_erts_internal_order/1,
+ t_erts_internal_hash/1,
+ t_pdict/1,
+ t_ets/1,
+ t_dets/1,
+ t_tracing/1,
+
+ %% instruction-level tests
+ t_has_map_fields/1,
+ y_regs/1
+ ]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+
+-define(CHECK(Cond,Term),
+ case (catch (Cond)) of
+ true -> true;
+ _ -> io:format("###### CHECK FAILED ######~nINPUT: ~p~n", [Term]),
+ exit(Term)
+ end).
+
+suite() -> [].
+
+all() -> [
+ t_build_and_match_literals, t_build_and_match_literals_large,
+ t_update_literals, t_update_literals_large,
+ t_match_and_update_literals, t_match_and_update_literals_large,
+ t_update_map_expressions,
+ t_update_assoc, t_update_assoc_large,
+ t_update_exact, t_update_exact_large,
+ t_guard_bifs,
+ t_guard_sequence, t_guard_sequence_large,
+ t_guard_update, t_guard_update_large,
+ t_guard_receive, t_guard_receive_large,
+ t_guard_fun, t_list_comprehension,
+ t_update_deep,
+ t_map_equal, t_map_compare,
+ t_map_sort_literals,
+
+ %% Specific Map BIFs
+ t_bif_map_get,t_bif_map_find,t_bif_map_is_key,
+ t_bif_map_keys, t_bif_map_merge, t_bif_map_new,
+ t_bif_map_put,
+ t_bif_map_remove, t_bif_map_update,
+ t_bif_map_values,
+ t_bif_map_to_list, t_bif_map_from_list,
+
+ %% erlang
+ t_erlang_hash, t_map_encode_decode,
+ t_map_size, t_is_map,
+
+ %% non specific BIF related
+ t_bif_build_and_check,
+ t_bif_merge_and_check,
+
+ %% maps module
+ t_maps_fold, t_maps_map,
+ t_maps_size, t_maps_without,
+
+
+ %% Other functions
+ t_erts_internal_order,
+ t_erts_internal_hash,
+ t_pdict,
+ t_ets,
+ t_tracing,
+
+ %% instruction-level tests
+ t_has_map_fields,
+ y_regs
+ ].
+
+groups() -> [].
+
+init_per_suite(Config) -> Config.
+end_per_suite(_Config) -> ok.
+
+init_per_group(_GroupName, Config) -> Config.
+end_per_group(_GroupName, Config) -> Config.
+
+%% tests
+
+t_build_and_match_literals(Config) when is_list(Config) ->
+ #{} = #{},
+ #{1:=a} = #{1=>a},
+ #{1:=a,2:=b} = #{1=>a,2=>b},
+ #{1:=a,2:=b,3:="c"} = #{1=>a,2=>b,3=>"c"},
+ #{1:=a,2:=b,3:="c","4":="d"} = #{1=>a,2=>b,3=>"c","4"=>"d"},
+ #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>} =
+ #{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>},
+ #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f"} =
+ #{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f"},
+ #{1:=a,2:=b,3:="c","4":="d",<<"5">>:=<<"e">>,{"6",7}:="f",8:=g} =
+ #{1=>a,2=>b,3=>"c","4"=>"d",<<"5">>=><<"e">>,{"6",7}=>"f",8=>g},
+
+ #{[]:=a,42.0:=b,x:={x,y},[a,b]:=list} =
+ #{[]=>a,42.0=>b,x=>{x,y},[a,b]=>list},
+
+ #{<<"hi all">> := 1} = #{<<"hi",32,"all">> => 1},
+
+ #{a:=X,a:=X=3,b:=4} = #{a=>3,b=>4}, % weird but ok =)
+
+ #{ a:=#{ b:=#{c := third, b:=second}}, b:=first} =
+ #{ b=>first, a=>#{ b=>#{c => third, b=> second}}},
+
+ M = #{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first},
+ M = #{ map_1:=#{ map_2:=#{value_3 := third}, value_2:= second}, value_1:=first} =
+ #{ map_1=>#{ map_2=>#{value_3 => third}, value_2=> second}, value_1=>first},
+
+ %% error case
+ %V = 32,
+ %{'EXIT',{{badmatch,_},_}} = (catch (#{<<"hi all">> => 1} = #{<<"hi",V,"all">> => 1})),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3,x:=2} = #{x=>3})),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=2} = #{x=>3})),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = {a,b,c})),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = #{y=>3})),
+ {'EXIT',{{badmatch,_},_}} = (catch (#{x:=3} = #{x=>"three"})),
+ ok.
+
+t_build_and_match_literals_large(Config) when is_list(Config) ->
+ % normal non-repeating
+ M0 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" },
+
+ #{10:=a0,20:=b0,30:="c0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M0,
+ #{11:=a1,21:=b1,31:="c1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M0,
+ #{12:=a2,22:=b2,32:="c2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M0,
+ #{13:=a3,23:=b3,33:="c3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M0,
+ #{14:=a4,24:=b4,34:="c4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M0,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M0,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M0,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M0,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M0,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M0,
+
+ 60 = map_size(M0),
+ 60 = maps:size(M0),
+
+ % with repeating
+ M1 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 10=>na0,20=>nb0,30=>"nc0","40"=>"nd0",<<"50">>=>"ne0",{["00"]}=>"n10",
+ 11=>na1,21=>nb1,31=>"nc1","41"=>"nd1",<<"51">>=>"ne1",{["01"]}=>"n11",
+ 12=>na2,22=>nb2,32=>"nc2","42"=>"nd2",<<"52">>=>"ne2",{["02"]}=>"n12",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+
+ 13=>na3,23=>nb3,33=>"nc3","43"=>"nd3",<<"53">>=>"ne3",{["03"]}=>"n13",
+ 14=>na4,24=>nb4,34=>"nc4","44"=>"nd4",<<"54">>=>"ne4",{["04"]}=>"n14",
+
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" },
+
+ #{10:=na0,20:=nb0,30:="nc0","40":="nd0",<<"50">>:="ne0",{["00"]}:="n10"} = M1,
+ #{11:=na1,21:=nb1,31:="nc1","41":="nd1",<<"51">>:="ne1",{["01"]}:="n11"} = M1,
+ #{12:=na2,22:=nb2,32:="nc2","42":="nd2",<<"52">>:="ne2",{["02"]}:="n12"} = M1,
+ #{13:=na3,23:=nb3,33:="nc3","43":="nd3",<<"53">>:="ne3",{["03"]}:="n13"} = M1,
+ #{14:=na4,24:=nb4,34:="nc4","44":="nd4",<<"54">>:="ne4",{["04"]}:="n14"} = M1,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M1,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M1,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M1,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M1,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M1,
+
+ 60 = map_size(M1),
+ 60 = maps:size(M1),
+
+ % with floats
+
+ M2 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9"},
+
+ #{10:=a0,20:=b0,30:="c0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M2,
+ #{11:=a1,21:=b1,31:="c1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M2,
+ #{12:=a2,22:=b2,32:="c2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M2,
+ #{13:=a3,23:=b3,33:="c3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M2,
+ #{14:=a4,24:=b4,34:="c4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M2,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M2,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M2,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M2,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M2,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M2,
+
+ #{10.0:=fa0,20.0:=fb0,30.0:="fc0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M2,
+ #{11.0:=fa1,21.0:=fb1,31.0:="fc1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M2,
+ #{12.0:=fa2,22.0:=fb2,32.0:="fc2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M2,
+ #{13.0:=fa3,23.0:=fb3,33.0:="fc3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M2,
+ #{14.0:=fa4,24.0:=fb4,34.0:="fc4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M2,
+
+ #{15.0:=fa5,25.0:=fb5,35.0:="fc5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M2,
+ #{16.0:=fa6,26.0:=fb6,36.0:="fc6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M2,
+ #{17.0:=fa7,27.0:=fb7,37.0:="fc7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M2,
+ #{18.0:=fa8,28.0:=fb8,38.0:="fc8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M2,
+ #{19.0:=fa9,29.0:=fb9,39.0:="fc9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M2,
+
+ 90 = map_size(M2),
+ 90 = maps:size(M2),
+
+ % with bignums
+ M3 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ 36893488147419103232=>big1, 73786976294838206464=>big2,
+ 147573952589676412928=>big3, 18446744073709551616=>big4,
+ 4294967296=>big5, 8589934592=>big6,
+ 4294967295=>big7, 67108863=>big8
+ },
+
+ #{10:=a0,20:=b0,30:="c0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M3,
+ #{11:=a1,21:=b1,31:="c1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M3,
+ #{12:=a2,22:=b2,32:="c2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M3,
+ #{13:=a3,23:=b3,33:="c3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M3,
+ #{14:=a4,24:=b4,34:="c4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M3,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M3,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M3,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M3,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M3,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M3,
+
+ #{10.0:=fa0,20.0:=fb0,30.0:="fc0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M3,
+ #{11.0:=fa1,21.0:=fb1,31.0:="fc1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M3,
+ #{12.0:=fa2,22.0:=fb2,32.0:="fc2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M3,
+ #{13.0:=fa3,23.0:=fb3,33.0:="fc3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M3,
+ #{14.0:=fa4,24.0:=fb4,34.0:="fc4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M3,
+
+ #{15.0:=fa5,25.0:=fb5,35.0:="fc5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M3,
+ #{16.0:=fa6,26.0:=fb6,36.0:="fc6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M3,
+ #{17.0:=fa7,27.0:=fb7,37.0:="fc7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M3,
+ #{18.0:=fa8,28.0:=fb8,38.0:="fc8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M3,
+ #{19.0:=fa9,29.0:=fb9,39.0:="fc9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M3,
+
+ #{36893488147419103232:=big1,67108863:=big8,"45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M3,
+ #{147573952589676412928:=big3,8589934592:=big6,"46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M3,
+ #{4294967296:=big5,18446744073709551616:=big4,"47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M3,
+ #{4294967295:=big7,73786976294838206464:=big2,"48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M3,
+
+ 98 = map_size(M3),
+ 98 = maps:size(M3),
+
+ %% with maps
+
+ M4 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ #{ one => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+ #{10:=a0,20:=b0,30:="c0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M4,
+ #{11:=a1,21:=b1,31:="c1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M4,
+ #{12:=a2,22:=b2,32:="c2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M4,
+ #{13:=a3,23:=b3,33:="c3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M4,
+ #{14:=a4,24:=b4,34:="c4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M4,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M4,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M4,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M4,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M4,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M4,
+
+ #{ #{ one => small, map => key } := "small map key 1",
+ #{ second => small, map => key } := "small map key 2",
+ #{ third => small, map => key } := "small map key 3" } = M4,
+
+ #{ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } := "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } := "large map key 2" } = M4,
+
+
+ #{ 15:=V1,25:=b5,35:=V2,"45":="d5",<<"55">>:=V3,{["05"]}:="15",
+ #{ one => small, map => key } := "small map key 1",
+ #{ second => small, map => key } := V4,
+ #{ third => small, map => key } := "small map key 3",
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } := V5 } = M4,
+
+ a5 = V1,
+ "c5" = V2,
+ "e5" = V3,
+ "small map key 2" = V4,
+ "large map key 1" = V5,
+
+ 95 = map_size(M4),
+ 95 = maps:size(M4),
+
+ % call for value
+
+ M5 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ #{ one => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+ #{10:=a0,20:=b0,30:="c0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M5,
+ #{11:=a1,21:=b1,31:="c1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M5,
+ #{12:=a2,22:=b2,32:="c2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M5,
+ #{13:=a3,23:=b3,33:="c3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M5,
+ #{14:=a4,24:=b4,34:="c4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M5,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M5,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M5,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M5,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M5,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M5,
+
+ #{ #{ one => small, map => key } := "small map key 1",
+ #{ second => small, map => key } := "small map key 2",
+ #{ third => small, map => key } := "small map key 3" } = M5,
+
+ #{ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } := "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } := "large map key 2" } = M5,
+
+ 95 = map_size(M5),
+ 95 = maps:size(M5),
+
+ %% remember
+
+ #{10:=a0,20:=b0,30:="c0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M0,
+ #{11:=a1,21:=b1,31:="c1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M0,
+ #{12:=a2,22:=b2,32:="c2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M0,
+ #{13:=a3,23:=b3,33:="c3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M0,
+ #{14:=a4,24:=b4,34:="c4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M0,
+
+ #{10:=na0,20:=nb0,30:="nc0","40":="nd0",<<"50">>:="ne0",{["00"]}:="n10"} = M1,
+ #{11:=na1,21:=nb1,31:="nc1","41":="nd1",<<"51">>:="ne1",{["01"]}:="n11"} = M1,
+ #{12:=na2,22:=nb2,32:="nc2","42":="nd2",<<"52">>:="ne2",{["02"]}:="n12"} = M1,
+ #{13:=na3,23:=nb3,33:="nc3","43":="nd3",<<"53">>:="ne3",{["03"]}:="n13"} = M1,
+ #{14:=na4,24:=nb4,34:="nc4","44":="nd4",<<"54">>:="ne4",{["04"]}:="n14"} = M1,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M1,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M1,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M1,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M1,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M1,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M2,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M2,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M2,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M2,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M2,
+
+ #{10.0:=fa0,20.0:=fb0,30.0:="fc0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M2,
+ #{11.0:=fa1,21.0:=fb1,31.0:="fc1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M2,
+ #{12.0:=fa2,22.0:=fb2,32.0:="fc2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M2,
+ #{13.0:=fa3,23.0:=fb3,33.0:="fc3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M2,
+ #{14.0:=fa4,24.0:=fb4,34.0:="fc4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M2,
+
+ #{15:=a5,25:=b5,35:="c5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M3,
+ #{16:=a6,26:=b6,36:="c6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M3,
+ #{17:=a7,27:=b7,37:="c7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M3,
+ #{18:=a8,28:=b8,38:="c8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M3,
+ #{19:=a9,29:=b9,39:="c9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M3,
+
+ #{10.0:=fa0,20.0:=fb0,30.0:="fc0","40":="d0",<<"50">>:="e0",{["00"]}:="10"} = M3,
+ #{11.0:=fa1,21.0:=fb1,31.0:="fc1","41":="d1",<<"51">>:="e1",{["01"]}:="11"} = M3,
+ #{12.0:=fa2,22.0:=fb2,32.0:="fc2","42":="d2",<<"52">>:="e2",{["02"]}:="12"} = M3,
+ #{13.0:=fa3,23.0:=fb3,33.0:="fc3","43":="d3",<<"53">>:="e3",{["03"]}:="13"} = M3,
+ #{14.0:=fa4,24.0:=fb4,34.0:="fc4","44":="d4",<<"54">>:="e4",{["04"]}:="14"} = M3,
+
+ #{15.0:=fa5,25.0:=fb5,35.0:="fc5","45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M3,
+ #{16.0:=fa6,26.0:=fb6,36.0:="fc6","46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M3,
+ #{17.0:=fa7,27.0:=fb7,37.0:="fc7","47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M3,
+ #{18.0:=fa8,28.0:=fb8,38.0:="fc8","48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M3,
+ #{19.0:=fa9,29.0:=fb9,39.0:="fc9","49":="d9",<<"59">>:="e9",{["09"]}:="19"} = M3,
+
+ #{36893488147419103232:=big1,67108863:=big8,"45":="d5",<<"55">>:="e5",{["05"]}:="15"} = M3,
+ #{147573952589676412928:=big3,8589934592:=big6,"46":="d6",<<"56">>:="e6",{["06"]}:="16"} = M3,
+ #{4294967296:=big5,18446744073709551616:=big4,"47":="d7",<<"57">>:="e7",{["07"]}:="17"} = M3,
+ #{4294967295:=big7,73786976294838206464:=big2,"48":="d8",<<"58">>:="e8",{["08"]}:="18"} = M3,
+
+ ok.
+
+
+t_map_size(Config) when is_list(Config) ->
+ 0 = map_size(#{}),
+ 1 = map_size(#{a=>1}),
+ 1 = map_size(#{a=>"wat"}),
+ 2 = map_size(#{a=>1, b=>2}),
+ 3 = map_size(#{a=>1, b=>2, b=>"3","33"=><<"n">>}),
+
+ true = map_is_size(#{a=>1}, 1),
+ true = map_is_size(#{a=>1, a=>2}, 1),
+ M = #{ "a" => 1, "b" => 2},
+ true = map_is_size(M, 2),
+ false = map_is_size(M, 3),
+ true = map_is_size(M#{ "a" => 2}, 2),
+ false = map_is_size(M#{ "c" => 2}, 2),
+
+ Ks = [build_key(fun(K) -> <<1,K:32,1>> end,I)||I<-lists:seq(1,100)],
+ ok = build_and_check_size(Ks,0,#{}),
+
+ %% try deep collisions
+ %% statistically we get another subtree at 50k -> 100k elements
+ %% Try to be nice and don't use too much memory in the testcase,
+
+ N = 500000,
+ Is = lists:seq(1,N),
+ N = map_size(maps:from_list([{I,I}||I<-Is])),
+ N = map_size(maps:from_list([{<<I:32>>,I}||I<-Is])),
+ N = map_size(maps:from_list([{integer_to_list(I),I}||I<-Is])),
+ N = map_size(maps:from_list([{float(I),I}||I<-Is])),
+
+ %% Error cases.
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},_}} =
+ (catch map_size(T))
+ end),
+ ok.
+
+build_and_check_size([K|Ks],N,M0) ->
+ N = map_size(M0),
+ M1 = M0#{ K => K },
+ build_and_check_size(Ks,N + 1,M1);
+build_and_check_size([],N,M) ->
+ N = map_size(M),
+ ok.
+
+map_is_size(M,N) when map_size(M) =:= N -> true;
+map_is_size(_,_) -> false.
+
+t_is_map(Config) when is_list(Config) ->
+ true = is_map(#{}),
+ true = is_map(#{a=>1}),
+ false = is_map({a,b}),
+ false = is_map(x),
+ if is_map(#{}) -> ok end,
+ if is_map(#{b=>1}) -> ok end,
+ if not is_map([1,2,3]) -> ok end,
+ if not is_map(x) -> ok end,
+ ok.
+
+% test map updates without matching
+t_update_literals_large(Config) when is_list(Config) ->
+ Map = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ #{ one => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+ #{x:="d",q:="4"} = loop_update_literals_x_q(Map, [
+ {"a","1"},{"b","2"},{"c","3"},{"d","4"}
+ ]),
+ ok.
+
+t_update_literals(Config) when is_list(Config) ->
+ Map = #{x=>1,y=>2,z=>3,q=>4},
+ #{x:="d",q:="4"} = loop_update_literals_x_q(Map, [
+ {"a","1"},{"b","2"},{"c","3"},{"d","4"}
+ ]),
+ ok.
+
+
+loop_update_literals_x_q(Map, []) -> Map;
+loop_update_literals_x_q(Map, [{X,Q}|Vs]) ->
+ loop_update_literals_x_q(Map#{q=>Q,x=>X},Vs).
+
+% test map updates with matching
+t_match_and_update_literals(Config) when is_list(Config) ->
+ Map = #{ x=>0,y=>"untouched",z=>"also untouched",q=>1,
+ #{ "one" => small, map => key } => "small map key 1" },
+ #{x:=16,q:=21,y:="untouched",z:="also untouched"} = loop_match_and_update_literals_x_q(Map, [
+ {1,2},{3,4},{5,6},{7,8}
+ ]),
+ M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+ M1 = #{},
+ M2 = M1#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+ M0 = M2,
+
+ #{ 4 := another_number, int := 3 } = M2#{ 4 => another_number },
+ ok.
+
+t_match_and_update_literals_large(Config) when is_list(Config) ->
+ Map = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ x=>0,y=>"untouched",z=>"also untouched",q=>1,
+
+ #{ "one" => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+ #{x:=16,q:=21,y:="untouched",z:="also untouched"} = loop_match_and_update_literals_x_q(Map, [
+ {1,2},{3,4},{5,6},{7,8}
+ ]),
+ M0 = Map#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+ M1 = Map#{},
+ M2 = M1#{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+ M0 = M2,
+
+ #{ 4 := another_number, int := 3 } = M2#{ 4 => another_number },
+ ok.
+
+
+loop_match_and_update_literals_x_q(Map, []) -> Map;
+loop_match_and_update_literals_x_q(#{ q:=Q0, x:=X0,
+ #{ "one" => small, map => key } := "small map key 1" } = Map, [{X,Q}|Vs]) ->
+ loop_match_and_update_literals_x_q(Map#{q=>Q0+Q,x=>X0+X},Vs).
+
+
+t_update_map_expressions(Config) when is_list(Config) ->
+ M = maps:new(),
+ #{ a := 1 } = M#{a => 1},
+
+ #{ b := 2 } = (maps:new())#{ b => 2 },
+
+ #{ a :=42, b:=42, c:=42 } = (maps:from_list([{a,1},{b,2},{c,3}]))#{ a := 42, b := 42, c := 42 },
+ #{ "a" :=1, "b":=42, "c":=42 } = (maps:from_list([{"a",1},{"b",2}]))#{ "b" := 42, "c" => 42 },
+ Ks = lists:seq($a,$z),
+ #{ "aa" := {$a,$a}, "ac":=41, "dc":=42 } =
+ (maps:from_list([{[K1,K2],{K1,K2}}|| K1 <- Ks, K2 <- Ks]))#{ "ac" := 41, "dc" => 42 },
+
+ %% Error cases.
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},_}} =
+ (catch (T)#{a:=42,b=>2})
+ end),
+ ok.
+
+t_update_assoc(Config) when is_list(Config) ->
+ M0 = #{1=>a,2=>b,3.0=>c,4=>d,5=>e},
+
+ M1 = M0#{1=>42,2=>100,4=>[a,b,c]},
+ #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1,
+ #{1:=42,2:=b,4:=d,5:=e,2.0:=100,3.0:=c,4.0:=[a,b,c]} = M0#{1.0=>float,1:=42,2.0=>wrong,2.0=>100,4.0=>[a,b,c]},
+
+ M2 = M0#{3.0=>new},
+ #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2,
+ M2 = M0#{3.0:=wrong,3.0=>new},
+
+ %% Errors cases.
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},_}} =
+ (catch T#{nonexisting=>val})
+ end),
+ ok.
+
+
+t_update_assoc_large(Config) when is_list(Config) ->
+ M0 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ #{ one => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+
+ M1 = M0#{1=>42,2=>100,4=>[a,b,c]},
+ #{1:=42,2:=100,10.0:=fa0,4:=[a,b,c],25:=b5} = M1,
+ #{ 10:=43, 24:=b4, 15:=a5, 35:="c5", 2.0:=100, 13.0:=fa3, 4.0:=[a,b,c]} =
+ M0#{1.0=>float,10:=43,2.0=>wrong,2.0=>100,4.0=>[a,b,c]},
+
+ M2 = M0#{13.0=>new},
+ #{10:=a0,20:=b0,13.0:=new,"40":="d0",<<"50">>:="e0"} = M2,
+ M2 = M0#{13.0:=wrong,13.0=>new},
+
+ ok.
+
+t_update_exact(Config) when is_list(Config) ->
+ M0 = #{1=>a,2=>b,3.0=>c,4=>d,5=>e},
+
+ M1 = M0#{1:=42,2:=100,4:=[a,b,c]},
+ #{1:=42,2:=100,3.0:=c,4:=[a,b,c],5:=e} = M1,
+ M1 = M0#{1:=wrong,1=>42,2=>wrong,2:=100,4:=[a,b,c]},
+
+ M2 = M0#{3.0:=new},
+ #{1:=a,2:=b,3.0:=new,4:=d,5:=e} = M2,
+ M2 = M0#{3.0=>wrong,3.0:=new},
+ true = M2 =/= M0#{3=>right,3.0:=new},
+ #{ 3 := right, 3.0 := new } = M0#{3=>right,3.0:=new},
+
+ M3 = #{ 1 => val},
+ #{1 := update2,1.0 := new_val4} = M3#{
+ 1.0 => new_val1, 1 := update, 1=> update3,
+ 1 := update2, 1.0 := new_val2, 1.0 => new_val3,
+ 1.0 => new_val4 },
+
+ %% Errors cases.
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},_}} =
+ (catch T#{nonexisting=>val})
+ end),
+ Empty = #{},
+ {'EXIT',{{badkey,nonexisting},_}} = (catch Empty#{nonexisting:=val}),
+ {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}),
+ {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}),
+ {'EXIT',{{badkey,42},_}} = (catch M0#{42.0:=v,42:=v2}),
+ {'EXIT',{{badkey,42.0},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}),
+
+ ok.
+
+t_update_exact_large(Config) when is_list(Config) ->
+ M0 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ #{ one => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+
+ M1 = M0#{10:=42,<<"55">>:=100,10.0:=[a,b,c]},
+ #{ 10:=42,<<"55">>:=100,{["05"]}:="15",10.0:=[a,b,c],
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } := "large map key 1" } = M1,
+
+ M1 = M0#{10:=wrong,10=>42,<<"55">>=>wrong,<<"55">>:=100,10.0:=[a,b,c]},
+
+ M2 = M0#{13.0:=new},
+ #{10:=a0,20:=b0,13.0:=new} = M2,
+ M2 = M0#{13.0=>wrong,13.0:=new},
+
+ %% Errors cases.
+ {'EXIT',{{badkey,nonexisting},_}} = (catch M0#{nonexisting:=val}),
+ {'EXIT',{{badkey,1.0},_}} = (catch M0#{1.0:=v,1.0=>v2}),
+ {'EXIT',{{badkey,42},_}} = (catch M0#{42.0:=v,42:=v2}),
+ {'EXIT',{{badkey,42.0},_}} = (catch M0#{42=>v1,42.0:=v2,42:=v3}),
+
+ ok.
+
+t_update_deep(Config) when is_list(Config) ->
+ N = 250000,
+ M0 = maps:from_list([{integer_to_list(I),a}||I<-lists:seq(1,N)]),
+ #{ "1" := a, "10" := a, "100" := a, "1000" := a, "10000" := a } = M0,
+
+ M1 = M0#{ "1" := b, "10" := b, "100" := b, "1000" := b, "10000" := b },
+ #{ "1" := a, "10" := a, "100" := a, "1000" := a, "10000" := a } = M0,
+ #{ "1" := b, "10" := b, "100" := b, "1000" := b, "10000" := b } = M1,
+
+ M2 = M0#{ "1" => c, "10" => c, "100" => c, "1000" => c, "10000" => c },
+ #{ "1" := a, "10" := a, "100" := a, "1000" := a, "10000" := a } = M0,
+ #{ "1" := b, "10" := b, "100" := b, "1000" := b, "10000" := b } = M1,
+ #{ "1" := c, "10" := c, "100" := c, "1000" := c, "10000" := c } = M2,
+
+ M3 = M2#{ "n1" => d, "n10" => d, "n100" => d, "n1000" => d, "n10000" => d },
+ #{ "1" := a, "10" := a, "100" := a, "1000" := a, "10000" := a } = M0,
+ #{ "1" := b, "10" := b, "100" := b, "1000" := b, "10000" := b } = M1,
+ #{ "1" := c, "10" := c, "100" := c, "1000" := c, "10000" := c } = M2,
+ #{ "1" := c, "10" := c, "100" := c, "1000" := c, "10000" := c } = M3,
+ #{ "n1" := d, "n10" := d, "n100" := d, "n1000" := d, "n10000" := d } = M3,
+ ok.
+
+t_guard_bifs(Config) when is_list(Config) ->
+ true = map_guard_head(#{a=>1}),
+ false = map_guard_head([]),
+ true = map_guard_body(#{a=>1}),
+ false = map_guard_body({}),
+ true = map_guard_pattern(#{a=>1, <<"hi">> => "hi" }),
+ false = map_guard_pattern("list"),
+ ok.
+
+map_guard_head(M) when is_map(M) -> true;
+map_guard_head(_) -> false.
+
+map_guard_body(M) -> is_map(M).
+
+map_guard_pattern(#{}) -> true;
+map_guard_pattern(_) -> false.
+
+t_guard_sequence(Config) when is_list(Config) ->
+ {1, "a"} = map_guard_sequence_1(#{seq=>1,val=>"a"}),
+ {2, "b"} = map_guard_sequence_1(#{seq=>2,val=>"b"}),
+ {3, "c"} = map_guard_sequence_1(#{seq=>3,val=>"c"}),
+ {4, "d"} = map_guard_sequence_1(#{seq=>4,val=>"d"}),
+ {5, "e"} = map_guard_sequence_1(#{seq=>5,val=>"e"}),
+
+ {1,M1} = map_guard_sequence_2(M1 = #{a=>3}),
+ {2,M2} = map_guard_sequence_2(M2 = #{a=>4, b=>4}),
+ {3,gg,M3} = map_guard_sequence_2(M3 = #{a=>gg, b=>4}),
+ {4,sc,sc,M4} = map_guard_sequence_2(M4 = #{a=>sc, b=>3, c=>sc2}),
+ {5,kk,kk,M5} = map_guard_sequence_2(M5 = #{a=>kk, b=>other, c=>sc2}),
+
+ %% error case
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(#{seq=>6,val=>"e"})),
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(#{b=>5})),
+ ok.
+
+t_guard_sequence_large(Config) when is_list(Config) ->
+ M0 = #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00",03]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01",03]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02",03]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03",03]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04",03]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05",03]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06",03]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07",03]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08",03]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09",03]}=>"19",
+
+ 10.0=>fa0,20.0=>fb0,30.0=>"fc0",
+ 11.0=>fa1,21.0=>fb1,31.0=>"fc1",
+ 12.0=>fa2,22.0=>fb2,32.0=>"fc2",
+ 13.0=>fa3,23.0=>fb3,33.0=>"fc3",
+ 14.0=>fa4,24.0=>fb4,34.0=>"fc4",
+
+ 15.0=>fa5,25.0=>fb5,35.0=>"fc5",
+ 16.0=>fa6,26.0=>fb6,36.0=>"fc6",
+ 17.0=>fa7,27.0=>fb7,37.0=>"fc7",
+ 18.0=>fa8,28.0=>fb8,38.0=>"fc8",
+ 19.0=>fa9,29.0=>fb9,39.0=>"fc9",
+
+ #{ one => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+ {1, "a"} = map_guard_sequence_1(M0#{seq=>1,val=>"a"}),
+ {2, "b"} = map_guard_sequence_1(M0#{seq=>2,val=>"b"}),
+ {3, "c"} = map_guard_sequence_1(M0#{seq=>3,val=>"c"}),
+ {4, "d"} = map_guard_sequence_1(M0#{seq=>4,val=>"d"}),
+ {5, "e"} = map_guard_sequence_1(M0#{seq=>5,val=>"e"}),
+
+ {1,M1} = map_guard_sequence_2(M1 = M0#{a=>3}),
+ {2,M2} = map_guard_sequence_2(M2 = M0#{a=>4, b=>4}),
+ {3,gg,M3} = map_guard_sequence_2(M3 = M0#{a=>gg, b=>4}),
+ {4,sc,sc,M4} = map_guard_sequence_2(M4 = M0#{a=>sc, b=>3, c=>sc2}),
+ {5,kk,kk,M5} = map_guard_sequence_2(M5 = M0#{a=>kk, b=>other, c=>sc2}),
+
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_1(M0#{seq=>6,val=>"e"})),
+ {'EXIT',{function_clause,_}} = (catch map_guard_sequence_2(M0#{b=>5})),
+ ok.
+
+
+map_guard_sequence_1(#{seq:=1=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=2=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=3=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=4=Seq, val:=Val}) -> {Seq,Val};
+map_guard_sequence_1(#{seq:=5=Seq, val:=Val}) -> {Seq,Val}.
+
+map_guard_sequence_2(#{ a:=3 }=M) -> {1, M};
+map_guard_sequence_2(#{ a:=4 }=M) -> {2, M};
+map_guard_sequence_2(#{ a:=X, a:=X, b:=4 }=M) -> {3,X,M};
+map_guard_sequence_2(#{ a:=X, a:=Y, b:=3 }=M) when X =:= Y -> {4,X,Y,M};
+map_guard_sequence_2(#{ a:=X, a:=Y }=M) when X =:= Y -> {5,X,Y,M}.
+
+
+t_guard_update(Config) when is_list(Config) ->
+ error = map_guard_update(#{},#{}),
+ first = map_guard_update(#{}, #{x=>first}),
+ second = map_guard_update(#{y=>old}, #{x=>second,y=>old}),
+ ok.
+
+t_guard_update_large(Config) when is_list(Config) ->
+ M0 = #{ 70=>a0,80=>b0,90=>"c0","40"=>"d0",<<"50">>=>"e0",{["00",03]}=>"10",
+ 71=>a1,81=>b1,91=>"c1","41"=>"d1",<<"51">>=>"e1",{["01",03]}=>"11",
+ 72=>a2,82=>b2,92=>"c2","42"=>"d2",<<"52">>=>"e2",{["02",03]}=>"12",
+ 73=>a3,83=>b3,93=>"c3","43"=>"d3",<<"53">>=>"e3",{["03",03]}=>"13",
+ 74=>a4,84=>b4,94=>"c4","44"=>"d4",<<"54">>=>"e4",{["04",03]}=>"14",
+
+ 75=>a5,85=>b5,95=>"c5","45"=>"d5",<<"55">>=>"e5",{["05",03]}=>"15",
+ 76=>a6,86=>b6,96=>"c6","46"=>"d6",<<"56">>=>"e6",{["06",03]}=>"16",
+ 77=>a7,87=>b7,97=>"c7","47"=>"d7",<<"57">>=>"e7",{["07",03]}=>"17",
+ 78=>a8,88=>b8,98=>"c8","48"=>"d8",<<"58">>=>"e8",{["08",03]}=>"18",
+ 79=>a9,89=>b9,99=>"c9","49"=>"d9",<<"59">>=>"e9",{["09",03]}=>"19",
+
+ 70.0=>fa0,80.0=>fb0,90.0=>"fc0",
+ 71.0=>fa1,81.0=>fb1,91.0=>"fc1",
+ 72.0=>fa2,82.0=>fb2,92.0=>"fc2",
+ 73.0=>fa3,83.0=>fb3,93.0=>"fc3",
+ 74.0=>fa4,84.0=>fb4,94.0=>"fc4",
+
+ 75.0=>fa5,85.0=>fb5,95.0=>"fc5",
+ 76.0=>fa6,86.0=>fb6,96.0=>"fc6",
+ 77.0=>fa7,87.0=>fb7,97.0=>"fc7",
+ 78.0=>fa8,88.0=>fb8,98.0=>"fc8",
+ 79.0=>fa9,89.0=>fb9,99.0=>"fc9",
+
+ #{ one => small, map => key } => "small map key 1",
+ #{ second => small, map => key } => "small map key 2",
+ #{ third => small, map => key } => "small map key 3",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ 16=>a6,26=>b6,36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 1",
+
+ #{ 10=>a0,20=>b0,30=>"c0","40"=>"d0",<<"50">>=>"e0",{["00"]}=>"10",
+ 11=>a1,21=>b1,31=>"c1","41"=>"d1",<<"51">>=>"e1",{["01"]}=>"11",
+ 12=>a2,22=>b2,32=>"c2","42"=>"d2",<<"52">>=>"e2",{["02"]}=>"12",
+ 13=>a3,23=>b3,33=>"c3","43"=>"d3",<<"53">>=>"e3",{["03"]}=>"13",
+ 14=>a4,24=>b4,34=>"c4","44"=>"d4",<<"54">>=>"e4",{["04"]}=>"14",
+
+ 15=>a5,25=>b5,35=>"c5","45"=>"d5",<<"55">>=>"e5",{["05"]}=>"15",
+ k16=>a6,k26=>b6,k36=>"c6","46"=>"d6",<<"56">>=>"e6",{["06"]}=>"16",
+ 17=>a7,27=>b7,37=>"c7","47"=>"d7",<<"57">>=>"e7",{["07"]}=>"17",
+ 18=>a8,28=>b8,38=>"c8","48"=>"d8",<<"58">>=>"e8",{["08"]}=>"18",
+ 19=>a9,29=>b9,39=>"c9","49"=>"d9",<<"59">>=>"e9",{["09"]}=>"19" } => "large map key 2" },
+
+
+ error = map_guard_update(M0#{},M0#{}),
+ first = map_guard_update(M0#{},M0#{x=>first}),
+ second = map_guard_update(M0#{y=>old}, M0#{x=>second,y=>old}),
+ ok.
+
+
+map_guard_update(M1, M2) when M1#{x=>first} =:= M2 -> first;
+map_guard_update(M1, M2) when M1#{x=>second} =:= M2 -> second;
+map_guard_update(_, _) -> error.
+
+t_guard_receive(Config) when is_list(Config) ->
+ M0 = #{ id => 0 },
+ Pid = spawn_link(fun() -> guard_receive_loop() end),
+ Big = 36893488147419103229,
+ B1 = <<"some text">>,
+ B2 = <<"was appended">>,
+ B3 = <<B1/binary, B2/binary>>,
+
+ #{id:=1, res:=Big} = M1 = call(Pid, M0#{op=>sub,in=>{1 bsl 65, 3}}),
+ #{id:=2, res:=26} = M2 = call(Pid, M1#{op=>idiv,in=>{53,2}}),
+ #{id:=3, res:=832} = M3 = call(Pid, M2#{op=>imul,in=>{26,32}}),
+ #{id:=4, res:=4} = M4 = call(Pid, M3#{op=>add,in=>{1,3}}),
+ #{id:=5, res:=Big} = M5 = call(Pid, M4#{op=>sub,in=>{1 bsl 65, 3}}),
+ #{id:=6, res:=B3} = M6 = call(Pid, M5#{op=>"append",in=>{B1,B2}}),
+ #{id:=7, res:=4} = _ = call(Pid, M6#{op=>add,in=>{1,3}}),
+
+
+ %% update old maps and check id update
+ #{id:=2, res:=B3} = call(Pid, M1#{op=>"append",in=>{B1,B2}}),
+ #{id:=5, res:=99} = call(Pid, M4#{op=>add,in=>{33, 66}}),
+
+ %% cleanup
+ done = call(Pid, done),
+ ok.
+
+-define(t_guard_receive_large_procs, 1500).
+
+t_guard_receive_large(Config) when is_list(Config) ->
+ M = lists:foldl(fun(_,#{procs := Ps } = M) ->
+ M#{ procs := Ps#{ spawn_link(fun() -> grecv_loop() end) => 0 }}
+ end, #{procs => #{}, done => 0}, lists:seq(1,?t_guard_receive_large_procs)),
+ lists:foreach(fun(Pid) ->
+ Pid ! {self(), hello}
+ end, maps:keys(maps:get(procs,M))),
+ ok = guard_receive_large_loop(M),
+ ok.
+
+guard_receive_large_loop(#{done := ?t_guard_receive_large_procs}) ->
+ ok;
+guard_receive_large_loop(M) ->
+ receive
+ #{pid := Pid, msg := hello} ->
+ case M of
+ #{done := Count, procs := #{Pid := 150}} ->
+ Pid ! {self(), done},
+ guard_receive_large_loop(M#{done := Count + 1});
+ #{procs := #{Pid := Count} = Ps} ->
+ Pid ! {self(), hello},
+ guard_receive_large_loop(M#{procs := Ps#{Pid := Count + 1}})
+ end
+ end.
+
+grecv_loop() ->
+ receive
+ {_, done} ->
+ ok;
+ {Pid, hello} ->
+ Pid ! #{pid=>self(), msg=>hello},
+ grecv_loop()
+ end.
+
+call(Pid, M) ->
+ Pid ! {self(), M}, receive {Pid, Res} -> Res end.
+
+guard_receive_loop() ->
+ receive
+ {Pid, #{ id:=Id, op:="append", in:={X,Y}}=M} when is_binary(X), is_binary(Y) ->
+ Pid ! {self(), M#{ id=>Id+1, res=><<X/binary,Y/binary>>}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=add, in:={X,Y}}} ->
+ Pid ! {self(), #{ id=>Id+1, res=>X+Y}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=sub, in:={X,Y}}=M} ->
+ Pid ! {self(), M#{ id=>Id+1, res=>X-Y}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=idiv, in:={X,Y}}=M} ->
+ Pid ! {self(), M#{ id=>Id+1, res=>X div Y}},
+ guard_receive_loop();
+ {Pid, #{ id:=Id, op:=imul, in:={X,Y}}=M} ->
+ Pid ! {self(), M#{ id=>Id+1, res=>X * Y}},
+ guard_receive_loop();
+ {Pid, done} ->
+ Pid ! {self(), done};
+ {Pid, Other} ->
+ Pid ! {error, Other},
+ guard_receive_loop()
+ end.
+
+
+t_list_comprehension(Config) when is_list(Config) ->
+ [#{k:=1},#{k:=2},#{k:=3}] = [#{k=>I} || I <- [1,2,3]],
+
+ Ks = lists:seq($a,$z),
+ Ms = [#{[K1,K2]=>{K1,K2}} || K1 <- Ks, K2 <- Ks],
+ [#{"aa" := {$a,$a}},#{"ab":={$a,$b}}|_] = Ms,
+ [#{"zz" := {$z,$z}},#{"zy":={$z,$y}}|_] = lists:reverse(Ms),
+ ok.
+
+t_guard_fun(Config) when is_list(Config) ->
+ F1 = fun
+ (#{s:=v,v:=V}) -> {v,V};
+ (#{s:=t,v:={V,V}}) -> {t,V};
+ (#{s:=l,v:=[V,V]}) -> {l,V}
+ end,
+
+ F2 = fun
+ (#{s:=T,v:={V,V}}) -> {T,V};
+ (#{s:=T,v:=[V,V]}) -> {T,V};
+ (#{s:=T,v:=V}) -> {T,V}
+ end,
+ V = <<"hi">>,
+
+ {v,V} = F1(#{s=>v,v=>V}),
+ {t,V} = F1(#{s=>t,v=>{V,V}}),
+ {l,V} = F1(#{s=>l,v=>[V,V]}),
+
+ {v,V} = F2(#{s=>v,v=>V}),
+ {t,V} = F2(#{s=>t,v=>{V,V}}),
+ {l,V} = F2(#{s=>l,v=>[V,V]}),
+
+ %% error case
+ {'EXIT', {function_clause,[{?MODULE,_,[#{s:=none,v:=none}],_}|_]}} = (catch F1(#{s=>none,v=>none})),
+ ok.
+
+
+t_map_sort_literals(Config) when is_list(Config) ->
+ % test relation
+
+ %% size order
+ true = #{ a => 1, b => 2} < #{ a => 1, b => 1, c => 1},
+ true = #{ b => 1, a => 1} < #{ c => 1, a => 1, b => 1},
+ false = #{ c => 1, b => 1, a => 1} < #{ c => 1, a => 1},
+
+ %% key order
+ true = #{ a => 1 } < #{ b => 1},
+ false = #{ b => 1 } < #{ a => 1},
+ true = #{ a => 1, b => 1, c => 1 } < #{ b => 1, c => 1, d => 1},
+ true = #{ b => 1, c => 1, d => 1 } > #{ a => 1, b => 1, c => 1},
+ true = #{ c => 1, b => 1, a => 1 } < #{ b => 1, c => 1, d => 1},
+ true = #{ "a" => 1 } < #{ <<"a">> => 1},
+ false = #{ <<"a">> => 1 } < #{ "a" => 1},
+ true = #{ 1 => 1 } < #{ 1.0 => 1},
+ false = #{ 1.0 => 1 } < #{ 1 => 1},
+
+ %% value order
+ true = #{ a => 1 } < #{ a => 2},
+ false = #{ a => 2 } < #{ a => 1},
+ false = #{ a => 2, b => 1 } < #{ a => 1, b => 3},
+ true = #{ a => 1, b => 1 } < #{ a => 1, b => 3},
+ false = #{ a => 1 } < #{ a => 1.0},
+ false = #{ a => 1.0 } < #{ a => 1},
+
+ true = #{ "a" => "hi", b => 134 } == #{ b => 134,"a" => "hi"},
+
+ %% large maps
+
+ M = maps:from_list([{I,I}||I <- lists:seq(1,500)]),
+
+ %% size order
+ true = M#{ a => 1, b => 2} < M#{ a => 1, b => 1, c => 1},
+ true = M#{ b => 1, a => 1} < M#{ c => 1, a => 1, b => 1},
+ false = M#{ c => 1, b => 1, a => 1} < M#{ c => 1, a => 1},
+
+ %% key order
+ true = M#{ a => 1 } < M#{ b => 1},
+ false = M#{ b => 1 } < M#{ a => 1},
+ true = M#{ a => 1, b => 1, c => 1 } < M#{ b => 1, c => 1, d => 1},
+ true = M#{ b => 1, c => 1, d => 1 } > M#{ a => 1, b => 1, c => 1},
+ true = M#{ c => 1, b => 1, a => 1 } < M#{ b => 1, c => 1, d => 1},
+ true = M#{ "a" => 1 } < M#{ <<"a">> => 1},
+ false = M#{ <<"a">> => 1 } < #{ "a" => 1},
+ true = M#{ 1 => 1 } < maps:remove(1,M#{ 1.0 => 1}),
+ false = M#{ 1.0 => 1 } < M#{ 1 => 1},
+
+ %% value order
+ true = M#{ a => 1 } < M#{ a => 2},
+ false = M#{ a => 2 } < M#{ a => 1},
+ false = M#{ a => 2, b => 1 } < M#{ a => 1, b => 3},
+ true = M#{ a => 1, b => 1 } < M#{ a => 1, b => 3},
+ false = M#{ a => 1 } < M#{ a => 1.0},
+ false = M#{ a => 1.0 } < M#{ a => 1},
+
+ true = M#{ "a" => "hi", b => 134 } == M#{ b => 134,"a" => "hi"},
+
+ %% lists:sort
+
+ SortVs = [#{"a"=>1},#{a=>2},#{1=>3},#{<<"a">>=>4}],
+ [#{1:=ok},#{a:=ok},#{"a":=ok},#{<<"a">>:=ok}] = lists:sort([#{"a"=>ok},#{a=>ok},#{1=>ok},#{<<"a">>=>ok}]),
+ [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(SortVs),
+ [#{1:=3},#{a:=2},#{"a":=1},#{<<"a">>:=4}] = lists:sort(lists:reverse(SortVs)),
+ ok.
+
+t_map_equal(Config) when is_list(Config) ->
+ true = #{} =:= #{},
+ false = #{} =:= #{a=>1},
+ false = #{a=>1} =:= #{},
+ true = #{ "a" => "hi", b => 134 } =:= #{ b => 134,"a" => "hi"},
+
+ false = #{ a => 1 } =:= #{ a => 2},
+ false = #{ a => 2 } =:= #{ a => 1},
+ false = #{ a => 2, b => 1 } =:= #{ a => 1, b => 3},
+ false = #{ a => 1, b => 1 } =:= #{ a => 1, b => 3},
+
+ true = #{ a => 1 } =:= #{ a => 1},
+ true = #{ "a" => 2 } =:= #{ "a" => 2},
+ true = #{ "a" => 2, b => 3 } =:= #{ "a" => 2, b => 3},
+ true = #{ a => 1, b => 3, c => <<"wat">> } =:= #{ a => 1, b => 3, c=><<"wat">>},
+ ok.
+
+
+t_map_compare(Config) when is_list(Config) ->
+ Seed = {erlang:monotonic_time(),
+ erlang:time_offset(),
+ erlang:unique_integer()},
+ io:format("seed = ~p\n", [Seed]),
+ random:seed(Seed),
+ repeat(100, fun(_) -> float_int_compare() end, []),
+ repeat(100, fun(_) -> recursive_compare() end, []),
+ ok.
+
+float_int_compare() ->
+ Terms = numeric_keys(3),
+ %%io:format("Keys to use: ~p\n", [Terms]),
+ Pairs = lists:map(fun(K) -> list_to_tuple([{K,V} || V <- Terms]) end, Terms),
+ lists:foreach(fun(Size) ->
+ MapGen = fun() -> map_gen(list_to_tuple(Pairs), Size) end,
+ repeat(100, fun do_compare/1, [MapGen, MapGen])
+ end,
+ lists:seq(1,length(Terms))),
+ ok.
+
+numeric_keys(N) ->
+ lists:foldl(fun(_,Acc) ->
+ Int = random:uniform(N*4) - N*2,
+ Float = float(Int),
+ [Int, Float, Float * 0.99, Float * 1.01 | Acc]
+ end,
+ [],
+ lists:seq(1,N)).
+
+
+repeat(0, _, _) ->
+ ok;
+repeat(N, Fun, Arg) ->
+ Fun(Arg),
+ repeat(N-1, Fun, Arg).
+
+copy_term(T) ->
+ Papa = self(),
+ P = spawn_link(fun() -> receive Msg -> Papa ! Msg end end),
+ P ! T,
+ receive R -> R end.
+
+do_compare([Gen1, Gen2]) ->
+ M1 = Gen1(),
+ M2 = Gen2(),
+ %%io:format("Maps to compare: ~p AND ~p\n", [M1, M2]),
+ C = (M1 < M2),
+ Erlang = maps_lessthan(M1, M2),
+ C = Erlang,
+ ?CHECK(M1==M1, M1),
+
+ %% Change one key from int to float (or vice versa) and check compare
+ ML1 = maps:to_list(M1),
+ {K1,V1} = lists:nth(random:uniform(length(ML1)), ML1),
+ case K1 of
+ I when is_integer(I) ->
+ case maps:find(float(I),M1) of
+ error ->
+ M1f = maps:remove(I, maps:put(float(I), V1, M1)),
+ ?CHECK(M1f > M1, [M1f, M1]);
+ _ -> ok
+ end;
+
+ F when is_float(F), round(F) == F ->
+ case maps:find(round(F),M1) of
+ error ->
+ M1i = maps:remove(F, maps:put(round(F), V1, M1)),
+ ?CHECK(M1i < M1, [M1i, M1]);
+ _ -> ok
+ end;
+
+ _ -> ok % skip floats with decimals
+ end,
+
+ ?CHECK(M2 == M2, [M2]).
+
+
+maps_lessthan(M1, M2) ->
+ case {maps:size(M1),maps:size(M2)} of
+ {_S,_S} ->
+ {K1,V1} = lists:unzip(term_sort(maps:to_list(M1))),
+ {K2,V2} = lists:unzip(term_sort(maps:to_list(M2))),
+
+ case erts_internal:cmp_term(K1,K2) of
+ -1 -> true;
+ 0 -> (V1 < V2);
+ 1 -> false
+ end;
+
+ {S1, S2} ->
+ S1 < S2
+ end.
+
+term_sort(L) ->
+ lists:sort(fun(A,B) -> erts_internal:cmp_term(A,B) =< 0 end,
+ L).
+
+
+cmp(T1, T2, Exact) when is_tuple(T1) and is_tuple(T2) ->
+ case {size(T1),size(T2)} of
+ {_S,_S} -> cmp(tuple_to_list(T1), tuple_to_list(T2), Exact);
+ {S1,S2} when S1 < S2 -> -1;
+ {S1,S2} when S1 > S2 -> 1
+ end;
+
+cmp([H1|T1], [H2|T2], Exact) ->
+ case cmp(H1,H2, Exact) of
+ 0 -> cmp(T1,T2, Exact);
+ C -> C
+ end;
+
+cmp(M1, M2, Exact) when is_map(M1) andalso is_map(M2) ->
+ cmp_maps(M1,M2,Exact);
+cmp(M1, M2, Exact) ->
+ cmp_others(M1, M2, Exact).
+
+cmp_maps(M1, M2, Exact) ->
+ case {maps:size(M1),maps:size(M2)} of
+ {_S,_S} ->
+ {K1,V1} = lists:unzip(term_sort(maps:to_list(M1))),
+ {K2,V2} = lists:unzip(term_sort(maps:to_list(M2))),
+
+ case cmp(K1, K2, true) of
+ 0 -> cmp(V1, V2, Exact);
+ C -> C
+ end;
+
+ {S1,S2} when S1 < S2 -> -1;
+ {S1,S2} when S1 > S2 -> 1
+ end.
+
+cmp_others(I, F, true) when is_integer(I), is_float(F) ->
+ -1;
+cmp_others(F, I, true) when is_float(F), is_integer(I) ->
+ 1;
+cmp_others(T1, T2, _) ->
+ case {T1<T2, T1==T2} of
+ {true,false} -> -1;
+ {false,true} -> 0;
+ {false,false} -> 1
+ end.
+
+map_gen(Pairs, Size) ->
+ {_,L} = lists:foldl(fun(_, {Keys, Acc}) ->
+ KI = random:uniform(size(Keys)),
+ K = element(KI,Keys),
+ KV = element(random:uniform(size(K)), K),
+ {erlang:delete_element(KI,Keys), [KV | Acc]}
+ end,
+ {Pairs, []},
+ lists:seq(1,Size)),
+
+ maps:from_list(L).
+
+
+recursive_compare() ->
+ Leafs = {atom, 17, 16.9, 17.1, [], self(), spawn(fun() -> ok end), make_ref(), make_ref()},
+ {A, B} = term_gen_recursive(Leafs, 0, 0),
+ %%io:format("Recursive term A = ~p\n", [A]),
+ %%io:format("Recursive term B = ~p\n", [B]),
+
+ ?CHECK({true,false} =:= case do_cmp(A, B, false) of
+ -1 -> {A<B, A>=B};
+ 0 -> {A==B, A/=B};
+ 1 -> {A>B, A=<B}
+ end,
+ {A,B}),
+ A2 = copy_term(A),
+ ?CHECK(A == A2, {A,A2}),
+ ?CHECK(0 =:= cmp(A, A2, false), {A,A2}),
+
+ B2 = copy_term(B),
+ ?CHECK(B == B2, {B,B2}),
+ ?CHECK(0 =:= cmp(B, B2, false), {B,B2}),
+ ok.
+
+do_cmp(A, B, Exact) ->
+ C = cmp(A, B, Exact),
+ C.
+
+%% Generate two terms {A,B} that may only differ
+%% at float vs integer types.
+term_gen_recursive(Leafs, Flags, Depth) ->
+ MaxDepth = 10,
+ Rnd = case {Flags, Depth} of
+ {_, MaxDepth} -> % Only leafs
+ random:uniform(size(Leafs)) + 3;
+ {0, 0} -> % Only containers
+ random:uniform(3);
+ {0,_} -> % Anything
+ random:uniform(size(Leafs)+3)
+ end,
+ case Rnd of
+ 1 -> % Make map
+ Size = random:uniform(size(Leafs)),
+ lists:foldl(fun(_, {Acc1,Acc2}) ->
+ {K1,K2} = term_gen_recursive(Leafs, Flags,
+ Depth+1),
+ {V1,V2} = term_gen_recursive(Leafs, Flags, Depth+1),
+ {maps:put(K1,V1, Acc1), maps:put(K2,V2, Acc2)}
+ end,
+ {maps:new(), maps:new()},
+ lists:seq(1,Size));
+ 2 -> % Make cons
+ {Car1,Car2} = term_gen_recursive(Leafs, Flags, Depth+1),
+ {Cdr1,Cdr2} = term_gen_recursive(Leafs, Flags, Depth+1),
+ {[Car1 | Cdr1], [Car2 | Cdr2]};
+ 3 -> % Make tuple
+ Size = random:uniform(size(Leafs)),
+ L = lists:map(fun(_) -> term_gen_recursive(Leafs, Flags, Depth+1) end,
+ lists:seq(1,Size)),
+ {L1, L2} = lists:unzip(L),
+ {list_to_tuple(L1), list_to_tuple(L2)};
+
+ N -> % Make leaf
+ case element(N-3, Leafs) of
+ I when is_integer(I) ->
+ case random:uniform(4) of
+ 1 -> {I, float(I)};
+ 2 -> {float(I), I};
+ _ -> {I,I}
+ end;
+ T -> {T,T}
+ end
+ end.
+
+%% BIFs
+t_bif_map_get(Config) when is_list(Config) ->
+ %% small map
+ 1 = maps:get(a, #{ a=> 1}),
+ 2 = maps:get(b, #{ a=> 1, b => 2}),
+ "hi" = maps:get("hello", #{ a=>1, "hello" => "hi"}),
+ "tuple hi" = maps:get({1,1.0}, #{ a=>a, {1,1.0} => "tuple hi"}),
+
+ M0 = #{ k1=>"v1", <<"k2">> => <<"v3">> },
+ "v4" = maps:get(<<"k2">>, M0#{<<"k2">> => "v4"}),
+
+ %% large map
+ M1 = maps:from_list([{I,I}||I<-lists:seq(1,100)] ++
+ [{a,1},{b,2},{"hello","hi"},{{1,1.0},"tuple hi"},
+ {k1,"v1"},{<<"k2">>,"v3"}]),
+ 1 = maps:get(a, M1),
+ 2 = maps:get(b, M1),
+ "hi" = maps:get("hello", M1),
+ "tuple hi" = maps:get({1,1.0}, M1),
+ "v3" = maps:get(<<"k2">>, M1),
+
+ %% error cases
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,get,_,_}|_]}} =
+ (catch maps:get(a, T))
+ end),
+
+ {'EXIT',{{badkey,{1,1}},[{maps,get,_,_}|_]}} =
+ (catch maps:get({1,1}, #{{1,1.0} => "tuple"})),
+ {'EXIT',{{badkey,a},[{maps,get,_,_}|_]}} = (catch maps:get(a, #{})),
+ {'EXIT',{{badkey,a},[{maps,get,_,_}|_]}} =
+ (catch maps:get(a, #{b=>1, c=>2})),
+ ok.
+
+t_bif_map_find(Config) when is_list(Config) ->
+ %% small map
+ {ok, 1} = maps:find(a, #{ a=> 1}),
+ {ok, 2} = maps:find(b, #{ a=> 1, b => 2}),
+ {ok, "int"} = maps:find(1, #{ 1 => "int"}),
+ {ok, "float"} = maps:find(1.0, #{ 1.0=> "float"}),
+
+ {ok, "hi"} = maps:find("hello", #{ a=>1, "hello" => "hi"}),
+ {ok, "tuple hi"} = maps:find({1,1.0}, #{ a=>a, {1,1.0} => "tuple hi"}),
+
+ M0 = #{ k1=>"v1", <<"k2">> => <<"v3">> },
+ {ok, "v4"} = maps:find(<<"k2">>, M0#{ <<"k2">> => "v4" }),
+
+ %% large map
+ M1 = maps:from_list([{I,I}||I<-lists:seq(1,100)] ++
+ [{a,1},{b,2},{"hello","hi"},{{1,1.0},"tuple hi"},
+ {k1,"v1"},{<<"k2">>,"v3"}]),
+ {ok, 1} = maps:find(a, M1),
+ {ok, 2} = maps:find(b, M1),
+ {ok, "hi"} = maps:find("hello", M1),
+ {ok, "tuple hi"} = maps:find({1,1.0}, M1),
+ {ok, "v3"} = maps:find(<<"k2">>, M1),
+
+ %% error case
+ error = maps:find(a,#{}),
+ error = maps:find(a,#{b=>1, c=>2}),
+ error = maps:find(1.0, #{ 1 => "int"}),
+ error = maps:find(1, #{ 1.0 => "float"}),
+ error = maps:find({1.0,1}, #{ a=>a, {1,1.0} => "tuple hi"}), % reverse types in tuple key
+
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,find,_,_}|_]}} =
+ (catch maps:find(a, T))
+ end),
+ ok.
+
+
+t_bif_map_is_key(Config) when is_list(Config) ->
+ M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number},
+
+ true = maps:is_key("hi", M1),
+ true = maps:is_key(int, M1),
+ true = maps:is_key(<<"key">>, M1),
+ true = maps:is_key(4, M1),
+
+ false = maps:is_key(5, M1),
+ false = maps:is_key(<<"key2">>, M1),
+ false = maps:is_key("h", M1),
+ false = maps:is_key("hello", M1),
+ false = maps:is_key(atom, M1),
+ false = maps:is_key(any, #{}),
+
+ false = maps:is_key("hi", maps:remove("hi", M1)),
+ true = maps:is_key("hi", M1),
+ true = maps:is_key(1, maps:put(1, "number", M1)),
+ false = maps:is_key(1.0, maps:put(1, "number", M1)),
+
+ %% error case
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,is_key,_,_}|_]}} =
+ (catch maps:is_key(a, T))
+ end),
+ ok.
+
+t_bif_map_keys(Config) when is_list(Config) ->
+ [] = maps:keys(#{}),
+
+ [1,2,3,4,5] = lists:sort(maps:keys(#{ 1 => a, 2 => b, 3 => c, 4 => d, 5 => e})),
+ [1,2,3,4,5] = lists:sort(maps:keys(#{ 4 => d, 5 => e, 1 => a, 2 => b, 3 => c})),
+
+ % values in key order: [4,int,"hi",<<"key">>]
+ M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number},
+ [4,int,"hi",<<"key">>] = lists:sort(maps:keys(M1)),
+
+ %% error case
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,keys,_,_}|_]}} =
+ (catch maps:keys(T))
+ end),
+ ok.
+
+t_bif_map_new(Config) when is_list(Config) ->
+ #{} = maps:new(),
+ 0 = erlang:map_size(maps:new()),
+ ok.
+
+t_bif_map_merge(Config) when is_list(Config) ->
+ 0 = erlang:map_size(maps:merge(#{},#{})),
+
+ M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+
+ #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
+ 4 := number, 18446744073709551629 := wat} = maps:merge(#{}, M0),
+
+ #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
+ 4 := number, 18446744073709551629 := wat} = maps:merge(M0, #{}),
+
+ M1 = #{ "hi" => "hello again", float => 3.3, {1,2} => "tuple", 4 => integer },
+
+ #{4 := number, 18446744073709551629 := wat, float := 3.3, int := 3,
+ {1,2} := "tuple", "hi" := "hello", <<"key">> := <<"value">>} = maps:merge(M1,M0),
+
+ #{4 := integer, 18446744073709551629 := wat, float := 3.3, int := 3,
+ {1,2} := "tuple", "hi" := "hello again", <<"key">> := <<"value">>} = maps:merge(M0,M1),
+
+ %% try deep collisions
+ N = 150000,
+ Is = lists:seq(1,N),
+ M2 = maps:from_list([{I,I}||I<-Is]),
+ 150000 = maps:size(M2),
+ M3 = maps:from_list([{<<I:32>>,I}||I<-Is]),
+ 150000 = maps:size(M3),
+ M4 = maps:merge(M2,M3),
+ 300000 = maps:size(M4),
+ M5 = maps:from_list([{integer_to_list(I),I}||I<-Is]),
+ 150000 = maps:size(M5),
+ M6 = maps:merge(M4,M5),
+ 450000 = maps:size(M6),
+ M7 = maps:from_list([{float(I),I}||I<-Is]),
+ 150000 = maps:size(M7),
+ M8 = maps:merge(M7,M6),
+ 600000 = maps:size(M8),
+
+ #{ 1 := 1, "1" := 1, <<1:32>> := 1 } = M8,
+ #{ 10 := 10, "10" := 10, <<10:32>> := 10 } = M8,
+ #{ 100 := 100, "100" := 100, <<100:32>> := 100 } = M8,
+ #{ 1000 := 1000, "1000" := 1000, <<1000:32>> := 1000 } = M8,
+ #{ 10000 := 10000, "10000" := 10000, <<10000:32>> := 10000 } = M8,
+ #{ 100000 := 100000, "100000" := 100000, <<100000:32>> := 100000 } = M8,
+
+ %% overlapping
+ M8 = maps:merge(M2,M8),
+ M8 = maps:merge(M3,M8),
+ M8 = maps:merge(M4,M8),
+ M8 = maps:merge(M5,M8),
+ M8 = maps:merge(M6,M8),
+ M8 = maps:merge(M7,M8),
+ M8 = maps:merge(M8,M8),
+
+ %% maps:merge/2 and mixed
+
+ Ks1 = [764492191,2361333849], %% deep collision
+ Ks2 = lists:seq(1,33),
+ M9 = maps:from_list([{K,K}||K <- Ks1]),
+ M10 = maps:from_list([{K,K}||K <- Ks2]),
+ M11 = maps:merge(M9,M10),
+ ok = check_keys_exist(Ks1 ++ Ks2, M11),
+
+ %% error case
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
+ (catch maps:merge(#{}, T)),
+ {'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
+ (catch maps:merge(T, #{})),
+ {'EXIT',{{badmap,T},[{maps,merge,_,_}|_]}} =
+ (catch maps:merge(T, T))
+ end),
+ ok.
+
+
+t_bif_map_put(Config) when is_list(Config) ->
+ M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+
+ M1 = #{ "hi" := "hello"} = maps:put("hi", "hello", #{}),
+
+ true = is_members(["hi"],maps:keys(M1)),
+ true = is_members(["hello"],maps:values(M1)),
+
+ M2 = #{ int := 3 } = maps:put(int, 3, M1),
+
+ true = is_members([int,"hi"],maps:keys(M2)),
+ true = is_members([3,"hello"],maps:values(M2)),
+
+ M3 = #{ <<"key">> := <<"value">> } = maps:put(<<"key">>, <<"value">>, M2),
+
+ true = is_members([int,"hi",<<"key">>],maps:keys(M3)),
+ true = is_members([3,"hello",<<"value">>],maps:values(M3)),
+
+ M4 = #{ 18446744073709551629 := wat } = maps:put(18446744073709551629, wat, M3),
+
+ true = is_members([18446744073709551629,int,"hi",<<"key">>],maps:keys(M4)),
+ true = is_members([wat,3,"hello",<<"value">>],maps:values(M4)),
+
+ M0 = #{ 4 := number } = M5 = maps:put(4, number, M4),
+
+ true = is_members([4,18446744073709551629,int,"hi",<<"key">>],maps:keys(M5)),
+ true = is_members([number,wat,3,"hello",<<"value">>],maps:values(M5)),
+
+ M6 = #{ <<"key">> := <<"other value">> } = maps:put(<<"key">>, <<"other value">>, M5),
+
+ true = is_members([4,18446744073709551629,int,"hi",<<"key">>],maps:keys(M6)),
+ true = is_members([number,wat,3,"hello",<<"other value">>],maps:values(M6)),
+
+ %% error case
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,put,_,_}|_]}} =
+ (catch maps:put(1, a, T))
+ end),
+ ok.
+
+is_members(Ks,Ls) when length(Ks) =/= length(Ls) -> false;
+is_members(Ks,Ls) -> is_members_do(Ks,Ls).
+
+is_members_do([],[]) -> true;
+is_members_do([],_) -> false;
+is_members_do([K|Ks],Ls) ->
+ is_members_do(Ks, lists:delete(K,Ls)).
+
+t_bif_map_remove(Config) when is_list(Config) ->
+ 0 = erlang:map_size(maps:remove(some_key, #{})),
+
+ M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+
+ M1 = maps:remove("hi", M0),
+ true = is_members([4,18446744073709551629,int,<<"key">>],maps:keys(M1)),
+ true = is_members([number,wat,3,<<"value">>],maps:values(M1)),
+
+ M2 = maps:remove(int, M1),
+ true = is_members([4,18446744073709551629,<<"key">>],maps:keys(M2)),
+ true = is_members([number,wat,<<"value">>],maps:values(M2)),
+
+ M3 = maps:remove(<<"key">>, M2),
+ true = is_members([4,18446744073709551629],maps:keys(M3)),
+ true = is_members([number,wat],maps:values(M3)),
+
+ M4 = maps:remove(18446744073709551629, M3),
+ true = is_members([4],maps:keys(M4)),
+ true = is_members([number],maps:values(M4)),
+
+ M5 = maps:remove(4, M4),
+ [] = maps:keys(M5),
+ [] = maps:values(M5),
+
+ M0 = maps:remove(5,M0),
+ M0 = maps:remove("hi there",M0),
+
+ #{ "hi" := "hello", int := 3, 4 := number} = maps:remove(18446744073709551629,maps:remove(<<"key">>,M0)),
+
+ %% error case
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,remove,_,_}|_]}} =
+ (catch maps:remove(a, T))
+ end),
+ ok.
+
+t_bif_map_update(Config) when is_list(Config) ->
+ M0 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>,
+ 4 => number, 18446744073709551629 => wat},
+
+ #{ "hi" := "hello again", int := 3, <<"key">> := <<"value">>,
+ 4 := number, 18446744073709551629 := wat} = maps:update("hi", "hello again", M0),
+
+ #{ "hi" := "hello", int := 1337, <<"key">> := <<"value">>,
+ 4 := number, 18446744073709551629 := wat} = maps:update(int, 1337, M0),
+
+ #{ "hi" := "hello", int := 3, <<"key">> := <<"new value">>,
+ 4 := number, 18446744073709551629 := wat} = maps:update(<<"key">>, <<"new value">>, M0),
+
+ #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
+ 4 := integer, 18446744073709551629 := wat} = maps:update(4, integer, M0),
+
+ #{ "hi" := "hello", int := 3, <<"key">> := <<"value">>,
+ 4 := number, 18446744073709551629 := wazzup} = maps:update(18446744073709551629, wazzup, M0),
+
+ %% error case
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,update,_,_}|_]}} =
+ (catch maps:update(1, none, T))
+ end),
+ ok.
+
+
+
+t_bif_map_values(Config) when is_list(Config) ->
+
+ [] = maps:values(#{}),
+ [1] = maps:values(#{a=>1}),
+
+ true = is_members([a,b,c,d,e],maps:values(#{ 1 => a, 2 => b, 3 => c, 4 => d, 5 => e})),
+ true = is_members([a,b,c,d,e],maps:values(#{ 4 => d, 5 => e, 1 => a, 2 => b, 3 => c})),
+
+ M1 = #{ "hi" => "hello", int => 3, <<"key">> => <<"value">>, 4 => number},
+ M2 = M1#{ "hi" => "hello2", <<"key">> => <<"value2">> },
+ true = is_members([number,3,"hello2",<<"value2">>],maps:values(M2)),
+ true = is_members([number,3,"hello",<<"value">>],maps:values(M1)),
+
+ Vs = lists:seq(1000,20000),
+ M3 = maps:from_list([{K,K}||K<-Vs]),
+ M4 = maps:merge(M1,M3),
+ M5 = maps:merge(M2,M3),
+ true = is_members(Vs,maps:values(M3)),
+ true = is_members([number,3,"hello",<<"value">>]++Vs,maps:values(M4)),
+ true = is_members([number,3,"hello2",<<"value2">>]++Vs,maps:values(M5)),
+
+ %% error case
+ do_badmap(fun(T) ->
+ {'EXIT',{{badmap,T},[{maps,values,_,_}|_]}} =
+ (catch maps:values(T))
+ end),
+ ok.
+
+t_erlang_hash(Config) when is_list(Config) ->
+ ok = t_bif_erlang_phash2(),
+ ok = t_bif_erlang_phash(),
+ ok.
+
+t_bif_erlang_phash2() ->
+
+ 39679005 = erlang:phash2(#{}),
+ 33667975 = erlang:phash2(#{ a => 1, "a" => 2, <<"a">> => 3, {a,b} => 4 }), % 78942764
+ 95332690 = erlang:phash2(#{ 1 => a, 2 => "a", 3 => <<"a">>, 4 => {a,b} }), % 37338230
+ 108954384 = erlang:phash2(#{ 1 => a }), % 14363616
+ 59617982 = erlang:phash2(#{ a => 1 }), % 51612236
+
+ 42770201 = erlang:phash2(#{{} => <<>>}), % 37468437
+ 71687700 = erlang:phash2(#{<<>> => {}}), % 44049159
+
+ M0 = #{ a => 1, "key" => <<"value">> },
+ M1 = maps:remove("key",M0),
+ M2 = M1#{ "key" => <<"value">> },
+
+ 70249457 = erlang:phash2(M0), % 118679416
+ 59617982 = erlang:phash2(M1), % 51612236
+ 70249457 = erlang:phash2(M2), % 118679416
+ ok.
+
+t_bif_erlang_phash() ->
+ Sz = 1 bsl 32,
+ 1113425985 = erlang:phash(#{},Sz), % 268440612
+ 1510068139 = erlang:phash(#{ a => 1, "a" => 2, <<"a">> => 3, {a,b} => 4 },Sz), % 1196461908
+ 3182345590 = erlang:phash(#{ 1 => a, 2 => "a", 3 => <<"a">>, 4 => {a,b} },Sz), % 3944426064
+ 2927531828 = erlang:phash(#{ 1 => a },Sz), % 1394238263
+ 1670235874 = erlang:phash(#{ a => 1 },Sz), % 4066388227
+
+ 3935089469 = erlang:phash(#{{} => <<>>},Sz), % 1578050717
+ 71692856 = erlang:phash(#{<<>> => {}},Sz), % 1578050717
+
+ M0 = #{ a => 1, "key" => <<"value">> },
+ M1 = maps:remove("key",M0),
+ M2 = M1#{ "key" => <<"value">> },
+
+ 2620391445 = erlang:phash(M0,Sz), % 3590546636
+ 1670235874 = erlang:phash(M1,Sz), % 4066388227
+ 2620391445 = erlang:phash(M2,Sz), % 3590546636
+ ok.
+
+t_map_encode_decode(Config) when is_list(Config) ->
+ <<131,116,0,0,0,0>> = erlang:term_to_binary(#{}),
+ Pairs = [
+ {a,b},{"key","values"},{<<"key">>,<<"value">>},
+ {1,b},{[atom,1],{<<"wat">>,1,2,3}},
+ {aa,"values"},
+ {1 bsl 64 + (1 bsl 50 - 1), sc1},
+ {99, sc2},
+ {1 bsl 65 + (1 bsl 51 - 1), sc3},
+ {88, sc4},
+ {1 bsl 66 + (1 bsl 52 - 1), sc5},
+ {77, sc6},
+ {1 bsl 67 + (1 bsl 53 - 1), sc3},
+ {75, sc6}, {-10,sc8},
+ {<<>>, sc9}, {3.14158, sc10},
+ {[3.14158], sc11}, {more_atoms, sc12},
+ {{more_tuples}, sc13}, {self(), sc14},
+ {{},{}},{[],[]}
+ ],
+ ok = map_encode_decode_and_match(Pairs,[],#{}),
+
+ %% check sorting
+
+ %% literally #{ b=>2, a=>1 } in the internal order
+ #{ a:=1, b:=2 } =
+ erlang:binary_to_term(<<131,116,0,0,0,2,100,0,1,98,97,2,100,0,1,97,97,1>>),
+
+
+ %% literally #{ "hi" => "value", a=>33, b=>55 } in the internal order
+ #{ a:=33, b:=55, "hi" := "value"} = erlang:binary_to_term(<<131,116,0,0,0,3,
+ 107,0,2,104,105, % "hi" :: list()
+ 107,0,5,118,97,108,117,101, % "value" :: list()
+ 100,0,1,97, % a :: atom()
+ 97,33, % 33 :: integer()
+ 100,0,1,98, % b :: atom()
+ 97,55 % 55 :: integer()
+ >>),
+
+ %% Maps of different sizes
+ lists:foldl(fun(Key, M0) ->
+ M1 = M0#{Key => Key},
+ case Key rem 17 of
+ 0 ->
+ M1 = binary_to_term(term_to_binary(M1));
+ _ ->
+ ok
+ end,
+ M1
+ end,
+ #{},
+ lists:seq(1,10000)),
+
+ %% many maps in same binary
+ MapList = lists:foldl(fun(K, [M|_]=Acc) -> [M#{K => K} | Acc] end,
+ [#{}],
+ lists:seq(1,100)),
+ MapList = binary_to_term(term_to_binary(MapList)),
+ MapListR = lists:reverse(MapList),
+ MapListR = binary_to_term(term_to_binary(MapListR)),
+
+ %% error cases
+ %% template: <<131,116,0,0,0,2,100,0,1,97,100,0,1,98,97,1,97,1>>
+ %% which is: #{ a=>1, b=>1 }
+
+ %% uniqueness violation
+ %% literally #{ a=>1, "hi"=>"value", a=>2 }
+ {'EXIT',{badarg,[{_,_,_,_}|_]}} = (catch
+ erlang:binary_to_term(<<131,116,0,0,0,3,
+ 100,0,1,97,
+ 97,1,
+ 107,0,2,104,105,
+ 107,0,5,118,97,108,117,101,
+ 100,0,1,97,
+ 97,2>>)),
+
+ %% bad size (too large)
+ {'EXIT',{badarg,[{_,_,_,_}|_]}} = (catch
+ erlang:binary_to_term(<<131,116,0,0,0,12,100,0,1,97,97,1,100,0,1,98,97,1>>)),
+
+ %% bad size (too small) .. should fail just truncate it .. weird.
+ %% possibly change external format so truncated will be #{a:=1}
+ #{ a:=b } =
+ erlang:binary_to_term(<<131,116,0,0,0,1,100,0,1,97,100,0,1,98,97,1,97,1>>),
+
+ ok.
+
+map_encode_decode_and_match([{K,V}|Pairs], EncodedPairs, M0) ->
+ M1 = maps:put(K,V,M0),
+ B0 = erlang:term_to_binary(M1),
+ Ls = [{erlang:term_to_binary(K), erlang:term_to_binary(V)}|EncodedPairs],
+ ok = match_encoded_map(B0, length(Ls), Ls),
+ %% decode and match it
+ M1 = erlang:binary_to_term(B0),
+ map_encode_decode_and_match(Pairs,Ls,M1);
+map_encode_decode_and_match([],_,_) -> ok.
+
+match_encoded_map(<<131,116,Size:32,Encoded/binary>>,Size,Items) ->
+ match_encoded_map_stripped_size(Encoded,Items,Items);
+match_encoded_map(_,_,_) -> no_match_size.
+
+match_encoded_map_stripped_size(<<>>,_,_) -> ok;
+match_encoded_map_stripped_size(B0,[{<<131,K/binary>>,<<131,V/binary>>}|Items],Ls) ->
+ Ksz = byte_size(K),
+ Vsz = byte_size(V),
+ case B0 of
+ <<K:Ksz/binary,V:Vsz/binary,B1/binary>> ->
+ match_encoded_map_stripped_size(B1,Ls,Ls);
+ _ ->
+ match_encoded_map_stripped_size(B0,Items,Ls)
+ end;
+match_encoded_map_stripped_size(_,[],_) -> fail.
+
+
+t_bif_map_to_list(Config) when is_list(Config) ->
+ [] = maps:to_list(#{}),
+ [{a,1},{b,2}] = lists:sort(maps:to_list(#{a=>1,b=>2})),
+ [{a,1},{b,2},{c,3}] = lists:sort(maps:to_list(#{c=>3,a=>1,b=>2})),
+ [{a,1},{b,2},{g,3}] = lists:sort(maps:to_list(#{g=>3,a=>1,b=>2})),
+ [{a,1},{b,2},{g,3},{"c",4}] = lists:sort(maps:to_list(#{g=>3,a=>1,b=>2,"c"=>4})),
+ [{3,v2},{hi,v4},{{hi,3},v5},{"hi",v3},{<<"hi">>,v1}] =
+ lists:sort(maps:to_list(#{<<"hi">>=>v1,3=>v2,"hi"=>v3,hi=>v4,{hi,3}=>v5})),
+
+ [{3,v7},{hi,v9},{{hi,3},v10},{"hi",v8},{<<"hi">>,v6}] =
+ lists:sort(maps:to_list(#{<<"hi">>=>v1,3=>v2,"hi"=>v3,hi=>v4,{hi,3}=>v5,
+ <<"hi">>=>v6,3=>v7,"hi"=>v8,hi=>v9,{hi,3}=>v10})),
+
+ %% error cases
+ do_badmap(fun(T) ->
+ {'EXIT', {{badmap,T},_}} =
+ (catch maps:to_list(T))
+ end),
+ ok.
+
+
+t_bif_map_from_list(Config) when is_list(Config) ->
+ #{} = maps:from_list([]),
+ A = maps:from_list([]),
+ 0 = erlang:map_size(A),
+
+ #{a:=1,b:=2} = maps:from_list([{a,1},{b,2}]),
+ #{c:=3,a:=1,b:=2} = maps:from_list([{a,1},{b,2},{c,3}]),
+ #{g:=3,a:=1,b:=2} = maps:from_list([{a,1},{b,2},{g,3}]),
+
+ #{a:=2} = maps:from_list([{a,1},{a,3},{a,2}]),
+
+ #{ <<"hi">>:=v1,3:=v3,"hi":=v6,hi:=v4,{hi,3}:=v5} =
+ maps:from_list([{3,v3},{"hi",v6},{hi,v4},{{hi,3},v5},{<<"hi">>,v1}]),
+
+ #{<<"hi">>:=v6,3:=v8,"hi":=v11,hi:=v9,{hi,3}:=v10} =
+ maps:from_list([ {{hi,3},v3}, {"hi",v0},{3,v1}, {<<"hi">>,v4}, {hi,v2},
+ {<<"hi">>,v6}, {{hi,3},v10},{"hi",v11}, {hi,v9}, {3,v8}]),
+
+ %% repeated keys (large -> small)
+ Ps1 = [{a,I}|| I <- lists:seq(1,32)],
+ Ps2 = [{a,I}|| I <- lists:seq(33,64)],
+
+ M = maps:from_list(Ps1 ++ [{b,1},{c,1}] ++ Ps2),
+ #{ a := 64, b := 1, c := 1 } = M,
+
+ %% error cases
+ {'EXIT', {badarg,_}} = (catch maps:from_list([{a,b},b])),
+ {'EXIT', {badarg,_}} = (catch maps:from_list([{a,b},{b,b,3}])),
+ {'EXIT', {badarg,_}} = (catch maps:from_list([{a,b},<<>>])),
+ {'EXIT', {badarg,_}} = (catch maps:from_list([{a,b}|{b,a}])),
+ {'EXIT', {badarg,_}} = (catch maps:from_list(a)),
+ {'EXIT', {badarg,_}} = (catch maps:from_list(42)),
+ ok.
+
+t_bif_build_and_check(Config) when is_list(Config) ->
+ ok = check_build_and_remove(750,[
+ fun(K) -> [K,K] end,
+ fun(K) -> [float(K),K] end,
+ fun(K) -> K end,
+ fun(K) -> {1,K} end,
+ fun(K) -> {K} end,
+ fun(K) -> [K|K] end,
+ fun(K) -> [K,1,2,3,4] end,
+ fun(K) -> {K,atom} end,
+ fun(K) -> float(K) end,
+ fun(K) -> integer_to_list(K) end,
+ fun(K) -> list_to_atom(integer_to_list(K)) end,
+ fun(K) -> [K,{K,[K,{K,[K]}]}] end,
+ fun(K) -> <<K:32>> end
+ ]),
+
+ ok.
+
+check_build_and_remove(_,[]) -> ok;
+check_build_and_remove(N,[F|Fs]) ->
+ {M,Ks} = build_and_check(N, maps:new(), F, []),
+ ok = remove_and_check(Ks,M),
+ check_build_and_remove(N,Fs).
+
+build_and_check(0, M0, _, Ks) -> {M0, Ks};
+build_and_check(N, M0, F, Ks) ->
+ K = build_key(F,N),
+ M1 = maps:put(K,K,M0),
+ ok = check_keys_exist([I||{I,_} <- [{K,M1}|Ks]], M1),
+ M2 = maps:update(K,v,M1),
+ v = maps:get(K,M2),
+ build_and_check(N-1,M1,F,[{K,M1}|Ks]).
+
+remove_and_check([],_) -> ok;
+remove_and_check([{K,Mc}|Ks], M0) ->
+ K = maps:get(K,M0),
+ true = maps:is_key(K,M0),
+ true = Mc =:= M0,
+ true = M0 == Mc,
+ M1 = maps:remove(K,M0),
+ false = M1 =:= Mc,
+ false = Mc == M1,
+ false = maps:is_key(K,M1),
+ true = maps:is_key(K,M0),
+ ok = check_keys_exist([I||{I,_} <- Ks],M1),
+ error = maps:find(K,M1),
+ remove_and_check(Ks, M1).
+
+build_key(F,N) when N rem 3 =:= 0 -> F(N);
+build_key(F,N) when N rem 3 =:= 1 -> K = F(N), {K,K};
+build_key(F,N) when N rem 3 =:= 2 -> K = F(N), [K,K].
+
+check_keys_exist([], _) -> ok;
+check_keys_exist([K|Ks],M) ->
+ true = maps:is_key(K,M),
+ check_keys_exist(Ks,M).
+
+t_bif_merge_and_check(Config) when is_list(Config) ->
+
+ io:format("rand:export_seed() -> ~p\n",[rand:export_seed()]),
+
+ %% simple disjunct ones
+ %% make sure all keys are unique
+ Kss = [[a,b,c,d],
+ [1,2,3,4],
+ [],
+ ["hi"],
+ [e],
+ [build_key(fun(K) -> {small,K} end, I) || I <- lists:seq(1,32)],
+ lists:seq(5, 28),
+ lists:seq(29, 59),
+ [build_key(fun(K) -> integer_to_list(K) end, I) || I <- lists:seq(2000,10000)],
+ [build_key(fun(K) -> <<K:32>> end, I) || I <- lists:seq(1,80)],
+ [build_key(fun(K) -> {<<K:32>>} end, I) || I <- lists:seq(100,1000)]],
+
+
+ KsMs = build_keys_map_pairs(Kss),
+ Cs = [{CKs1,CM1,CKs2,CM2} || {CKs1,CM1} <- KsMs, {CKs2,CM2} <- KsMs],
+ ok = merge_and_check_combo(Cs),
+
+ %% overlapping ones
+
+ KVs1 = [{a,1},{b,2},{c,3}],
+ KVs2 = [{b,3},{c,4},{d,5}],
+ KVs = [{I,I} || I <- lists:seq(1,32)],
+ KVs3 = KVs1 ++ KVs,
+ KVs4 = KVs2 ++ KVs,
+
+ M1 = maps:from_list(KVs1),
+ M2 = maps:from_list(KVs2),
+ M3 = maps:from_list(KVs3),
+ M4 = maps:from_list(KVs4),
+
+ M12 = maps:merge(M1,M2),
+ ok = check_key_values(KVs2 ++ [{a,1}], M12),
+ M21 = maps:merge(M2,M1),
+ ok = check_key_values(KVs1 ++ [{d,5}], M21),
+
+ M34 = maps:merge(M3,M4),
+ ok = check_key_values(KVs4 ++ [{a,1}], M34),
+ M43 = maps:merge(M4,M3),
+ ok = check_key_values(KVs3 ++ [{d,5}], M43),
+
+ M14 = maps:merge(M1,M4),
+ ok = check_key_values(KVs4 ++ [{a,1}], M14),
+ M41 = maps:merge(M4,M1),
+ ok = check_key_values(KVs1 ++ [{d,5}] ++ KVs, M41),
+
+ [begin Ma = random_map(SzA, a),
+ Mb = random_map(SzB, b),
+ ok = merge_maps(Ma, Mb)
+ end || SzA <- [3,10,20,100,200,1000], SzB <- [3,10,20,100,200,1000]],
+
+ ok.
+
+% Generate random map with an average of Sz number of pairs: K -> {V,K}
+random_map(Sz, V) ->
+ random_map_insert(#{}, 0, V, Sz*2).
+
+random_map_insert(M0, K0, _, Sz) when K0 > Sz ->
+ M0;
+random_map_insert(M0, K0, V, Sz) ->
+ Key = K0 + rand:uniform(3),
+ random_map_insert(M0#{Key => {V,Key}}, Key, V, Sz).
+
+
+merge_maps(A, B) ->
+ AB = maps:merge(A, B),
+ %%io:format("A=~p\nB=~p\n",[A,B]),
+ maps_foreach(fun(K,VB) -> VB = maps:get(K, AB)
+ end, B),
+ maps_foreach(fun(K,VA) ->
+ case {maps:get(K, AB),maps:find(K, B)} of
+ {VA, error} -> ok;
+ {VB, {ok, VB}} -> ok
+ end
+ end, A),
+
+ maps_foreach(fun(K,V) ->
+ case {maps:find(K, A),maps:find(K, B)} of
+ {{ok, V}, error} -> ok;
+ {error, {ok, V}} -> ok;
+ {{ok,_}, {ok, V}} -> ok
+ end
+ end, AB),
+ ok.
+
+maps_foreach(Fun, Map) ->
+ maps:fold(fun(K,V,_) -> Fun(K,V) end, void, Map).
+
+
+check_key_values([],_) -> ok;
+check_key_values([{K,V}|KVs],M) ->
+ V = maps:get(K,M),
+ check_key_values(KVs,M).
+
+merge_and_check_combo([]) -> ok;
+merge_and_check_combo([{Ks1,M1,Ks2,M2}|Cs]) ->
+ M12 = maps:merge(M1,M2),
+ ok = check_keys_exist(Ks1 ++ Ks2, M12),
+ M21 = maps:merge(M2,M1),
+ ok = check_keys_exist(Ks1 ++ Ks2, M21),
+
+ true = M12 =:= M21,
+ M12 = M21,
+
+ merge_and_check_combo(Cs).
+
+build_keys_map_pairs([]) -> [];
+build_keys_map_pairs([Ks|Kss]) ->
+ M = maps:from_list(keys_to_pairs(Ks)),
+ ok = check_keys_exist(Ks, M),
+ [{Ks,M}|build_keys_map_pairs(Kss)].
+
+keys_to_pairs(Ks) -> [{K,K} || K <- Ks].
+
+
+%% Maps module, not BIFs
+t_maps_fold(_Config) ->
+ Vs = lists:seq(1,100),
+ M = maps:from_list([{{k,I},{v,I}}||I<-Vs]),
+
+ %% fold
+ 5050 = maps:fold(fun({k,_},{v,V},A) -> V + A end, 0, M),
+
+ ok.
+
+t_maps_map(_Config) ->
+ Vs = lists:seq(1,100),
+ M1 = maps:from_list([{I,I}||I<-Vs]),
+ M2 = maps:from_list([{I,{token,I}}||I<-Vs]),
+
+ M2 = maps:map(fun(_K,V) -> {token,V} end, M1),
+ ok.
+
+t_maps_size(_Config) ->
+ Vs = lists:seq(1,100),
+ lists:foldl(fun(I,M) ->
+ M1 = maps:put(I,I,M),
+ I = maps:size(M1),
+ M1
+ end, #{}, Vs),
+ ok.
+
+
+t_maps_without(_Config) ->
+ Ki = [11,22,33,44,55,66,77,88,99],
+ M0 = maps:from_list([{{k,I},{v,I}}||I<-lists:seq(1,100)]),
+ M1 = maps:from_list([{{k,I},{v,I}}||I<-lists:seq(1,100) -- Ki]),
+ M1 = maps:without([{k,I}||I <- Ki],M0),
+ ok.
+
+t_erts_internal_order(_Config) when is_list(_Config) ->
+ M = #{0 => 0,2147483648 => 0},
+ true = M =:= binary_to_term(term_to_binary(M)),
+
+ F1 = fun(_, _) -> 0 end,
+ F2 = fun(_, _) -> 1 end,
+ M0 = maps:from_list( [{-2147483649, 0}, {0,0}, {97, 0}, {false, 0}, {flower, 0}, {F1, 0}, {F2, 0}, {<<>>, 0}]),
+ M1 = maps:merge(M0, #{0 => 1}),
+ 8 = maps:size(M1),
+ 1 = maps:get(0,M1),
+ ok.
+
+t_erts_internal_hash(_Config) when is_list(_Config) ->
+ K1 = 0.0,
+ K2 = 0.0/-1,
+ M = maps:from_list([{I,I}||I<-lists:seq(1,32)]),
+
+ M1 = M#{ K1 => a, K2 => b },
+ b = maps:get(K2,M1),
+
+ M2 = M#{ K2 => a, K1 => b },
+ b = maps:get(K1,M2),
+
+ %% test previously faulty hash list optimization
+
+ M3 = M#{[0] => a, [0,0] => b, [0,0,0] => c, [0,0,0,0] => d},
+ a = maps:get([0],M3),
+ b = maps:get([0,0],M3),
+ c = maps:get([0,0,0],M3),
+ d = maps:get([0,0,0,0],M3),
+
+ M4 = M#{{[0]} => a, {[0,0]} => b, {[0,0,0]} => c, {[0,0,0,0]} => d},
+ a = maps:get({[0]},M4),
+ b = maps:get({[0,0]},M4),
+ c = maps:get({[0,0,0]},M4),
+ d = maps:get({[0,0,0,0]},M4),
+
+ M5 = M3#{[0,0,0] => e, [0,0,0,0] => f, [0,0,0,0,0] => g,
+ [0,0,0,0,0,0] => h, [0,0,0,0,0,0,0] => i,
+ [0,0,0,0,0,0,0,0] => j, [0,0,0,0,0,0,0,0,0] => k},
+
+ a = maps:get([0],M5),
+ b = maps:get([0,0],M5),
+ e = maps:get([0,0,0],M5),
+ f = maps:get([0,0,0,0],M5),
+ g = maps:get([0,0,0,0,0],M5),
+ h = maps:get([0,0,0,0,0,0],M5),
+ i = maps:get([0,0,0,0,0,0,0],M5),
+ j = maps:get([0,0,0,0,0,0,0,0],M5),
+ k = maps:get([0,0,0,0,0,0,0,0,0],M5),
+
+ M6 = M4#{{[0,0,0]} => e, {[0,0,0,0]} => f, {[0,0,0,0,0]} => g,
+ {[0,0,0,0,0,0]} => h, {[0,0,0,0,0,0,0]} => i,
+ {[0,0,0,0,0,0,0,0]} => j, {[0,0,0,0,0,0,0,0,0]} => k},
+
+ a = maps:get({[0]},M6),
+ b = maps:get({[0,0]},M6),
+ e = maps:get({[0,0,0]},M6),
+ f = maps:get({[0,0,0,0]},M6),
+ g = maps:get({[0,0,0,0,0]},M6),
+ h = maps:get({[0,0,0,0,0,0]},M6),
+ i = maps:get({[0,0,0,0,0,0,0]},M6),
+ j = maps:get({[0,0,0,0,0,0,0,0]},M6),
+ k = maps:get({[0,0,0,0,0,0,0,0,0]},M6),
+
+ M7 = maps:merge(M5,M6),
+
+ a = maps:get([0],M7),
+ b = maps:get([0,0],M7),
+ e = maps:get([0,0,0],M7),
+ f = maps:get([0,0,0,0],M7),
+ g = maps:get([0,0,0,0,0],M7),
+ h = maps:get([0,0,0,0,0,0],M7),
+ i = maps:get([0,0,0,0,0,0,0],M7),
+ j = maps:get([0,0,0,0,0,0,0,0],M7),
+ k = maps:get([0,0,0,0,0,0,0,0,0],M7),
+ a = maps:get({[0]},M7),
+ b = maps:get({[0,0]},M7),
+ e = maps:get({[0,0,0]},M7),
+ f = maps:get({[0,0,0,0]},M7),
+ g = maps:get({[0,0,0,0,0]},M7),
+ h = maps:get({[0,0,0,0,0,0]},M7),
+ i = maps:get({[0,0,0,0,0,0,0]},M7),
+ j = maps:get({[0,0,0,0,0,0,0,0]},M7),
+ k = maps:get({[0,0,0,0,0,0,0,0,0]},M7),
+ ok.
+
+t_pdict(_Config) ->
+
+ put(#{ a => b, b => a},#{ c => d}),
+ put(get(#{ a => b, b => a}),1),
+ 1 = get(#{ c => d}),
+ #{ c := d } = get(#{ a => b, b => a}).
+
+t_ets(_Config) ->
+
+ Tid = ets:new(map_table,[]),
+
+ [ets:insert(Tid,{maps:from_list([{I,-I}]),I}) || I <- lists:seq(1,100)],
+
+
+ [{#{ 2 := -2},2}] = ets:lookup(Tid,#{ 2 => -2 }),
+
+ %% Test equal
+ [3,4] = lists:sort(
+ ets:select(Tid,[{{'$1','$2'},
+ [{'or',{'==','$1',#{ 3 => -3 }},
+ {'==','$1',#{ 4 => -4 }}}],
+ ['$2']}])),
+ %% Test match
+ [30,50] = lists:sort(
+ ets:select(Tid,
+ [{{#{ 30 => -30}, '$1'},[],['$1']},
+ {{#{ 50 => -50}, '$1'},[],['$1']}]
+ )),
+
+ ets:insert(Tid,{#{ a => b, b => c, c => a},transitivity}),
+
+ %% Test equal with map of different size
+ [] = ets:select(Tid,[{{'$1','_'},[{'==','$1',#{ b => c }}],['$_']}]),
+
+ %% Test match with map of different size
+ %[{#{ a := b },_}] = ets:select(Tid,[{{#{ b => c },'_'},[],['$_']}]),
+
+ %%% Test match with don't care value
+ %[{#{ a := b },_}] = ets:select(Tid,[{{#{ b => '_' },'_'},[],['$_']}]),
+
+ %% Test is_map bif
+ 101 = length(ets:select(Tid,[{'$1',[{is_map,{element,1,'$1'}}],['$1']}])),
+ ets:insert(Tid,{not_a_map,2}),
+ 101 = length(ets:select(Tid,[{'$1',[{is_map,{element,1,'$1'}}],['$1']}])),
+ ets:insert(Tid,{{nope,a,tuple},2}),
+ 101 = length(ets:select(Tid,[{'$1',[{is_map,{element,1,'$1'}}],['$1']}])),
+
+ %% Test map_size bif
+ [3] = ets:select(Tid,[{{'$1','_'},[{'==',{map_size,'$1'},3}],
+ [{map_size,'$1'}]}]),
+
+ true = ets:delete(Tid,#{50 => -50}),
+ [] = ets:lookup(Tid,#{50 => -50}),
+
+ ets:delete(Tid),
+ ok.
+
+t_dets(_Config) ->
+ ok.
+
+t_tracing(_Config) ->
+
+ dbg:stop_clear(),
+ {ok,Tracer} = dbg:tracer(process,{fun trace_collector/2, self()}),
+ dbg:p(self(),c),
+
+ %% Test basic map call
+ {ok,_} = dbg:tpl(?MODULE,id,x),
+ #{ a => b },
+ {trace,_,call,{?MODULE,id,[#{ a := b }]}} = getmsg(Tracer),
+ {trace,_,return_from,{?MODULE,id,1},#{ a := b }} = getmsg(Tracer),
+ dbg:ctpl(),
+
+ %% Test equals in argument list
+ {ok,_} = dbg:tpl(?MODULE,id,[{['$1'],[{'==','$1',#{ b => c}}],
+ [{return_trace}]}]),
+ #{ a => b },
+ #{ b => c },
+ {trace,_,call,{?MODULE,id,[#{ b := c }]}} = getmsg(Tracer),
+ {trace,_,return_from,{?MODULE,id,1},#{ b := c }} = getmsg(Tracer),
+ dbg:ctpl(),
+
+ %% Test match in head
+ {ok,_} = dbg:tpl(?MODULE,id,[{[#{b => c}],[],[]}]),
+ #{ a => b },
+ #{ b => c },
+ {trace,_,call,{?MODULE,id,[#{ b := c }]}} = getmsg(Tracer),
+ dbg:ctpl(),
+
+ % Test map guard bifs
+ {ok,_} = dbg:tpl(?MODULE,id,[{['$1'],[{is_map,{element,1,'$1'}}],[]}]),
+ #{ a => b },
+ {1,2},
+ {#{ a => b},2},
+ {trace,_,call,{?MODULE,id,[{#{ a := b },2}]}} = getmsg(Tracer),
+ dbg:ctpl(),
+
+ {ok,_} = dbg:tpl(?MODULE,id,[{['$1'],[{'==',{map_size,{element,1,'$1'}},2}],[]}]),
+ #{ a => b },
+ {1,2},
+ {#{ a => b},2},
+ {#{ a => b, b => c},atom},
+ {trace,_,call,{?MODULE,id,[{#{ a := b, b := c },atom}]}} = getmsg(Tracer),
+ dbg:ctpl(),
+
+ %MS = dbg:fun2ms(fun([A]) when A == #{ a => b} -> ok end),
+ %dbg:tpl(?MODULE,id,MS),
+ %#{ a => b },
+ %#{ b => c },
+ %{trace,_,call,{?MODULE,id,[#{ a := b }]}} = getmsg(Tracer),
+ %dbg:ctpl(),
+
+ %% Check to extra messages
+ timeout = getmsg(Tracer),
+
+ dbg:stop_clear(),
+ ok.
+
+getmsg(_Tracer) ->
+ receive V -> V after 100 -> timeout end.
+
+trace_collector(Msg,Parent) ->
+ io:format("~p~n",[Msg]),
+ Parent ! Msg,
+ Parent.
+
+t_has_map_fields(Config) when is_list(Config) ->
+ true = has_map_fields_1(#{one=>1}),
+ true = has_map_fields_1(#{one=>1,two=>2}),
+ false = has_map_fields_1(#{two=>2}),
+ false = has_map_fields_1(#{}),
+
+ true = has_map_fields_2(#{c=>1,b=>2,a=>3}),
+ true = has_map_fields_2(#{c=>1,b=>2,a=>3,x=>42}),
+ false = has_map_fields_2(#{b=>2,c=>1}),
+ false = has_map_fields_2(#{x=>y}),
+ false = has_map_fields_2(#{}),
+
+ true = has_map_fields_3(#{c=>1,b=>2,a=>3}),
+ true = has_map_fields_3(#{c=>1,b=>2,a=>3,[]=>42}),
+ true = has_map_fields_3(#{b=>2,a=>3,[]=>42,42.0=>43}),
+ true = has_map_fields_3(#{a=>3,[]=>42,42.0=>43}),
+ true = has_map_fields_3(#{[]=>42,42.0=>43}),
+ false = has_map_fields_3(#{b=>2,c=>1}),
+ false = has_map_fields_3(#{[]=>y}),
+ false = has_map_fields_3(#{42.0=>x,a=>99}),
+ false = has_map_fields_3(#{}),
+
+ ok.
+
+has_map_fields_1(#{one:=_}) -> true;
+has_map_fields_1(#{}) -> false.
+
+has_map_fields_2(#{a:=_,b:=_,c:=_}) -> true;
+has_map_fields_2(#{}) -> false.
+
+has_map_fields_3(#{a:=_,b:=_}) -> true;
+has_map_fields_3(#{[]:=_,42.0:=_}) -> true;
+has_map_fields_3(#{}) -> false.
+
+y_regs(Config) when is_list(Config) ->
+ Val = [length(Config)],
+ Map0 = y_regs_update(#{}, Val),
+ Map2 = y_regs_update(Map0, Val),
+
+ Map3 = maps:from_list([{I,I*I} || I <- lists:seq(1, 100)]),
+ Map4 = y_regs_update(Map3, Val),
+
+ true = is_map(Map2) andalso is_map(Map4),
+
+ ok.
+
+y_regs_update(Map0, Val0) ->
+ Val1 = {t,Val0},
+ K1 = {key,1},
+ K2 = {key,2},
+ Map1 = Map0#{K1=>K1,
+ a=>Val0,b=>Val0,c=>Val0,d=>Val0,e=>Val0,
+ f=>Val0,g=>Val0,h=>Val0,i=>Val0,j=>Val0,
+ k=>Val0,l=>Val0,m=>Val0,n=>Val0,o=>Val0,
+ p=>Val0,q=>Val0,r=>Val0,s=>Val0,t=>Val0,
+ u=>Val0,v=>Val0,w=>Val0,x=>Val0,y=>Val0,
+ z=>Val0,
+ aa=>Val0,ab=>Val0,ac=>Val0,ad=>Val0,ae=>Val0,
+ af=>Val0,ag=>Val0,ah=>Val0,ai=>Val0,aj=>Val0,
+ ak=>Val0,al=>Val0,am=>Val0,an=>Val0,ao=>Val0,
+ ap=>Val0,aq=>Val0,ar=>Val0,as=>Val0,at=>Val0,
+ au=>Val0,av=>Val0,aw=>Val0,ax=>Val0,ay=>Val0,
+ az=>Val0,
+ K2=>[a,b,c]},
+ Map2 = Map1#{K1=>K1,
+ a:=Val1,b:=Val1,c:=Val1,d:=Val1,e:=Val1,
+ f:=Val1,g:=Val1,h:=Val1,i:=Val1,j:=Val1,
+ k:=Val1,l:=Val1,m:=Val1,n:=Val1,o:=Val1,
+ p:=Val1,q:=Val1,r:=Val1,s:=Val1,t:=Val1,
+ u:=Val1,v:=Val1,w:=Val1,x:=Val1,y:=Val1,
+ z:=Val1,
+ aa:=Val1,ab:=Val1,ac:=Val1,ad:=Val1,ae:=Val1,
+ af:=Val1,ag:=Val1,ah:=Val1,ai:=Val1,aj:=Val1,
+ ak:=Val1,al:=Val1,am:=Val1,an:=Val1,ao:=Val1,
+ ap:=Val1,aq:=Val1,ar:=Val1,as:=Val1,at:=Val1,
+ au:=Val1,av:=Val1,aw:=Val1,ax:=Val1,ay:=Val1,
+ az:=Val1,
+ K2=>[a,b,c]},
+
+ %% Traverse the maps to validate them.
+ _ = erlang:phash2({Map1,Map2}, 100000),
+
+ _ = {K1,K2,Val0,Val1}, %Force use of Y registers.
+ Map2.
+
+do_badmap(Test) ->
+ Terms = [Test,fun erlang:abs/1,make_ref(),self(),0.0/-1,
+ <<0:1024>>,<<1:1>>,<<>>,<<1,2,3>>,
+ [],{a,b,c},[a,b],atom,10.0,42,(1 bsl 65) + 3],
+ [Test(T) || T <- Terms].
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/order.erl b/lib/dialyzer/test/indent_SUITE_data/src/order.erl
new file mode 100644
index 0000000000..51868d7e94
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/order.erl
@@ -0,0 +1,56 @@
+-module(order).
+
+-export([t1/0, t2/0, t3/0, t4/0, t5/0, t6/0]).
+
+t1() ->
+ case maps:get(a, #{a=>1, a=>b}) of
+ Int when is_integer(Int) -> fail;
+ Atom when is_atom(Atom) -> error(ok);
+ _Else -> fail
+ end.
+
+t2() ->
+ case maps:get(a, #{a=>id_1(1), a=>id_b(b)}) of
+ Int when is_integer(Int) -> fail;
+ Atom when is_atom(Atom) -> error(ok);
+ _Else -> fail
+ end.
+
+t3() ->
+ case maps:get(a, #{a=>id_1(1), id_a(a)=>id_b(b)}) of
+ Int when is_integer(Int) -> fail;
+ Atom when is_atom(Atom) -> error(ok);
+ _Else -> fail
+ end.
+
+t4() ->
+ case maps:get(a, #{a=>id_1(1), a_or_b()=>id_b(b)}) of
+ Int when is_integer(Int) -> ok;
+ Atom when is_atom(Atom) -> ok;
+ _Else -> fail
+ end.
+
+t5() ->
+ case maps:get(c, #{c=>id_1(1), a_or_b()=>id_b(b)}) of
+ Int when is_integer(Int) -> error(ok);
+ Atom when is_atom(Atom) -> fail;
+ _Else -> fail
+ end.
+
+t6() ->
+ case maps:get(a, #{a_or_b()=>id_1(1), id_a(a)=>id_b(b)}) of
+ Int when is_integer(Int) -> fail;
+ Atom when is_atom(Atom) -> error(ok);
+ _Else -> fail
+ end.
+
+id_1(X) -> X.
+
+id_a(X) -> X.
+
+id_b(X) -> X.
+
+any() -> binary_to_term(<<>>).
+
+-spec a_or_b() -> a | b.
+a_or_b() -> any().
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/queue_use.erl b/lib/dialyzer/test/indent_SUITE_data/src/queue_use.erl
new file mode 100644
index 0000000000..8d46bdb989
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/queue_use.erl
@@ -0,0 +1,65 @@
+-module(queue_use).
+
+-export([ok1/0, ok2/0]).
+-export([wrong1/0, wrong2/0, wrong3/0, wrong4/0, wrong5/0, wrong6/0, wrong7/0, wrong8/0]).
+
+ok1() ->
+ queue:is_empty(queue:new()).
+
+ok2() ->
+ Q0 = queue:new(),
+ Q1 = queue:in(42, Q0),
+ {{value, 42}, Q2} = queue:out(Q1),
+ queue:is_empty(Q2).
+
+%%--------------------------------------------------
+
+wrong1() ->
+ queue:is_empty({[],[]}).
+
+wrong2() ->
+ Q0 = {[],[]},
+ queue:in(42, Q0).
+
+wrong3() ->
+ Q0 = queue:new(),
+ Q1 = queue:in(42, Q0),
+ {[42],Q2} = Q1,
+ Q2.
+
+wrong4() ->
+ Q0 = queue:new(),
+ Q1 = queue:in(42, Q0),
+ Q1 =:= {[42],[]}.
+
+wrong5() ->
+ {F, _R} = queue:new(),
+ F.
+
+wrong6() ->
+ {{value, 42}, Q2} = queue:out({[42],[]}),
+ Q2.
+
+%%--------------------------------------------------
+
+-record(db, {p, q}).
+
+wrong7() ->
+ add_unique(42, #db{p = [], q = queue:new()}).
+
+add_unique(E, DB) ->
+ case is_in_queue(E, DB) of
+ true -> DB;
+ false -> DB#db{q = queue:in(E, DB#db.q)}
+ end.
+
+is_in_queue(P, #db{q = {L1,L2}}) ->
+ lists:member(P, L1) orelse lists:member(P, L2).
+
+%%--------------------------------------------------
+
+wrong8() ->
+ tuple_queue({42, gazonk}).
+
+tuple_queue({F, Q}) ->
+ queue:in(F, Q).
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/rec/rec_adt.erl b/lib/dialyzer/test/indent_SUITE_data/src/rec/rec_adt.erl
new file mode 100644
index 0000000000..f01cc5e519
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/rec/rec_adt.erl
@@ -0,0 +1,22 @@
+-module(rec_adt).
+
+-export([new/0, get_a/1, get_b/1, set_a/2, set_b/2]).
+
+-record(rec, {a :: atom(), b = 0 :: integer()}).
+
+-opaque rec() :: #rec{}.
+
+-spec new() -> rec().
+new() -> #rec{a = gazonk, b = 42}.
+
+-spec get_a(rec()) -> atom().
+get_a(#rec{a = A}) -> A.
+
+-spec get_b(rec()) -> integer().
+get_b(#rec{b = B}) -> B.
+
+-spec set_a(rec(), atom()) -> rec().
+set_a(R, A) -> R#rec{a = A}.
+
+-spec set_b(rec(), integer()) -> rec().
+set_b(R, B) -> R#rec{b = B}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/rec/rec_use.erl b/lib/dialyzer/test/indent_SUITE_data/src/rec/rec_use.erl
new file mode 100644
index 0000000000..358e9f918c
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/rec/rec_use.erl
@@ -0,0 +1,30 @@
+-module(rec_use).
+
+-export([ok1/0, ok2/0, wrong1/0, wrong2/0, wrong3/0, wrong4/0]).
+
+ok1() ->
+ rec_adt:set_a(rec_adt:new(), foo).
+
+ok2() ->
+ R1 = rec_adt:new(),
+ B1 = rec_adt:get_b(R1),
+ R2 = rec_adt:set_b(R1, 42),
+ B2 = rec_adt:get_b(R2),
+ B1 =:= B2.
+
+wrong1() ->
+ case rec_adt:new() of
+ {rec, _, 42} -> weird1;
+ R when tuple_size(R) =:= 3 -> weird2
+ end.
+
+wrong2() ->
+ R = list_to_tuple([rec, a, 42]),
+ rec_adt:get_a(R).
+
+wrong3() ->
+ R = rec_adt:new(),
+ R =:= {rec, gazonk, 42}.
+
+wrong4() ->
+ tuple_size(rec_adt:new()).
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/record_construct.erl b/lib/dialyzer/test/indent_SUITE_data/src/record_construct.erl
new file mode 100644
index 0000000000..b250c6ee65
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/record_construct.erl
@@ -0,0 +1,21 @@
+-module(record_construct).
+-export([t_loc/0, t_opa/0, t_rem/0]).
+
+-record(r_loc, {a = gazonk :: integer(), b = 42 :: atom()}).
+
+t_loc() ->
+ #r_loc{}.
+
+-record(r_opa, {a :: atom(),
+ b = gb_sets:new() :: gb_sets:set(),
+ c = 42 :: boolean(),
+ d, % untyped on purpose
+ e = false :: boolean()}).
+
+t_opa() ->
+ #r_opa{}.
+
+-record(r_rem, {a = gazonk :: string()}).
+
+t_rem() ->
+ #r_rem{}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/record_creation_diffs.erl b/lib/dialyzer/test/indent_SUITE_data/src/record_creation_diffs.erl
new file mode 100644
index 0000000000..e813459f8e
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/record_creation_diffs.erl
@@ -0,0 +1,11 @@
+-module(record_creation_diffs).
+
+-export([foo/1]).
+
+-record(bar, {
+ some_atom :: atom(),
+ some_list :: list()
+ }).
+
+foo(Input) ->
+ #bar{some_atom = Input, some_list = {this,is,a,tuple}}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/record_match.erl b/lib/dialyzer/test/indent_SUITE_data/src/record_match.erl
new file mode 100644
index 0000000000..8e9b91937f
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/record_match.erl
@@ -0,0 +1,17 @@
+-module(record_match).
+
+-export([select/0]).
+
+-record(b_literal, {val}).
+-record(b_remote, {mod,name,arity}).
+-record(b_local, {name,arity}).
+
+-type b_remote() :: #b_remote{}.
+-type b_local() :: #b_local{}.
+
+-type argument() :: b_remote() | b_local().
+
+-record(b_set, {args=[] :: [argument()]}).
+
+select() ->
+ #b_set{args=[#b_remote{},#b_literal{}]}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/record_pat.erl b/lib/dialyzer/test/indent_SUITE_data/src/record_pat.erl
new file mode 100644
index 0000000000..3308641571
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/record_pat.erl
@@ -0,0 +1,15 @@
+%%%-------------------------------------------------------------------
+%%% File : record_pat.erl
+%%% Author : Tobias Lindahl <>
+%%% Description : Emit warning if a pattern violates the record type
+%%%
+%%% Created : 21 Oct 2008 by Tobias Lindahl <>
+%%%-------------------------------------------------------------------
+-module(record_pat).
+
+-export([t/1]).
+
+-record(foo, {bar :: integer()}).
+
+t(#foo{bar=baz}) -> no_way;
+t(#foo{bar=1}) -> ok.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/record_send_test.erl b/lib/dialyzer/test/indent_SUITE_data/src/record_send_test.erl
new file mode 100644
index 0000000000..87cd97bd85
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/record_send_test.erl
@@ -0,0 +1,32 @@
+%%-------------------------------------------------------------------
+%% File : record_send_test.erl
+%% Author : Kostis Sagonas <[email protected]>
+%% Description : A test inspired by a post of Mkcael Remond to the
+%% Erlang mailing list suggesting thst Dialyzer should
+%% be reporting sends to records rather than to pids.
+%% Dialyzer v1.3.0 indeed reports one of the dicrepancies
+%% (the one with the 4-tuple) but not the one where the
+%% message is sent to a pair which is a record.
+%% This should be fixed.
+%%
+%% Created : 10 Apr 2005 by Kostis Sagonas <[email protected]>
+%%-------------------------------------------------------------------
+-module(record_send_test).
+
+-export([t/0]).
+
+-record(rec1, {a=a, b=b, c=c}).
+-record(rec2, {a}).
+
+t() ->
+ t(#rec1{}).
+
+t(Rec1 = #rec1{b=B}) ->
+ Rec2 = some_mod:some_function(),
+ if
+ is_record(Rec2, rec2) ->
+ Rec2 ! hello; %% currently this one is not found
+ true ->
+ Rec1 ! hello_again
+ end,
+ B.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/record_test.erl b/lib/dialyzer/test/indent_SUITE_data/src/record_test.erl
new file mode 100644
index 0000000000..48a00b172e
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/record_test.erl
@@ -0,0 +1,22 @@
+%%%-------------------------------------------------------------------
+%%% File : record_test.erl
+%%% Author : Tobias Lindahl <[email protected]>
+%%% Description :
+%%%
+%%% Created : 22 Oct 2004 by Tobias Lindahl <[email protected]>
+%%%-------------------------------------------------------------------
+-module(record_test).
+
+-export([t/0]).
+
+-record(foo, {bar}).
+
+t() ->
+ doit(foo).
+
+doit(X) ->
+ case X of
+ #foo{} -> error1;
+ foo -> ok;
+ _ -> error2
+ end.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/record_update.erl b/lib/dialyzer/test/indent_SUITE_data/src/record_update.erl
new file mode 100644
index 0000000000..bad7a0a929
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/record_update.erl
@@ -0,0 +1,10 @@
+-module(record_update).
+
+-export([quux/2]).
+
+-record(foo, {bar :: atom()}).
+
+-spec quux(#foo{}, string()) -> #foo{}.
+
+quux(Foo, NotBar) ->
+ Foo#foo{ bar = NotBar }.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_behaviour.erl b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_behaviour.erl
new file mode 100644
index 0000000000..116980986b
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_behaviour.erl
@@ -0,0 +1,13 @@
+-module(sample_behaviour).
+
+-type custom() :: 1..42.
+
+-callback sample_callback_1() -> term().
+-callback sample_callback_2() -> atom().
+-callback sample_callback_3() -> {'ok', custom()} | 'fail'.
+
+-callback sample_callback_4(term()) -> 'ok'.
+-callback sample_callback_5(custom()) -> 'ok' | 'fail'.
+
+-callback sample_callback_6(custom(), custom(), string()) ->
+ {'ok', custom()} | 'fail'.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct.erl b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct.erl
new file mode 100644
index 0000000000..ab0378e6f0
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct.erl
@@ -0,0 +1,32 @@
+-module(sample_callback_correct).
+
+-behaviour(sample_behaviour).
+
+-export([
+ sample_callback_1/0,
+ sample_callback_2/0,
+ sample_callback_3/0,
+ sample_callback_4/1,
+ sample_callback_5/1,
+ sample_callback_6/3
+ ]).
+
+sample_callback_1() -> 42. % This is a valid return.
+sample_callback_2() -> foo. % This is a valid return.
+sample_callback_3() -> {ok, 17}. % This is a valid return.
+sample_callback_4(Input) ->
+ put(mine, Input+1), % This is valid handling of the input
+ ok. % This is a valid return.
+sample_callback_5(Input) ->
+ case Input - 1 < 22 of % This is valid handling of the input
+ true -> ok; % This is a valid return.
+ false -> fail % This is a valid return.
+ end.
+sample_callback_6(OldNr, NewNr, Reason) ->
+ Diff = NewNr - OldNr, % This is valid handling of the input
+ Msg = string:join(["Reason: ", Reason], ","), % This is valid handling of the input
+ case Diff > 0 of
+ true -> put(mine, {NewNr, Msg}),
+ {ok, NewNr}; % This is a valid return.
+ false -> fail % This is a valid return.
+ end.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct_2.erl b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct_2.erl
new file mode 100644
index 0000000000..c218174e58
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_correct_2.erl
@@ -0,0 +1,38 @@
+-module(sample_callback_correct_2).
+
+-behaviour(sample_behaviour).
+
+-export([
+ sample_callback_1/0,
+ sample_callback_2/0,
+ sample_callback_3/0,
+ sample_callback_4/1,
+ sample_callback_5/1,
+ sample_callback_6/3,
+ common_infrastructure/1
+ ]).
+
+sample_callback_1() -> 42. % This is a valid return.
+sample_callback_2() -> halt(). % Crashes are also allowed.
+sample_callback_3() -> {ok, 17}. % This is a valid return.
+sample_callback_4(Input) ->
+ case Input of
+ 1 -> common_infrastructure(Input); % This is 'correct' input for
+ _ -> ok % common_infrastructure.
+ end.
+sample_callback_5(Input) ->
+ case get(Input) of % This is valid handling of a more generic input
+ true -> ok; % This is a valid return.
+ false -> fail % This is a valid return.
+ end.
+sample_callback_6(OldNr, NewNr, Reason) ->
+ Diff = NewNr - OldNr, % This is valid handling of the input
+ Msg = string:join(["Reason: ", Reason], ","), % This is valid handling of the input
+ case Diff > 0 of
+ true -> put(mine, {NewNr, Msg}),
+ {ok, NewNr}; % This is a valid return.
+ false -> fail % This is a valid return.
+ end.
+
+common_infrastructure( 1) -> 'ok';
+common_infrastructure(42) -> 'fail'.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl
new file mode 100644
index 0000000000..430494c48c
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/sample_behaviour/sample_callback_wrong.erl
@@ -0,0 +1,26 @@
+-module(sample_callback_wrong).
+
+%% This attribute uses the american spelling of 'behaviour'.
+-behavior(sample_behaviour).
+
+-export([
+% sample_callback_1/0,
+ sample_callback_2/0,
+ sample_callback_3/0,
+ sample_callback_4/1,
+ sample_callback_5/1,
+ sample_callback_6/3
+ ]).
+
+% sample_callback_1() -> 41. % We can't really break this contract so: missing!
+sample_callback_2() -> 42. % This is not an atom().
+sample_callback_3() -> fair. % This is probably a typo.
+sample_callback_4(_) -> % We cannot break the input.
+ fail. % We can definitely return a wrong value however. :)
+sample_callback_5(Input) -> % Input is treated as an atom, result is a list.
+ atom_to_list(Input). % Both violate the contract.
+sample_callback_6(OldNr, NewNr, Reason) ->
+ Diff = NewNr - OldNr, % This is valid handling of the input
+ %% Reason should have been treated as a string.
+ Msg = string:join(["Reason: ", atom_to_list(Reason)], ","),
+ {okk, NewNr}. %% This, too, is a typo.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/exact_adt.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/exact_adt.erl
new file mode 100644
index 0000000000..7103847ae7
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/exact_adt.erl
@@ -0,0 +1,17 @@
+-module(exact_adt).
+
+-export([exact_adt_set_type/1, exact_adt_set_type2/1]).
+
+-export_type([exact_adt/0]).
+
+-record(exact_adt, {}).
+
+-opaque exact_adt() :: #exact_adt{}.
+
+-spec exact_adt_set_type(_) -> exact_adt().
+
+exact_adt_set_type(G) -> G.
+
+-spec exact_adt_set_type2(exact_adt()) -> exact_adt().
+
+exact_adt_set_type2(G) -> G.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/exact_api.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/exact_api.erl
new file mode 100644
index 0000000000..597460ce77
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/exact_api.erl
@@ -0,0 +1,60 @@
+-module(exact_api).
+
+-export([new/0, exact_api_test/1, exact_api_new/1,
+ exact_adt_test/1, exact_adt_new/1]).
+
+-export_type([exact_api/0]).
+
+-record(digraph, {vtab = notable :: ets:tab(),
+ etab = notable :: ets:tab(),
+ ntab = notable :: ets:tab(),
+ cyclic = true :: boolean()}).
+
+-spec new() -> digraph:graph().
+
+new() ->
+ A = #digraph{},
+ set_type(A), % does not have an opaque term as 1st argument
+ A.
+
+-spec set_type(digraph:graph()) -> true.
+
+set_type(G) ->
+ digraph:delete(G).
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+%%% The derived spec of exact_api_new() is
+%%% -spec exact_api_new(exact_api:exact_api()) -> exact_api:exact_api().
+%%% This won't happen unless dialyzer_typesig uses
+%%% t_is_exactly_equal() rather than t_is_equal().
+%%% [As of R17B the latter considers two types equal if nothing but
+%%% their ?opaque tags differ.]
+
+-record(exact_api, {}).
+
+-opaque exact_api() :: #exact_api{}.
+
+exact_api_test(X) ->
+ #exact_api{} = exact_api_set_type(X). % OK
+
+exact_api_new(A) ->
+ A = #exact_api{},
+ _ = exact_api_set_type(A), % OK (the opaque type is local)
+ A.
+
+-spec exact_api_set_type(exact_api()) -> exact_api().
+
+exact_api_set_type(#exact_api{}=E) -> E.
+
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+
+-record(exact_adt, {}).
+
+exact_adt_test(X) ->
+ #exact_adt{} = exact_adt:exact_adt_set_type(X). % breaks the opacity
+
+exact_adt_new(A) ->
+ A = #exact_adt{},
+ _ = exact_adt:exact_adt_set_type2(A), % does not have an opaque term as 1st argument
+ A.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/is_rec.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/is_rec.erl
new file mode 100644
index 0000000000..b906431b44
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/is_rec.erl
@@ -0,0 +1,65 @@
+-module(is_rec).
+
+-export([ri1/0, ri11/0, ri13/0, ri14/0, ri2/0, ri3/0, ri4/0, ri5/0,
+ ri6/0, ri7/0, ri8/0]).
+
+-record(r, {f1 :: integer()}).
+
+ri1() ->
+ A = simple1_adt:d1(),
+ is_record(A, r). % opaque term 1
+
+ri11() ->
+ A = simple1_adt:d1(),
+ I = '1-3'(),
+ is_record(A, r, I). % opaque term 1
+
+ri13() ->
+ A = simple1_adt:d1(),
+ if is_record(A, r) -> true end. % breaks the opacity
+
+ri14() ->
+ A = simple1_adt:d1(),
+ if is_record({A, 1}, r) -> true end. % breaks the opacity
+
+-type '1-3-t'() :: 1..3.
+
+-spec '1-3'() -> '1-3-t'().
+
+'1-3'() ->
+ random:uniform(3).
+
+
+-spec 'Atom'() -> atom().
+
+'Atom'() ->
+ a.
+
+ri2() ->
+ A = simple1_adt:d1(),
+ R = 'Atom'(),
+ is_record(A, R). % opaque term 1
+
+ri3() ->
+ A = simple1_adt:d1(),
+ is_record(A, A, 1). % opaque term 2
+
+ri4() ->
+ A = simple1_adt:d1(),
+ is_record(A, hipp:hopp(), 1). % opaque term 1
+
+ri5() ->
+ A = simple1_adt:d1(),
+ is_record(A, A, hipp:hopp()). % opaque term 2
+
+ri6() ->
+ A = simple1_adt:d1(),
+ if is_record(A, r) -> true end. % breaks opacity
+
+ri7() ->
+ A = simple1_adt:d1(),
+ if is_record({r, A}, r) -> true end. % A violates #r{}
+
+ri8() ->
+ A = simple1_adt:d1(),
+ is_record({A, 1}, r). % opaque term 1
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/rec_adt.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/rec_adt.erl
new file mode 100644
index 0000000000..ff80d6e99b
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/rec_adt.erl
@@ -0,0 +1,28 @@
+-module(rec_adt).
+
+-export([f/0, r1/0]).
+
+-export_type([r1/0]).
+
+-export_type([f/0, op_t/0, a/0]).
+
+-opaque a() :: a | b.
+
+-record(r1,
+ {f1 :: a()}).
+
+-opaque r1() :: #r1{}.
+
+-opaque f() :: fun((_) -> _).
+
+-opaque op_t() :: integer().
+
+-spec f() -> f().
+
+f() ->
+ fun(_) -> 3 end.
+
+-spec r1() -> r1().
+
+r1() ->
+ #r1{f1 = a}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/rec_api.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/rec_api.erl
new file mode 100644
index 0000000000..59b9e0fec4
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/rec_api.erl
@@ -0,0 +1,123 @@
+-module(rec_api).
+
+-export([t1/0, t2/0, t3/0, adt_t1/0, adt_t1/1, adt_r1/0,
+ t/1, t_adt/0, r/0, r_adt/0, u1/0, u2/0, u3/0, v1/0, v2/0, v3/0]).
+
+-export_type([{a,0},{r1,0}, r2/0, r3/0]).
+
+-export_type([f/0, op_t/0, r/0, tup/0]).
+
+-opaque a() :: a | b.
+
+-record(r1,
+ {f1 :: a()}).
+
+-opaque r1() :: #r1{}.
+
+t1() ->
+ A = #r1{f1 = a},
+ {r1, a} = A.
+
+t2() ->
+ A = {r1, 10},
+ {r1, 10} = A,
+ A = #r1{f1 = 10}, % violates the type of field f1
+ #r1{f1 = 10} = A.
+
+t3() ->
+ A = {r1, 10},
+ #r1{f1 = 10} = A. % violates the type of #r1{}
+
+adt_t1() ->
+ R = rec_adt:r1(),
+ {r1, a} = R. % breaks the opacity
+
+-spec adt_t1(rec_adt:r1()) -> rec_adt:r1(). % invalid type spec
+
+adt_t1(R) ->
+ {r1, a} = R.
+
+-spec adt_r1() -> rec_adt:r1(). % invalid type spec
+
+adt_r1() ->
+ #r1{f1 = a}.
+
+-opaque f() :: fun((_) -> _).
+
+-opaque op_t() :: integer().
+
+-spec t(f()) -> _.
+
+t(A) ->
+ T = term(),
+ %% 3(T), % cannot test this: dialyzer_dep deliberately crashes
+ A(T).
+
+-spec term() -> op_t().
+
+term() ->
+ 3.
+
+t_adt() ->
+ A = rec_adt:f(),
+ T = term(),
+ A(T).
+
+-record(r, {f = fun(_) -> 3 end :: f(), o = 1 :: op_t()}).
+
+-opaque r() :: #r{}.
+
+-opaque tup() :: {'r', f(), op_t()}.
+
+-spec r() -> _.
+
+r() ->
+ {{r, f(), 2},
+ #r{f = f(), o = 2}}. % OK, f() is a local opaque type
+
+-spec f() -> f().
+
+f() ->
+ fun(_) -> 3 end.
+
+r_adt() ->
+ {{r, rec_adt:f(), 2},
+ #r{f = rec_adt:f(), o = 2}}. % breaks the opacity
+
+-record(r2, % like #r1{}, but with initial value
+ {f1 = a :: a()}).
+
+-opaque r2() :: #r2{}.
+
+u1() ->
+ A = #r2{f1 = a},
+ {r2, a} = A.
+
+u2() ->
+ A = {r2, 10},
+ {r2, 10} = A,
+ A = #r2{f1 = 10}, % violates the type of field f1
+ #r2{f1 = 10} = A.
+
+u3() ->
+ A = {r2, 10},
+ #r2{f1 = 10} = A. % violates the type of #r2{}
+
+-record(r3, % like #r1{}, but an opaque type
+ {f1 = queue:new():: queue:queue()}).
+
+-opaque r3() :: #r3{}.
+
+v1() ->
+ A = #r3{f1 = queue:new()},
+ {r3, a} = A. % breaks the opacity
+
+v2() ->
+ A = {r3, 10},
+ {r3, 10} = A,
+ A = #r3{f1 = 10}, % violates the type of field f1
+ #r3{f1 = 10} = A.
+
+v3() ->
+ A = {r3, 10},
+ #r3{f1 = 10} = A. % breaks the opacity
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_adt.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_adt.erl
new file mode 100644
index 0000000000..21a277c1e9
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_adt.erl
@@ -0,0 +1,138 @@
+-module(simple1_adt).
+
+-export([d1/0, d2/0, i/0, n1/0, n2/0, o1/0, o2/0,
+ c1/0, c2/0, bit1/0, a/0, i1/0, tuple/0,
+ b1/0, b2/0, ty_i1/0]).
+
+-export_type([o1/0, o2/0, d1/0, d2/0]).
+
+-export_type([i1/0, i2/0, di1/0, di2/0]).
+
+-export_type([ty_i1/0, c1/0, c2/0]).
+
+-export_type([b1/0, b2/0]).
+
+-export_type([bit1/0]).
+
+-export_type([tuple1/0, a/0, i/0]).
+
+%% Equal:
+
+-opaque o1() :: a | b | c.
+
+-opaque o2() :: a | b | c.
+
+%% Disjoint:
+
+-opaque d1() :: a | b | c.
+
+-opaque d2() :: d | e | f.
+
+%% One common element:
+
+-opaque c1() :: a | b | c.
+
+-opaque c2() :: c | e | f.
+
+%% Equal integer range:
+
+-opaque i1() :: 1 | 2.
+
+-opaque i2() :: 1 | 2.
+
+%% Disjoint integer range:
+
+-opaque di1() :: 1 | 2.
+
+-opaque di2() :: 3 | 4.
+
+
+-type ty_i1() :: 1 | 2.
+
+%% Boolean types
+
+-opaque b1() :: boolean().
+
+-opaque b2() :: boolean().
+
+%% Binary types
+
+-opaque bit1() :: binary().
+
+%% Tuple types
+
+-opaque tuple1() :: tuple().
+
+%% Atom type
+
+-opaque a() :: atom().
+
+-opaque i() :: integer().
+
+-spec d1() -> d1().
+
+d1() -> a.
+
+-spec d2() -> d2().
+
+d2() -> d.
+
+-spec i() -> i().
+
+i() ->
+ 1.
+
+-spec n1() -> o1().
+
+n1() -> a.
+
+-spec n2() -> o2().
+
+n2() -> a.
+
+-spec o1() -> o1().
+
+o1() -> a.
+
+-spec o2() -> o2().
+
+o2() -> a.
+
+-spec c1() -> c1().
+
+c1() -> a.
+
+-spec c2() -> c2().
+
+c2() -> e.
+
+-spec bit1() -> bit1().
+
+bit1() ->
+ <<"hej">>.
+
+-spec a() -> a().
+
+a() ->
+ e.
+
+-spec i1() -> i1().
+
+i1() -> 1.
+
+-spec tuple() -> tuple1().
+
+tuple() -> {1,2}.
+
+-spec b1() -> b1().
+
+b1() -> true.
+
+-spec b2() -> b2().
+
+b2() -> false.
+
+-spec ty_i1() -> ty_i1().
+
+ty_i1() ->
+ 1.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_api.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_api.erl
new file mode 100644
index 0000000000..d67aa913d8
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/simple1_api.erl
@@ -0,0 +1,571 @@
+-module(simple1_api).
+
+-export([t1/1, adt_t1/1, t2/1, adt_t2/1, tup/0, t3/0, t4/0, t5/0, t6/0, t7/0,
+ t8/0, adt_t3/0, adt_t4/0, adt_t7/0, adt_t8/0, adt_t5/0,
+ c1/2, c2/2, c2/0, c3/0, c4/0, tt1/0, tt2/0,
+ cmp1/0, cmp2/0, cmp3/0, cmp4/0,
+ ty_cmp1/0, ty_cmp2/0, ty_cmp3/0, ty_cmp4/0,
+ f1/0, f2/0, adt_f1/0, adt_f2/0, f3/0, f4/0, adt_f3/0, adt_f4/0,
+ adt_f4_a/0, adt_f4_b/0,
+ bool_t1/0, bool_t2/0, bool_t3/0, bool_t4/0, bool_t5/1, bool_t6/1,
+ bool_t7/0, bool_adt_t1/0, bool_adt_t2/0, bool_adt_t5/1,
+ bool_adt_t6/1, bool_t8/0, bool_adt_t8/2, bool_t9/0, bool_adt_t9/2,
+ bit_t1/0, bit_adt_t1/0, bit_t3/1, bit_adt_t2/0, bit_adt_t3/1,
+ bit_t5/1, bit_t4/1, bit_adt_t4/1, bit_t5/0, bit_adt_t5/0,
+ call_f/1, call_f_adt/1, call_m_adt/1, call_m/1, call_f_i/1,
+ call_m_i/1, call_m_adt_i/1, call_f_adt_i/1,
+ eq1/0, eq2/0, c5/0, c6/2, c7/2, c8/0]).
+
+%%% Equal opaque types
+
+-export_type([o1/0, o2/0]).
+
+-export_type([d1/0, d2/0]).
+
+-opaque o1() :: a | b | c.
+
+-opaque o2() :: a | b | c.
+
+-export_type([i1/0, i2/0, di1/0, di2/0]).
+
+-export_type([b1/0, b2/0]).
+
+-export_type([bit1/0]).
+
+-export_type([a/0, i/0]).
+
+%% The derived spec is
+%% -spec t1('a' | 'b') -> simple1_api:o1('a') | simple1_api:o2('a').
+%% but that is not tested...
+
+t1(a) ->
+ o1();
+t1(b) ->
+ o2().
+
+-spec o1() -> o1().
+
+o1() -> a.
+
+-spec o2() -> o2().
+
+o2() -> a.
+
+%% The derived spec is
+%% -spec adt_t1('a' | 'b') -> simple1_adt:o1('a') | simple1_adt:o2('a').
+%% but that is not tested...
+
+adt_t1(a) ->
+ simple1_adt:o1();
+adt_t1(b) ->
+ simple1_adt:o2().
+
+%%% Disjunct opaque types
+
+-opaque d1() :: a | b | c.
+
+-opaque d2() :: d | e | f.
+
+%% -spec t2('a' | 'b') -> simple1_api:d1('a') | simple1_api:d2('d').
+
+t2(a) ->
+ d1();
+t2(b) ->
+ d2().
+
+-spec d1() -> d1().
+
+d1() -> a.
+
+-spec d2() -> d2().
+
+d2() -> d.
+
+%% -spec adt_t2('a' | 'b') -> simple1_adt:d1('a') | simple1_adt:d2('d').
+
+adt_t2(a) ->
+ simple1_adt:d1();
+adt_t2(b) ->
+ simple1_adt:d2().
+
+-spec tup() -> simple1_adt:tuple1(). % invalid type spec
+
+tup() ->
+ {a, b}.
+
+%%% Matching equal opaque types with different names
+
+t3() ->
+ A = n1(),
+ B = n2(),
+ A = A, % OK, of course
+ A = B. % OK since o1() and o2() are local opaque types
+
+t4() ->
+ A = n1(),
+ B = n2(),
+ true = A =:= A, % OK, of course
+ A =:= B. % OK since o1() and o2() are local opaque types
+
+t5() ->
+ A = d1(),
+ B = d2(),
+ A =:= B. % can never evaluate to true
+
+t6() ->
+ A = d1(),
+ B = d2(),
+ A = B. % can never succeed
+
+t7() ->
+ A = d1(),
+ B = d2(),
+ A =/= B. % OK (always true?)
+
+t8() ->
+ A = d1(),
+ B = d2(),
+ A /= B. % OK (always true?)
+
+-spec n1() -> o1().
+
+n1() -> a.
+
+-spec n2() -> o2().
+
+n2() -> a.
+
+adt_t3() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ true = A =:= A, % OK.
+ A =:= B. % opaque test, not OK
+
+adt_t4() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ A = A, % OK
+ A = B. % opaque terms
+
+adt_t7() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ false = A =/= A, % OK
+ A =/= B. % opaque test, not OK
+
+adt_t8() ->
+ A = simple1_adt:n1(),
+ B = simple1_adt:n2(),
+ false = A /= A, % OK
+ A /= B. % opaque test, not OK
+
+adt_t5() ->
+ A = simple1_adt:c1(),
+ B = simple1_adt:c2(),
+ A =:= B. % opaque test, not OK
+
+%% Comparison in guard
+
+-spec c1(simple1_adt:d1(), simple1_adt:d2()) -> boolean().
+
+c1(A, B) when A =< B -> true. % succ type of A and B is any() (type spec is OK)
+
+-spec c2(simple1_adt:d1(), simple1_adt:d2()) -> boolean().
+
+c2(A, B) ->
+ if A =< B -> true end. % succ type of A and B is any() (type spec is OK)
+
+c2() ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:d2(),
+ if A =< B -> ok end. % opaque terms
+
+c3() ->
+ B = simple1_adt:d2(),
+ if a =< B -> ok end. % opaque term
+
+c4() ->
+ A = simple1_adt:d1(),
+ if A =< d -> ok end. % opaque term
+
+tt1() ->
+ A = o1(),
+ is_integer(A). % OK
+
+tt2() ->
+ A = simple1_adt:d1(),
+ is_integer(A). % breaks the opacity
+
+%% Comparison with integers
+
+-opaque i1() :: 1 | 2.
+
+-opaque i2() :: 1 | 2.
+
+-opaque di1() :: 1 | 2.
+
+-opaque di2() :: 3 | 4.
+
+-spec i1() -> i1().
+
+i1() -> 1.
+
+-type ty_i1() :: 1 | 2.
+
+-spec ty_i1() -> ty_i1().
+
+ty_i1() -> 1.
+
+cmp1() ->
+ A = i1(),
+ if A > 3 -> ok end. % can never succeed
+
+cmp2() ->
+ A = simple1_adt:i1(),
+ if A > 3 -> ok end. % opaque term
+
+cmp3() ->
+ A = i1(),
+ if A < 3 -> ok end.
+
+cmp4() ->
+ A = simple1_adt:i1(),
+ if A < 3 -> ok end. % opaque term
+
+%% -type
+
+ty_cmp1() ->
+ A = ty_i1(),
+ if A > 3 -> ok end. % can never succeed
+
+ty_cmp2() ->
+ A = simple1_adt:ty_i1(),
+ if A > 3 -> ok end. % can never succeed
+
+ty_cmp3() ->
+ A = ty_i1(),
+ if A < 3 -> ok end.
+
+ty_cmp4() ->
+ A = simple1_adt:ty_i1(),
+ if A < 3 -> ok end.
+
+%% is_function
+
+f1() ->
+ T = n1(),
+ if is_function(T) -> ok end. % can never succeed
+
+f2() ->
+ T = n1(),
+ is_function(T). % ok
+
+adt_f1() ->
+ T = simple1_adt:n1(),
+ if is_function(T) -> ok end. % breaks the opacity
+
+adt_f2() ->
+ T = simple1_adt:n1(),
+ is_function(T). % breaks the opacity
+
+f3() ->
+ A = i1(),
+ T = n1(),
+ if is_function(T, A) -> ok end. % can never succeed
+
+f4() ->
+ A = i1(),
+ T = n1(),
+ is_function(T, A). % ok
+
+adt_f3() ->
+ A = simple1_adt:i1(),
+ T = simple1_adt:n1(),
+ if is_function(T, A) -> ok end. % breaks the opacity
+
+adt_f4() ->
+ A = simple1_adt:i1(),
+ T = simple1_adt:n1(),
+ is_function(T, A). % breaks the opacity
+
+adt_f4_a() ->
+ A = simple1_adt:i1(),
+ T = n1(),
+ is_function(T, A). % opaque term
+
+
+adt_f4_b() ->
+ A = i1(),
+ T = simple1_adt:n1(),
+ is_function(T, A). % breaks the opacity
+
+%% A few Boolean examples
+
+bool_t1() ->
+ B = b2(),
+ if B -> ok end. % B =:= true can never succeed
+
+bool_t2() ->
+ A = b1(),
+ B = b2(),
+ if A and not B -> ok end.
+
+bool_t3() ->
+ A = b1(),
+ if not A -> ok end. % can never succeed
+
+bool_t4() ->
+ A = n1(),
+ if not ((A >= 1) and not (A < 1)) -> ok end. % can never succeed
+
+-spec bool_t5(i1()) -> integer().
+
+bool_t5(A) ->
+ if [not (A > 1)] =:=
+ [false]-> 1 end.
+
+-spec bool_t6(b1()) -> integer().
+
+bool_t6(A) ->
+ if [not A] =:=
+ [false]-> 1 end.
+
+-spec bool_t7() -> integer().
+
+bool_t7() ->
+ A = i1(),
+ if [not A] =:= % cannot succeed
+ [false]-> 1 end.
+
+bool_adt_t1() ->
+ B = simple1_adt:b2(),
+ if B -> ok end. % opaque term
+
+bool_adt_t2() ->
+ A = simple1_adt:b1(),
+ B = simple1_adt:b2(),
+ if A and not B -> ok end. % opaque term
+
+-spec bool_adt_t5(simple1_adt:i1()) -> integer().
+
+bool_adt_t5(A) ->
+ if [not (A > 1)] =:= % succ type of A is any() (type spec is OK)
+ [false]-> 1 end.
+
+-spec bool_adt_t6(simple1_adt:b1()) -> integer(). % invalid type spec
+
+bool_adt_t6(A) ->
+ if [not A] =:= % succ type of A is 'true'
+ [false]-> 1 end.
+
+-spec bool_t8() -> integer().
+
+bool_t8() ->
+ A = i1(),
+ if [A and A] =:= % cannot succeed
+ [false]-> 1 end.
+
+-spec bool_adt_t8(simple1_adt:b1(), simple1_adt:b2()) -> integer(). % invalid
+
+bool_adt_t8(A, B) ->
+ if [A and B] =:=
+ [false]-> 1 end.
+
+-spec bool_t9() -> integer().
+
+bool_t9() ->
+ A = i1(),
+ if [A or A] =:= % cannot succeed
+ [false]-> 1 end.
+
+-spec bool_adt_t9(simple1_adt:b1(), simple1_adt:b2()) -> integer(). % invalid
+
+bool_adt_t9(A, B) ->
+ if [A or B] =:=
+ [false]-> 1 end.
+
+-opaque b1() :: boolean().
+
+-opaque b2() :: boolean().
+
+-spec b1() -> b1().
+
+b1() -> true.
+
+-spec b2() -> b2().
+
+b2() -> false.
+
+%% Few (very few...) examples with bit syntax
+
+bit_t1() ->
+ A = i1(),
+ <<100:(A)>>.
+
+bit_adt_t1() ->
+ A = simple1_adt:i1(),
+ <<100:(A)>>. % breaks the opacity
+
+bit_t3(A) ->
+ B = i1(),
+ case none:none() of
+ <<A:B>> -> 1
+ end.
+
+bit_adt_t2() ->
+ A = simple1_adt:i1(),
+ case <<"hej">> of
+ <<_:A>> -> ok % breaks the opacity (but the message is strange)
+ end.
+
+
+bit_adt_t3(A) ->
+ B = simple1_adt:i1(),
+ case none:none() of
+ <<A: % breaks the opacity (the message is less than perfect)
+ B>> -> 1
+ end.
+
+bit_t5(A) ->
+ B = o1(),
+ case none:none() of % the type is any(); should fix that XXX
+ <<A:B>> -> 1 % can never match (local opaque type is OK)
+ end.
+
+-spec bit_t4(<<_:1>>) -> integer().
+
+bit_t4(A) ->
+ Sz = i1(),
+ case A of
+ <<_:Sz>> -> 1
+ end.
+
+-spec bit_adt_t4(<<_:1>>) -> integer().
+
+bit_adt_t4(A) ->
+ Sz = simple1_adt:i1(),
+ case A of
+ <<_:Sz>> -> 1 % breaks the opacity
+ end.
+
+bit_t5() ->
+ A = bit1(),
+ case A of
+ <<_/binary>> -> 1
+ end.
+
+bit_adt_t5() ->
+ A = simple1_adt:bit1(),
+ case A of
+ <<_/binary>> -> 1 % breaks the opacity
+ end.
+
+-opaque bit1() :: binary().
+
+-spec bit1() -> bit1().
+
+bit1() ->
+ <<"hej">>.
+
+%% Calls with variable module or function
+
+call_f(A) ->
+ A = a(),
+ foo:A(A).
+
+call_f_adt(A) ->
+ A = simple1_adt:a(),
+ foo:A(A). % breaks the opacity
+
+call_m(A) ->
+ A = a(),
+ A:foo(A).
+
+call_m_adt(A) ->
+ A = simple1_adt:a(),
+ A:foo(A). % breaks the opacity
+
+-opaque a() :: atom().
+
+-opaque i() :: integer().
+
+-spec a() -> a().
+
+a() ->
+ e.
+
+call_f_i(A) ->
+ A = i(),
+ foo:A(A). % A is not atom() but i()
+
+call_f_adt_i(A) ->
+ A = simple1_adt:i(),
+ foo:A(A). % A is not atom() but simple1_adt:i()
+
+call_m_i(A) ->
+ A = i(),
+ A:foo(A). % A is not atom() but i()
+
+call_m_adt_i(A) ->
+ A = simple1_adt:i(),
+ A:foo(A). % A is not atom() but simple1_adt:i()
+
+-spec eq1() -> integer().
+
+eq1() ->
+ A = simple1_adt:d2(),
+ B = simple1_adt:d1(),
+ if
+ A == B -> % opaque terms
+ 0;
+ A == A ->
+ 1;
+ A =:= A -> % compiler finds this one cannot match
+ 2;
+ true -> % compiler finds this one cannot match
+ 3
+ end.
+
+eq2() ->
+ A = simple1_adt:d1(),
+ if
+ {A} >= {A} ->
+ 1;
+ A >= 3 -> % opaque term
+ 2;
+ A == 3 -> % opaque term
+ 3;
+ A =:= 3 -> % opaque term
+ 4;
+ A == A ->
+ 5;
+ A =:= A -> % compiler finds this one cannot match
+ 6
+ end.
+
+c5() ->
+ A = simple1_adt:d1(),
+ A < 3. % opaque term
+
+c6(A, B) ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:d1(),
+ A =< B. % same type - no warning
+
+c7(A, B) ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:d2(),
+ A =< B. % opaque terms
+
+c8() ->
+ D = digraph:new(),
+ E = ets:new(foo, []),
+ if {D, a} > {D, E} -> true; % OK
+ {1.0, 2} > {{D}, {E}} -> true; % OK
+ {D, 3} > {D, E} -> true % opaque term 2
+ end.
+
+-spec i() -> i().
+
+i() ->
+ 1.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/simple/simple2_api.erl b/lib/dialyzer/test/indent_SUITE_data/src/simple/simple2_api.erl
new file mode 100644
index 0000000000..c86f6fd0b5
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/simple/simple2_api.erl
@@ -0,0 +1,125 @@
+-module(simple2_api).
+
+-export([c1/2, c2/0, c3/0, c4/1, c5/1, c6/0, c6_b/0, c7/0, c7_b/0,
+ c7_c/0, c8/0, c9/0, c10/0, c11/0, c12/0, c13/0, c14/0, c15/0,
+ c16/0, c17/0, c18/0, c19/0, c20/0, c21/0, c22/0, c23/0,
+ c24/0, c25/0, c26/0]).
+
+-spec c1(simple1_adt:d1(), simple1_adt:d2()) -> boolean().
+
+c1(A, B) ->
+ {A} =< {B}. % succ type of A and B is any()
+
+c2() ->
+ A = simple1_adt:d1(),
+ erlang:make_tuple(1, A). % ok
+
+c3() ->
+ A = simple1_adt:d1(),
+ setelement(1, {A}, A). % ok
+
+c4(_) ->
+ A = simple1_adt:d1(),
+ halt(A). % ok (BIF fails...)
+
+c5(_) ->
+ A = simple1_adt:d1(),
+ [A] -- [A]. % ok
+
+c6() ->
+ A = simple1_adt:d1(),
+ A ! foo. % opaque term
+
+c6_b() ->
+ A = simple1_adt:d1(),
+ erlang:send(A, foo). % opaque term
+
+c7() ->
+ A = simple1_adt:d1(),
+ foo ! A. % ok
+
+c7_b() ->
+ A = simple1_adt:d1(),
+ erlang:send(foo, A). % ok
+
+c7_c() ->
+ A = simple1_adt:d1(),
+ erlang:send(foo, A, []). % ok
+
+c8() ->
+ A = simple1_adt:d1(),
+ A < 3. % opaque term
+
+c9() ->
+ A = simple1_adt:d1(),
+ lists:keysearch(A, 1, []). % ok
+
+c10() ->
+ A = simple1_adt:d1(),
+ lists:keysearch(1, A, []). % opaque term 2
+
+c11() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, [A]). % ok
+
+c12() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, A). % opaque term 3
+
+c13() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, [{A,2}]). % ok
+
+c14() ->
+ A = simple1_adt:tuple(),
+ lists:keysearch(key, 1, [{2,A}]). % ok
+
+c15() ->
+ A = simple1_adt:d1(),
+ lists:keysearch(key, 1, [A]). % ok
+
+c16() ->
+ A = simple1_adt:tuple(),
+ erlang:send(foo, A). % ok
+
+c17() ->
+ A = simple1_adt:tuple(),
+ lists:reverse([A]). % ok
+
+c18() ->
+ A = simple1_adt:tuple(),
+ lists:keyreplace(a, 1, [A], {1,2}). % ok
+
+c19() ->
+ A = simple1_adt:tuple(),
+ %% Problem. The spec says argument 4 is a tuple(). Fix that!
+ lists:keyreplace(a, 1, [{1,2}], A). % opaque term 4
+
+c20() ->
+ A = simple1_adt:tuple(),
+ lists:flatten(A). % opaque term 1
+
+c21() ->
+ A = simple1_adt:tuple(),
+ lists:flatten([[{A}]]). % ok
+
+c22() ->
+ A = simple1_adt:tuple(),
+ lists:flatten([[A]]). % ok
+
+c23() ->
+ A = simple1_adt:tuple(),
+ lists:flatten([A]). % ok
+
+c24() ->
+ A = simple1_adt:tuple(),
+ lists:flatten({A}). % will never return
+
+c25() ->
+ A = simple1_adt:d1(),
+ B = simple1_adt:tuple(),
+ if {A,3} > {A,B} -> true end. % opaque 2nd argument
+
+c26() ->
+ B = simple1_adt:tuple(),
+ tuple_to_list(B). % opaque term 1
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/suppress_request.erl b/lib/dialyzer/test/indent_SUITE_data/src/suppress_request.erl
new file mode 100644
index 0000000000..c4275fa110
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/suppress_request.erl
@@ -0,0 +1,50 @@
+-module(suppress_request).
+
+-export([test1/1, test1_b/1, test2/0, test2_b/0,
+ test3/0, test3_b/0, test4/0, test4_b/0]).
+
+-dialyzer({[specdiffs], test1/1}).
+-spec test1(a | b) -> ok. % spec is subtype
+test1(A) ->
+ ok = test1_1(A).
+
+-spec test1_b(a | b) -> ok. % spec is subtype (suppressed by default)
+test1_b(A) ->
+ ok = test1_1(A).
+
+-spec test1_1(a | b | c) -> ok.
+test1_1(_) ->
+ ok.
+
+-dialyzer(unmatched_returns).
+test2() ->
+ tuple(), % unmatched
+ ok.
+
+test2_b() ->
+ tuple(), % unmatched
+ ok.
+
+-dialyzer({[no_return, no_match], [test3/0]}).
+test3() -> % no local return (suppressed)
+ A = fun(_) ->
+ 1
+ end,
+ A = 2. % can never succeed (suppressed)
+
+test3_b() -> % no local return (requested by default)
+ A = fun(_) ->
+ 1
+ end,
+ A = 2. % can never succeed (requested by default)
+
+-dialyzer(no_improper_lists).
+test4() ->
+ [1 | 2]. % improper list (suppressed)
+
+-dialyzer({no_improper_lists, test4_b/0}).
+test4_b() ->
+ [1 | 2]. % improper list (suppressed)
+
+tuple() ->
+ {a, b}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/trec.erl b/lib/dialyzer/test/indent_SUITE_data/src/trec.erl
new file mode 100644
index 0000000000..516358f7c6
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/trec.erl
@@ -0,0 +1,39 @@
+%%
+%% The current treatment of typed records leaves much to be desired.
+%% These are not made up examples; I have cases like that the branch
+%% of the HiPE compiler with types in records. I get very confusing
+%% warnings which require a lot of effort to find their cause and why
+%% a function has no local return.
+%%
+-module(trec).
+-export([test/0, mk_foo_exp/2]).
+
+-record(foo, {a :: integer() | 'undefined', b :: [atom()]}).
+
+%%
+%% For these functions we currently get the following warnings:
+%% 1. Function test/0 has no local return
+%% 2. The call trec:mk_foo_loc(42,any()) will fail since it differs
+%% in argument position 1 from the success typing arguments:
+%% ('undefined',atom())
+%% 3. Function mk_foo_loc/2 has no local return
+%%
+%% Arguably, the second warning is not what most users have in mind when
+%% they wrote the type declarations in the 'foo' record, so no doubt
+%% they'll find it confusing. But note that it is also quite confusing!
+%% Many users may be wondering: How come there is a success typing for a
+%% function that has no local return? Running typer on this module
+%% reveals a success typing for this function that is interesting indeed.
+%%
+test() ->
+ mk_foo_loc(42, some_mod:some_function()).
+
+mk_foo_loc(A, B) ->
+ #foo{a = A, b = [A,B]}.
+
+%%
+%% For this function we used to get a "has no local return" warning
+%% but we got no reason. This has now been fixed.
+%%
+mk_foo_exp(A, B) when is_integer(A) ->
+ #foo{a = A, b = [A,B]}.
diff --git a/lib/dialyzer/test/indent_SUITE_data/src/whereis_control_flow1.erl b/lib/dialyzer/test/indent_SUITE_data/src/whereis_control_flow1.erl
new file mode 100644
index 0000000000..e65f6c3e23
--- /dev/null
+++ b/lib/dialyzer/test/indent_SUITE_data/src/whereis_control_flow1.erl
@@ -0,0 +1,17 @@
+%% This tests the presence of possible races due to a whereis/register
+%% combination. It takes into account control flow that might exist.
+
+-module(whereis_control_flow1).
+-export([start/2]).
+
+start(AnAtom, Fun) ->
+ case whereis(AnAtom) of
+ undefined ->
+ Pid = spawn(Fun),
+ case Pid =:= self() of
+ true -> ok;
+ false -> register(AnAtom, Pid)
+ end;
+ P when is_pid(P) ->
+ ok
+ end.
diff --git a/lib/dialyzer/test/map_SUITE_data/dialyzer_options b/lib/dialyzer/test/map_SUITE_data/dialyzer_options
index 02425c33f2..1ddeb02c27 100644
--- a/lib/dialyzer/test/map_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/map_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, []}.
+{dialyzer_options, [{indent_opt, false}]}.
{time_limit, 30}.
diff --git a/lib/dialyzer/test/map_SUITE_data/results/contract b/lib/dialyzer/test/map_SUITE_data/results/contract
index 0f6e1d0c65..620ec8e6b1 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/contract
+++ b/lib/dialyzer/test/map_SUITE_data/results/contract
@@ -1,7 +1,7 @@
contract.erl:10: Function t2/0 has no local return
-contract.erl:10: The call missing:f(#{'a':=1, 'c':=4}) breaks the contract (#{'a':=1,'b'=>2,'c'=>3}) -> 'ok'
+contract.erl:10: The call missing:f(#{'a'=>1, 'c'=>4}) breaks the contract (#{'a':=1,'b'=>2,'c'=>3}) -> 'ok'
contract.erl:12: Function t3/0 has no local return
-contract.erl:12: The call missing:f(#{'a':=1, 'b':=2, 'e':=3}) breaks the contract (#{'a':=1,'b'=>2,'c'=>3}) -> 'ok'
+contract.erl:12: The call missing:f(#{'a'=>1, 'b'=>2, 'e'=>3}) breaks the contract (#{'a':=1,'b'=>2,'c'=>3}) -> 'ok'
contract.erl:8: Function t1/0 has no local return
-contract.erl:8: The call missing:f(#{'b':=2}) breaks the contract (#{'a':=1,'b'=>2,'c'=>3}) -> 'ok'
+contract.erl:8: The call missing:f(#{'b'=>2}) breaks the contract (#{'a':=1,'b'=>2,'c'=>3}) -> 'ok'
diff --git a/lib/dialyzer/test/map_SUITE_data/results/guard_update b/lib/dialyzer/test/map_SUITE_data/results/guard_update
index 98df23907f..f6de2158fc 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/guard_update
+++ b/lib/dialyzer/test/map_SUITE_data/results/guard_update
@@ -1,5 +1,5 @@
guard_update.erl:5: Function t/0 has no local return
-guard_update.erl:6: The call guard_update:f(#{'a':=2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, _=>_})
+guard_update.erl:6: The call guard_update:f(#{'a'=>2}) will never return since it differs in the 1st argument from the success typing arguments: (#{'b':=_, _=>_})
guard_update.erl:8: Clause guard cannot succeed. The variable M was matched against the type #{'a':=2}
guard_update.erl:8: Function f/1 has no local return
diff --git a/lib/dialyzer/test/map_SUITE_data/results/map_galore b/lib/dialyzer/test/map_SUITE_data/results/map_galore
index 9a140de255..6d1f4ff2bf 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/map_galore
+++ b/lib/dialyzer/test/map_SUITE_data/results/map_galore
@@ -2,11 +2,11 @@
map_galore.erl:1000: A key of type 42 cannot exist in a map of type #{1:='a', 2:='b', 4:='d', 5:='e', float()=>'c' | 'v'}
map_galore.erl:1080: A key of type 'nonexisting' cannot exist in a map of type #{10:='a0', 11:='a1', 12:='a2', 13:='a3', 14:='a4', 15:='a5', 16:='a6', 17:='a7', 18:='a8', 19:='a9', 20:='b0', 21:='b1', 22:='b2', 23:='b3', 24:='b4', 25:='b5', 26:='b6', 27:='b7', 28:='b8', 29:='b9', 30:=[48 | 99,...], 31:=[49 | 99,...], 32:=[50 | 99,...], 33:=[51 | 99,...], 34:=[52 | 99,...], 35:=[53 | 99,...], 36:=[54 | 99,...], 37:=[55 | 99,...], 38:=[56 | 99,...], 39:=[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...],...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[54 | 99,...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...],...]}=>[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 100 | 101,...]}=>atom() | [1..255,...]}
map_galore.erl:1082: A key of type 42 cannot exist in a map of type #{10:='a0', 11:='a1', 12:='a2', 13:='a3', 14:='a4', 15:='a5', 16:='a6', 17:='a7', 18:='a8', 19:='a9', 20:='b0', 21:='b1', 22:='b2', 23:='b3', 24:='b4', 25:='b5', 26:='b6', 27:='b7', 28:='b8', 29:='b9', 30:=[48 | 99,...], 31:=[49 | 99,...], 32:=[50 | 99,...], 33:=[51 | 99,...], 34:=[52 | 99,...], 35:=[53 | 99,...], 36:=[54 | 99,...], 37:=[55 | 99,...], 38:=[56 | 99,...], 39:=[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...],...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[54 | 99,...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...],...]}=>[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 100 | 101,...]}=>atom() | [1..255,...]}
-map_galore.erl:1140: The call map_galore:map_guard_sequence_1(#{'seq':=6, 'val':=[101,...]}) will never return since it differs in the 1st argument from the success typing arguments: (#{'seq':=1 | 2 | 3 | 4 | 5, 'val':=[97 | 98 | 99 | 100 | 101,...], 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[any(),...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[any(),...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[any(),...], 31=>[any(),...], 32=>[any(),...], 33=>[any(),...], 34=>[any(),...], 35=>[any(),...], 36=>[any(),...], 37=>[any(),...], 38=>[any(),...], 39=>[any(),...], <<_:16>> | [any(),...] | {_}=>[any(),...]}=>atom() | [1..255,...]})
-map_galore.erl:1141: The call map_galore:map_guard_sequence_2(#{'b':=5}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':='gg' | 'kk' | 'sc' | 3 | 4, 'b'=>'other' | 3 | 4 | 5, 'c'=>'sc2', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[any(),...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[any(),...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[any(),...], 31=>[any(),...], 32=>[any(),...], 33=>[any(),...], 34=>[any(),...], 35=>[any(),...], 36=>[any(),...], 37=>[any(),...], 38=>[any(),...], 39=>[any(),...], <<_:16>> | [any(),...] | {_}=>[any(),...]}=>atom() | [1..255,...]})
+map_galore.erl:1140: The call map_galore:map_guard_sequence_1(#{'seq'=>6, 'val'=>"e"}) will never return since it differs in the 1st argument from the success typing arguments: (#{'seq':=1 | 2 | 3 | 4 | 5, 'val':=[97 | 98 | 99 | 100 | 101,...], 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[any(),...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[any(),...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[any(),...], 31=>[any(),...], 32=>[any(),...], 33=>[any(),...], 34=>[any(),...], 35=>[any(),...], 36=>[any(),...], 37=>[any(),...], 38=>[any(),...], 39=>[any(),...], <<_:16>> | [any(),...] | {_}=>[any(),...]}=>atom() | [1..255,...]})
+map_galore.erl:1141: The call map_galore:map_guard_sequence_2(#{'b'=>5}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':='gg' | 'kk' | 'sc' | 3 | 4, 'b'=>'other' | 3 | 4 | 5, 'c'=>'sc2', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[any(),...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[any(),...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[any(),...], 31=>[any(),...], 32=>[any(),...], 33=>[any(),...], 34=>[any(),...], 35=>[any(),...], 36=>[any(),...], 37=>[any(),...], 38=>[any(),...], 39=>[any(),...], <<_:16>> | [any(),...] | {_}=>[any(),...]}=>atom() | [1..255,...]})
map_galore.erl:1209: The call map_galore:map_guard_sequence_1(#{'seq':=6, 'val':=[101,...], 10:='a0', 11:='a1', 12:='a2', 13:='a3', 14:='a4', 15:='a5', 16:='a6', 17:='a7', 18:='a8', 19:='a9', 20:='b0', 21:='b1', 22:='b2', 23:='b3', 24:='b4', 25:='b5', 26:='b6', 27:='b7', 28:='b8', 29:='b9', 30:=[48 | 99,...], 31:=[49 | 99,...], 32:=[50 | 99,...], 33:=[51 | 99,...], 34:=[52 | 99,...], 35:=[53 | 99,...], 36:=[54 | 99,...], 37:=[55 | 99,...], 38:=[56 | 99,...], 39:=[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | 3,...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[54 | 99,...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...],...]}=>[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 100 | 101,...]}=>atom() | [1..255,...]}) will never return since it differs in the 1st argument from the success typing arguments: (#{'seq':=1 | 2 | 3 | 4 | 5, 'val':=[97 | 98 | 99 | 100 | 101,...], 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[any(),...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[any(),...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[any(),...], 31=>[any(),...], 32=>[any(),...], 33=>[any(),...], 34=>[any(),...], 35=>[any(),...], 36=>[any(),...], 37=>[any(),...], 38=>[any(),...], 39=>[any(),...], <<_:16>> | [any(),...] | {_}=>[any(),...]}=>atom() | [1..255,...]})
map_galore.erl:1210: The call map_galore:map_guard_sequence_2(#{'b':=5, 10:='a0', 11:='a1', 12:='a2', 13:='a3', 14:='a4', 15:='a5', 16:='a6', 17:='a7', 18:='a8', 19:='a9', 20:='b0', 21:='b1', 22:='b2', 23:='b3', 24:='b4', 25:='b5', 26:='b6', 27:='b7', 28:='b8', 29:='b9', 30:=[48 | 99,...], 31:=[49 | 99,...], 32:=[50 | 99,...], 33:=[51 | 99,...], 34:=[52 | 99,...], 35:=[53 | 99,...], 36:=[54 | 99,...], 37:=[55 | 99,...], 38:=[56 | 99,...], 39:=[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | 3,...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[54 | 99,...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | {[[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...],...]}=>[48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 100 | 101,...]}=>atom() | [1..255,...]}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':='gg' | 'kk' | 'sc' | 3 | 4, 'b'=>'other' | 3 | 4 | 5, 'c'=>'sc2', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[48 | 99,...], 31=>[49 | 99,...], 32=>[50 | 99,...], 33=>[51 | 99,...], 34=>[52 | 99,...], 35=>[53 | 99,...], 36=>[54 | 99,...], 37=>[55 | 99,...], 38=>[56 | 99,...], 39=>[57 | 99,...], <<_:16>> | [48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57,...] | float() | {[any(),...]} | #{'k16'=>'a6', 'k26'=>'b6', 'k36'=>[any(),...], 'map'=>'key', 'one'=>'small', 'second'=>'small', 'third'=>'small', 10=>'a0', 11=>'a1', 12=>'a2', 13=>'a3', 14=>'a4', 15=>'a5', 16=>'a6', 17=>'a7', 18=>'a8', 19=>'a9', 20=>'b0', 21=>'b1', 22=>'b2', 23=>'b3', 24=>'b4', 25=>'b5', 26=>'b6', 27=>'b7', 28=>'b8', 29=>'b9', 30=>[any(),...], 31=>[any(),...], 32=>[any(),...], 33=>[any(),...], 34=>[any(),...], 35=>[any(),...], 36=>[any(),...], 37=>[any(),...], 38=>[any(),...], 39=>[any(),...], <<_:16>> | [any(),...] | {_}=>[any(),...]}=>atom() | [1..255,...]})
-map_galore.erl:1418: Fun application with arguments (#{'s':='none', 'v':='none'}) will never return since it differs in the 1st argument from the success typing arguments: (#{'s':='l' | 't' | 'v', 'v':='none' | <<_:16>> | [<<_:16>>,...] | {<<_:16>>,<<_:16>>}})
+map_galore.erl:1418: Fun application with arguments (#{'s'=>'none', 'v'=>'none'}) will never return since it differs in the 1st argument from the success typing arguments: (#{'s':='l' | 't' | 'v', 'v':='none' | <<_:16>> | [<<_:16>>,...] | {<<_:16>>,<<_:16>>}})
map_galore.erl:1491: The test #{} =:= #{'a':=1} can never evaluate to 'true'
map_galore.erl:1492: The test #{'a':=1} =:= #{} can never evaluate to 'true'
map_galore.erl:1495: The test #{'a':=1} =:= #{'a':=2} can never evaluate to 'true'
@@ -15,13 +15,13 @@ map_galore.erl:1497: The test #{'a':=2, 'b':=1} =:= #{'a':=1, 'b':=3} can never
map_galore.erl:1498: The test #{'a':=1, 'b':=1} =:= #{'a':=1, 'b':=3} can never evaluate to 'true'
map_galore.erl:1762: The call maps:get({1, 1},#{{1,float()}=>[101 | 108 | 112 | 116 | 117,...]}) will never return since the success typing arguments are (any(),map())
map_galore.erl:1763: The call maps:get('a',#{}) will never return since the success typing arguments are (any(),map())
-map_galore.erl:1765: The call maps:get('a',#{'b':=1, 'c':=2}) will never return since the success typing arguments are (any(),map())
+map_galore.erl:1765: The call maps:get('a',#{'b'=>1, 'c'=>2}) will never return since the success typing arguments are (any(),map())
map_galore.erl:186: The pattern #{'x':=2} can never match the type #{'x':=3}
map_galore.erl:187: The pattern #{'x':=3} can never match the type {'a','b','c'}
map_galore.erl:188: The pattern #{'x':=3} can never match the type #{'y':=3}
map_galore.erl:189: The pattern #{'x':=3} can never match the type #{'x':=[101 | 104 | 114 | 116,...]}
map_galore.erl:2280: Cons will produce an improper list since its 2nd argument is {'b','a'}
-map_galore.erl:2280: The call maps:from_list(nonempty_improper_list({'a','b'},{'b','a'})) will never return since it differs in the 1st argument from the success typing arguments: ([{_,_}])
+map_galore.erl:2280: The call maps:from_list([{'a', 'b'} | {'b', 'a'}]) will never return since it differs in the 1st argument from the success typing arguments: ([{_,_}])
map_galore.erl:2281: The call maps:from_list('a') will never return since it differs in the 1st argument from the success typing arguments: ([{_,_}])
map_galore.erl:2282: The call maps:from_list(42) will never return since it differs in the 1st argument from the success typing arguments: ([{_,_}])
map_galore.erl:997: A key of type 'nonexisting' cannot exist in a map of type #{}
diff --git a/lib/dialyzer/test/map_SUITE_data/results/typesig b/lib/dialyzer/test/map_SUITE_data/results/typesig
index fb2f851a7d..6a79fe35f1 100644
--- a/lib/dialyzer/test/map_SUITE_data/results/typesig
+++ b/lib/dialyzer/test/map_SUITE_data/results/typesig
@@ -1,5 +1,5 @@
typesig.erl:5: Function t1/0 has no local return
-typesig.erl:5: The call typesig:test(#{'a':=1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_})
+typesig.erl:5: The call typesig:test(#{'a'=>1}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_})
typesig.erl:6: Function t2/0 has no local return
typesig.erl:6: The call typesig:test(#{'a':={'b'}}) will never return since it differs in the 1st argument from the success typing arguments: (#{'a':={number()}, _=>_})
diff --git a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options
index cb301ff6a1..8551a47541 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/opaque_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{warnings, [no_unused, no_return]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [no_unused, no_return]}]}.
{time_limit, 40}.
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/dict b/lib/dialyzer/test/opaque_SUITE_data/results/dict
index 3f8242c72d..bda9bfc09f 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/dict
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/dict
@@ -10,6 +10,6 @@ dict_use.erl:64: Guard test length(D::dict:dict(_,_)) breaks the opacity of its
dict_use.erl:65: Guard test is_atom(D::dict:dict(_,_)) breaks the opacity of its argument
dict_use.erl:66: Guard test is_list(D::dict:dict(_,_)) breaks the opacity of its argument
dict_use.erl:70: The type test is_list(dict:dict(_,_)) breaks the opacity of the term dict:dict(_,_)
-dict_use.erl:73: The call dict:fetch('foo',[1 | 2 | 3,...]) does not have an opaque term of type dict:dict(_,_) as 2nd argument
-dict_use.erl:76: The call dict:merge(Fun::any(),42,[1 | 2,...]) does not have opaque terms as 2nd and 3rd arguments
-dict_use.erl:79: The call dict:store(42,'elli',{'dict',0,16,16,8,80,48,{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]},{{[],[],[],[],[],[],[],[],[],[],[],[],[],[],[],[]}}}) does not have an opaque term of type dict:dict(_,_) as 3rd argument
+dict_use.erl:73: The call dict:fetch('foo',[1, 2, 3]) does not have an opaque term of type dict:dict(_,_) as 2nd argument
+dict_use.erl:76: The call dict:merge(Fun::any(),42,[1, 2]) does not have opaque terms as 2nd and 3rd arguments
+dict_use.erl:79: The call dict:store(42,'elli',{'dict', 0, 16, 16, 8, 80, 48, {[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []}, {{[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []}}}) does not have an opaque term of type dict:dict(_,_) as 3rd argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/queue b/lib/dialyzer/test/opaque_SUITE_data/results/queue
index 9822b7168f..8f9373e8e4 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/queue
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/queue
@@ -1,11 +1,11 @@
-queue_use.erl:18: The call queue:is_empty({[],[]}) does not have an opaque term of type queue:queue(_) as 1st argument
+queue_use.erl:18: The call queue:is_empty({[], []}) does not have an opaque term of type queue:queue(_) as 1st argument
queue_use.erl:22: The call queue:in(42,Q0::{[],[]}) does not have an opaque term of type queue:queue(_) as 2nd argument
queue_use.erl:27: The attempt to match a term of type queue:queue(_) against the pattern {"*", Q2} breaks the opacity of the term
queue_use.erl:33: Attempt to test for equality between a term of type {[42,...],[]} and a term of opaque type queue:queue(_)
queue_use.erl:36: The attempt to match a term of type queue:queue(_) against the pattern {F, _R} breaks the opacity of the term
-queue_use.erl:40: The call queue:out({[42,...],[]}) does not have an opaque term of type queue:queue(_) as 1st argument
+queue_use.erl:40: The call queue:out({"*", []}) does not have an opaque term of type queue:queue(_) as 1st argument
queue_use.erl:51: The call queue_use:is_in_queue(E::42,DB::#db{p::[],q::queue:queue(_)}) contains an opaque term as 2nd argument when terms of different types are expected in these positions
queue_use.erl:56: The attempt to match a term of type #db{p::[],q::queue:queue(_)} against the pattern {'db', _, {L1, L2}} breaks the opacity of queue:queue(_)
-queue_use.erl:62: The call queue_use:tuple_queue({42,'gazonk'}) does not have a term of type {_,queue:queue(_)} (with opaque subterms) as 1st argument
+queue_use.erl:62: The call queue_use:tuple_queue({42, 'gazonk'}) does not have a term of type {_,queue:queue(_)} (with opaque subterms) as 1st argument
queue_use.erl:65: The call queue:in(F::42,Q::'gazonk') does not have an opaque term of type queue:queue(_) as 2nd argument
diff --git a/lib/dialyzer/test/opaque_SUITE_data/results/simple b/lib/dialyzer/test/opaque_SUITE_data/results/simple
index 0e1bb934e9..5d13b56970 100644
--- a/lib/dialyzer/test/opaque_SUITE_data/results/simple
+++ b/lib/dialyzer/test/opaque_SUITE_data/results/simple
@@ -51,7 +51,7 @@ simple1_api.erl:294: The call erlang:is_function(T::simple1_api:o1(),A::simple1_
simple1_api.erl:300: The type test is_function(T::simple1_adt:o1(),A::simple1_api:i1()) breaks the opacity of the term T::simple1_adt:o1()
simple1_api.erl:306: Guard test B::simple1_api:b2() =:= 'true' can never succeed
simple1_api.erl:315: Guard test A::simple1_api:b1() =:= 'false' can never succeed
-simple1_api.erl:319: Guard test not('and'('true','true')) can never succeed
+simple1_api.erl:319: Guard test not(and('true','true')) can never succeed
simple1_api.erl:337: Clause guard cannot succeed.
simple1_api.erl:342: Guard test B::simple1_adt:b2() =:= 'true' contains an opaque term as 1st argument
simple1_api.erl:347: Guard test A::simple1_adt:b1() =:= 'true' contains an opaque term as 1st argument
diff --git a/lib/dialyzer/test/options1_SUITE_data/dialyzer_options b/lib/dialyzer/test/options1_SUITE_data/dialyzer_options
index c612e77d3e..ef5887a1eb 100644
--- a/lib/dialyzer/test/options1_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/options1_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{include_dirs, ["my_include"]}, {defines, [{'COMPILER_VSN', 42}]}, {warnings, [no_improper_lists]}]}.
+{dialyzer_options, [{indent_opt, false}, {include_dirs, ["my_include"]}, {defines, [{'COMPILER_VSN', 42}]}, {warnings, [no_improper_lists]}]}.
{time_limit, 30}.
diff --git a/lib/dialyzer/test/options1_SUITE_data/results/compiler b/lib/dialyzer/test/options1_SUITE_data/results/compiler
index e1dc038800..121163d7ca 100644
--- a/lib/dialyzer/test/options1_SUITE_data/results/compiler
+++ b/lib/dialyzer/test/options1_SUITE_data/results/compiler
@@ -6,8 +6,8 @@ beam_disasm.erl:537: The variable X can never match since previous clauses compl
beam_type.erl:284: The pattern <'pi', 0> can never match the type <_,1 | 2>
beam_validator.erl:396: Matching of pattern {'vst', 'none', _} tagged with a record name violates the declared type of #vst{current::#st{ct::[]}}
beam_validator.erl:690: The pattern <'term', OldT> can never match the type <{'tuple',[any(),...]},_>
-beam_validator.erl:693: Guard test 'or'('false','false') can never succeed
-beam_validator.erl:700: Guard test 'or'('false','false') can never succeed
+beam_validator.erl:693: Guard test or('false','false') can never succeed
+beam_validator.erl:700: Guard test or('false','false') can never succeed
beam_validator.erl:702: The pattern <'number', OldT = {Type, _}> can never match the type <{'tuple',[any(),...]},_>
beam_validator.erl:705: The pattern <'bool', {'atom', A}> can never match the type <{'tuple',[any(),...]},_>
beam_validator.erl:707: The pattern <{'atom', A}, 'bool'> can never match the type <{'tuple',[any(),...]},_>
diff --git a/lib/dialyzer/test/options2_SUITE_data/dialyzer_options b/lib/dialyzer/test/options2_SUITE_data/dialyzer_options
index be57e2de72..6492098d01 100644
--- a/lib/dialyzer/test/options2_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/options2_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{defines, [{'vsn', 4}]}, {warnings, [unknown, no_return]}]}.
+{dialyzer_options, [{indent_opt, false}, {defines, [{'vsn', 4}]}, {warnings, [unknown, no_return]}]}.
diff --git a/lib/dialyzer/test/overspecs_SUITE_data/dialyzer_options b/lib/dialyzer/test/overspecs_SUITE_data/dialyzer_options
index ff4517e59d..f581ad6607 100644
--- a/lib/dialyzer/test/overspecs_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/overspecs_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{warnings, [overspecs]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [overspecs]}]}.
diff --git a/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options b/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options
index e00e23bb66..ab6c9439ad 100644
--- a/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/r9c_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, [{defines, [{vsn, 42}]}]}.
+{dialyzer_options, [{indent_opt, false}, {defines, [{vsn, 42}]}]}.
{time_limit, 20}.
diff --git a/lib/dialyzer/test/r9c_SUITE_data/results/inets b/lib/dialyzer/test/r9c_SUITE_data/results/inets
index d377f34978..31e9f031ce 100644
--- a/lib/dialyzer/test/r9c_SUITE_data/results/inets
+++ b/lib/dialyzer/test/r9c_SUITE_data/results/inets
@@ -21,9 +21,9 @@ httpd_manager.erl:885: The pattern {'EXIT', Reason} can never match since previo
httpd_manager.erl:919: Function auth_status/1 will never be called
httpd_manager.erl:926: Function sec_status/1 will never be called
httpd_manager.erl:933: Function acceptor_status/1 will never be called
-httpd_request_handler.erl:374: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 66 | 98 | 100 | 103 | 105 | 111 | 116 | 121,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any())
-httpd_request_handler.erl:378: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any())
-httpd_request_handler.erl:401: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,[32 | 77 | 97 | 100 | 101 | 104 | 108 | 110 | 111 | 116 | 119,...]) will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any())
+httpd_request_handler.erl:374: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,"Body to big") will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any())
+httpd_request_handler.erl:378: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,"Method not allowed") will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any())
+httpd_request_handler.erl:401: The call httpd_response:send_status(Info::#mod{parsed_header::maybe_improper_list()},417,"Method not allowed") will never return since it differs in the 2nd argument from the success typing arguments: (#mod{socket_type::'ip_comm' | {'ssl',_}},100 | 301 | 304 | 400 | 401 | 403 | 404 | 412 | 414 | 416 | 500 | 501 | 503,any())
httpd_request_handler.erl:649: Guard test [{_,_}] =:= Trailers::nonempty_string() can never succeed
httpd_sup.erl:63: The variable Else can never match since previous clauses completely covered the type {'error',_} | {'ok',[any()],_,_}
httpd_sup.erl:88: The pattern {'error', Reason} can never match the type {'ok',_,_}
diff --git a/lib/dialyzer/test/race_SUITE_data/dialyzer_options b/lib/dialyzer/test/race_SUITE_data/dialyzer_options
index 44e1720715..2be2f47dda 100644
--- a/lib/dialyzer/test/race_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/race_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{warnings, [race_conditions]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [race_conditions]}]}.
diff --git a/lib/dialyzer/test/race_SUITE_data/results/ets_insert_double1 b/lib/dialyzer/test/race_SUITE_data/results/ets_insert_double1
index b640b91271..2473bf5d01 100644
--- a/lib/dialyzer/test/race_SUITE_data/results/ets_insert_double1
+++ b/lib/dialyzer/test/race_SUITE_data/results/ets_insert_double1
@@ -1,4 +1,4 @@
ets_insert_double1.erl:15: The call ets:insert('foo',[{'pass',[number()]} | {'random',integer()},...]) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('foo','random') call in ets_insert_double1.erl on line 10, the ets:lookup('foo','pass') call in ets_insert_double1.erl on line 27
ets_insert_double1.erl:19: The call ets:insert('foo',[{'pass',[number()]} | {'random',integer()},...]) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('foo','random') call in ets_insert_double1.erl on line 10, the ets:lookup('foo','pass') call in ets_insert_double1.erl on line 27
-ets_insert_double1.erl:24: The call ets:insert('foo',{'pass','empty'}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('foo','pass') call in ets_insert_double1.erl on line 22
+ets_insert_double1.erl:24: The call ets:insert('foo',{'pass', 'empty'}) might have an unintended effect due to a possible race condition caused by its combination with the ets:lookup('foo','pass') call in ets_insert_double1.erl on line 22
diff --git a/lib/dialyzer/test/small_SUITE_data/dialyzer_options b/lib/dialyzer/test/small_SUITE_data/dialyzer_options
index 50991c9bc5..8413436b67 100644
--- a/lib/dialyzer/test/small_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/small_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, []}.
+{dialyzer_options, [{indent_opt, false}]}.
diff --git a/lib/dialyzer/test/small_SUITE_data/results/chars b/lib/dialyzer/test/small_SUITE_data/results/chars
index 72fbdb4528..02797f2a59 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/chars
+++ b/lib/dialyzer/test/small_SUITE_data/results/chars
@@ -1,4 +1,4 @@
chars.erl:37: Invalid type specification for function chars:f/1. The success typing is (#{'b':=50}) -> 'ok'
chars.erl:40: Function t1/0 has no local return
-chars.erl:40: The call chars:f(#{'b':=50}) breaks the contract (#{'a':=49,'b'=>50,'c'=>51}) -> 'ok'
+chars.erl:40: The call chars:f(#{'b'=>50}) breaks the contract (#{'a':=49,'b'=>50,'c'=>51}) -> 'ok'
diff --git a/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes b/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes
index d2a3ebb766..04336f43aa 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes
+++ b/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes
@@ -1,15 +1,15 @@
-contracts_with_subtypes.erl:106: The call contracts_with_subtypes:rec_arg({'a','b'}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A} | {'b',B}, A :: 'a' | {'b',B}, B :: 'b' | {'a',A}
-contracts_with_subtypes.erl:107: The call contracts_with_subtypes:rec_arg({'b','a'}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A} | {'b',B}, A :: 'a' | {'b',B}, B :: 'b' | {'a',A}
-contracts_with_subtypes.erl:109: The call contracts_with_subtypes:rec_arg({'b',{'a','b'}}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A} | {'b',B}, A :: 'a' | {'b',B}, B :: 'b' | {'a',A}
-contracts_with_subtypes.erl:135: The call contracts_with_subtypes:rec2({'a','b'}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
-contracts_with_subtypes.erl:136: The call contracts_with_subtypes:rec2({'b','a'}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
-contracts_with_subtypes.erl:137: The call contracts_with_subtypes:rec2({'a',{'b','a'}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
-contracts_with_subtypes.erl:138: The call contracts_with_subtypes:rec2({'b',{'a','b'}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
-contracts_with_subtypes.erl:139: The call contracts_with_subtypes:rec2({'a',{'b',{'a','b'}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
-contracts_with_subtypes.erl:140: The call contracts_with_subtypes:rec2({'b',{'a',{'b','a'}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
-contracts_with_subtypes.erl:141: The call contracts_with_subtypes:rec2({'a',{'b',{'a',{'b','a'}}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
-contracts_with_subtypes.erl:142: The call contracts_with_subtypes:rec2({'b',{'a',{'b',{'a','b'}}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:106: The call contracts_with_subtypes:rec_arg({'a', 'b'}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A} | {'b',B}, A :: 'a' | {'b',B}, B :: 'b' | {'a',A}
+contracts_with_subtypes.erl:107: The call contracts_with_subtypes:rec_arg({'b', 'a'}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A} | {'b',B}, A :: 'a' | {'b',B}, B :: 'b' | {'a',A}
+contracts_with_subtypes.erl:109: The call contracts_with_subtypes:rec_arg({'b', {'a', 'b'}}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A} | {'b',B}, A :: 'a' | {'b',B}, B :: 'b' | {'a',A}
+contracts_with_subtypes.erl:135: The call contracts_with_subtypes:rec2({'a', 'b'}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:136: The call contracts_with_subtypes:rec2({'b', 'a'}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:137: The call contracts_with_subtypes:rec2({'a', {'b', 'a'}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:138: The call contracts_with_subtypes:rec2({'b', {'a', 'b'}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:139: The call contracts_with_subtypes:rec2({'a', {'b', {'a', 'b'}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:140: The call contracts_with_subtypes:rec2({'b', {'a', {'b', 'a'}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:141: The call contracts_with_subtypes:rec2({'a', {'b', {'a', {'b', 'a'}}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
+contracts_with_subtypes.erl:142: The call contracts_with_subtypes:rec2({'b', {'a', {'b', {'a', 'b'}}}}) breaks the contract (Arg) -> 'ok' when Arg :: ab()
contracts_with_subtypes.erl:175: The pattern 1 can never match the type string()
contracts_with_subtypes.erl:178: The pattern 'alpha' can never match the type {'ok',_} | {'ok',_,string()}
contracts_with_subtypes.erl:180: The pattern 42 can never match the type {'ok',_} | {'ok',_,string()}
diff --git a/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes2 b/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes2
index 1a8aeb13d0..6d611db568 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes2
+++ b/lib/dialyzer/test/small_SUITE_data/results/contracts_with_subtypes2
@@ -1,3 +1,3 @@
contracts_with_subtypes2.erl:18: Function t/0 has no local return
-contracts_with_subtypes2.erl:19: The call contracts_with_subtypes2:t({'a',{'b',{'c',{'d',{'e',{'g',3}}}}}}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A}, A :: {'b',B}, B :: {'c',C}, C :: {'d',D}, D :: {'e',E}, E :: {'f',_}
+contracts_with_subtypes2.erl:19: The call contracts_with_subtypes2:t({'a', {'b', {'c', {'d', {'e', {'g', 3}}}}}}) breaks the contract (Arg) -> 'ok' when Arg :: {'a',A}, A :: {'b',B}, B :: {'c',C}, C :: {'d',D}, D :: {'e',E}, E :: {'f',_}
diff --git a/lib/dialyzer/test/small_SUITE_data/results/failing_guard1 b/lib/dialyzer/test/small_SUITE_data/results/failing_guard1
index 5bdd13093a..09fe076a6f 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/failing_guard1
+++ b/lib/dialyzer/test/small_SUITE_data/results/failing_guard1
@@ -1,4 +1,4 @@
failing_guard1.erl:12: Guard test float() =:= 2 can never succeed
-failing_guard1.erl:13: Guard test integer() =:= float() can never succeed
-failing_guard1.erl:14: Guard test -2 | -1 | 0 | 1 | 2 =:= float() can never succeed
+failing_guard1.erl:13: Guard test integer() =:= 2.0 can never succeed
+failing_guard1.erl:14: Guard test -2 | -1 | 0 | 1 | 2 =:= 2.0 can never succeed
diff --git a/lib/dialyzer/test/small_SUITE_data/results/guard_warnings b/lib/dialyzer/test/small_SUITE_data/results/guard_warnings
index 0ff998bf50..14b7a9052c 100644
--- a/lib/dialyzer/test/small_SUITE_data/results/guard_warnings
+++ b/lib/dialyzer/test/small_SUITE_data/results/guard_warnings
@@ -1,6 +1,6 @@
guard_warnings.erl:100: Function test45/0 has no local return
-guard_warnings.erl:100: Guard test 'not'('true') can never succeed
+guard_warnings.erl:100: Guard test not('true') can never succeed
guard_warnings.erl:102: Function test46/1 has no local return
guard_warnings.erl:102: Guard test X::'true' =:= 'false' can never succeed
guard_warnings.erl:104: Function test47/1 has no local return
@@ -14,79 +14,79 @@ guard_warnings.erl:12: Guard test X::'true' =:= 'false' can never succeed
guard_warnings.erl:14: Function test2/1 has no local return
guard_warnings.erl:14: Guard test X::'false' =:= 'true' can never succeed
guard_warnings.erl:16: Function test3/1 has no local return
-guard_warnings.erl:16: Guard test 'not'(X::'true') can never succeed
+guard_warnings.erl:16: Guard test not(X::'true') can never succeed
guard_warnings.erl:18: Function test4/1 has no local return
-guard_warnings.erl:18: Guard test 'and'('true',X::none()) can never succeed
+guard_warnings.erl:18: Guard test and('true',X::none()) can never succeed
guard_warnings.erl:20: Function test5/1 has no local return
-guard_warnings.erl:20: Guard test 'not'(X::'true') can never succeed
+guard_warnings.erl:20: Guard test not(X::'true') can never succeed
guard_warnings.erl:22: Function test6/1 has no local return
-guard_warnings.erl:22: Guard test 'and'('true',X::none()) can never succeed
+guard_warnings.erl:22: Guard test and('true',X::none()) can never succeed
guard_warnings.erl:24: Function test7_w/1 has no local return
guard_warnings.erl:26: Function test8_w/1 has no local return
guard_warnings.erl:28: Function test9/1 has no local return
-guard_warnings.erl:28: Guard test not('not'(X::'false')) can never succeed
+guard_warnings.erl:28: Guard test not(not(X::'false')) can never succeed
guard_warnings.erl:30: Function test10/1 has no local return
-guard_warnings.erl:30: Guard test not('or'('false',X::none())) can never succeed
+guard_warnings.erl:30: Guard test not(or('false',X::none())) can never succeed
guard_warnings.erl:32: Function test11/1 has no local return
-guard_warnings.erl:32: Guard test not('not'(X::'false')) can never succeed
+guard_warnings.erl:32: Guard test not(not(X::'false')) can never succeed
guard_warnings.erl:34: Function test12/1 has no local return
-guard_warnings.erl:34: Guard test not('or'('false',X::none())) can never succeed
+guard_warnings.erl:34: Guard test not(or('false',X::none())) can never succeed
guard_warnings.erl:36: Function test13/1 has no local return
-guard_warnings.erl:36: Guard test 'and'('true','false') can never succeed
+guard_warnings.erl:36: Guard test and('true','false') can never succeed
guard_warnings.erl:38: Function test14/1 has no local return
-guard_warnings.erl:38: Guard test 'and'('false',any()) can never succeed
+guard_warnings.erl:38: Guard test and('false',any()) can never succeed
guard_warnings.erl:40: Function test15/1 has no local return
-guard_warnings.erl:40: Guard test 'and'(X::'true','false') can never succeed
+guard_warnings.erl:40: Guard test and(X::'true','false') can never succeed
guard_warnings.erl:42: Function test16/1 has no local return
-guard_warnings.erl:42: Guard test 'and'('false',X::any()) can never succeed
+guard_warnings.erl:42: Guard test and('false',X::any()) can never succeed
guard_warnings.erl:44: Function test17/1 has no local return
-guard_warnings.erl:44: Guard test 'and'(X::'true','false') can never succeed
+guard_warnings.erl:44: Guard test and(X::'true','false') can never succeed
guard_warnings.erl:46: Function test18/1 has no local return
-guard_warnings.erl:46: Guard test 'and'('false',X::any()) can never succeed
+guard_warnings.erl:46: Guard test and('false',X::any()) can never succeed
guard_warnings.erl:48: Function test19/1 has no local return
-guard_warnings.erl:48: Guard test not('or'('true',any())) can never succeed
+guard_warnings.erl:48: Guard test not(or('true',any())) can never succeed
guard_warnings.erl:50: Function test20/1 has no local return
-guard_warnings.erl:50: Guard test not('or'('false','true')) can never succeed
+guard_warnings.erl:50: Guard test not(or('false','true')) can never succeed
guard_warnings.erl:52: Function test21/1 has no local return
-guard_warnings.erl:52: Guard test not('or'('true',X::any())) can never succeed
+guard_warnings.erl:52: Guard test not(or('true',X::any())) can never succeed
guard_warnings.erl:54: Function test22/1 has no local return
-guard_warnings.erl:54: Guard test not('or'(X::'false','true')) can never succeed
+guard_warnings.erl:54: Guard test not(or(X::'false','true')) can never succeed
guard_warnings.erl:56: Function test23/1 has no local return
-guard_warnings.erl:56: Guard test not('or'('true',X::any())) can never succeed
+guard_warnings.erl:56: Guard test not(or('true',X::any())) can never succeed
guard_warnings.erl:58: Function test24/1 has no local return
-guard_warnings.erl:58: Guard test not('or'(X::'false','true')) can never succeed
+guard_warnings.erl:58: Guard test not(or(X::'false','true')) can never succeed
guard_warnings.erl:60: Function test25/1 has no local return
-guard_warnings.erl:60: Guard test 'and'('false',any()) can never succeed
+guard_warnings.erl:60: Guard test and('false',any()) can never succeed
guard_warnings.erl:62: Function test26/1 has no local return
-guard_warnings.erl:62: Guard test 'and'('true','false') can never succeed
+guard_warnings.erl:62: Guard test and('true','false') can never succeed
guard_warnings.erl:64: Function test27/1 has no local return
-guard_warnings.erl:64: Guard test 'and'('false',X::any()) can never succeed
+guard_warnings.erl:64: Guard test and('false',X::any()) can never succeed
guard_warnings.erl:66: Function test28/1 has no local return
-guard_warnings.erl:66: Guard test 'and'(X::'true','false') can never succeed
+guard_warnings.erl:66: Guard test and(X::'true','false') can never succeed
guard_warnings.erl:68: Function test29/1 has no local return
-guard_warnings.erl:68: Guard test 'and'('false',X::any()) can never succeed
+guard_warnings.erl:68: Guard test and('false',X::any()) can never succeed
guard_warnings.erl:70: Function test30/1 has no local return
-guard_warnings.erl:70: Guard test 'and'(X::'true','false') can never succeed
+guard_warnings.erl:70: Guard test and(X::'true','false') can never succeed
guard_warnings.erl:72: Function test31/0 has no local return
-guard_warnings.erl:72: Guard test 'and'('false',any()) can never succeed
+guard_warnings.erl:72: Guard test and('false','false') can never succeed
guard_warnings.erl:74: Function test32/0 has no local return
-guard_warnings.erl:74: Guard test 'and'('false',any()) can never succeed
+guard_warnings.erl:74: Guard test and('false','false') can never succeed
guard_warnings.erl:76: Function test33/0 has no local return
-guard_warnings.erl:76: Guard test not('and'('true','true')) can never succeed
+guard_warnings.erl:76: Guard test not(and('true','true')) can never succeed
guard_warnings.erl:78: Function test34/0 has no local return
-guard_warnings.erl:78: Guard test 'and'('false',any()) can never succeed
+guard_warnings.erl:78: Guard test and('false','false') can never succeed
guard_warnings.erl:80: Function test35/0 has no local return
-guard_warnings.erl:80: Guard test not('and'('true','true')) can never succeed
+guard_warnings.erl:80: Guard test not(and('true','true')) can never succeed
guard_warnings.erl:82: Function test36/0 has no local return
-guard_warnings.erl:82: Guard test 'or'('false','false') can never succeed
+guard_warnings.erl:82: Guard test or('false','false') can never succeed
guard_warnings.erl:84: Function test37/0 has no local return
-guard_warnings.erl:84: Guard test 'or'('false','false') can never succeed
+guard_warnings.erl:84: Guard test or('false','false') can never succeed
guard_warnings.erl:86: Function test38/0 has no local return
-guard_warnings.erl:86: Guard test 'or'('false','false') can never succeed
+guard_warnings.erl:86: Guard test or('false','false') can never succeed
guard_warnings.erl:88: Function test39/0 has no local return
-guard_warnings.erl:88: Guard test 'or'('false','false') can never succeed
+guard_warnings.erl:88: Guard test or('false','false') can never succeed
guard_warnings.erl:90: Function test40/0 has no local return
-guard_warnings.erl:90: Guard test 'or'('false','false') can never succeed
+guard_warnings.erl:90: Guard test or('false','false') can never succeed
guard_warnings.erl:92: Function test41/0 has no local return
guard_warnings.erl:92: Guard test 'true' =:= 'false' can never succeed
guard_warnings.erl:94: Function test42/0 has no local return
diff --git a/lib/dialyzer/test/small_SUITE_data/results/spec_other_module b/lib/dialyzer/test/small_SUITE_data/results/spec_other_module
deleted file mode 100644
index ab2e35cf55..0000000000
--- a/lib/dialyzer/test/small_SUITE_data/results/spec_other_module
+++ /dev/null
@@ -1,2 +0,0 @@
-
-spec_other_module.erl:7: Contract for function that does not exist: lists:flatten/1
diff --git a/lib/dialyzer/test/small_SUITE_data/results/union_paren b/lib/dialyzer/test/small_SUITE_data/results/union_paren
new file mode 100644
index 0000000000..1766773f2d
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/results/union_paren
@@ -0,0 +1,25 @@
+
+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/spec_other_module.erl b/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl
deleted file mode 100644
index b36742b1bd..0000000000
--- a/lib/dialyzer/test/small_SUITE_data/src/spec_other_module.erl
+++ /dev/null
@@ -1,7 +0,0 @@
--module(spec_other_module).
-
-%% OTP-15562 and ERL-845. Example provided by Kostis.
-
--type deep_list(A) :: [A | deep_list(A)].
-
--spec lists:flatten(deep_list(A)) -> [A].
diff --git a/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl b/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl
new file mode 100644
index 0000000000..65bda1876e
--- /dev/null
+++ b/lib/dialyzer/test/small_SUITE_data/src/union_paren.erl
@@ -0,0 +1,98 @@
+-module(union_paren).
+
+-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).
+
+-spec t1((A :: integer()) | (B :: atom())) -> integer().
+t1(A) ->
+ fy:bar(A).
+
+t2() ->
+ t2(3.14).
+
+-spec t2(integer() | atom()) -> integer().
+t2(A) ->
+ fy:bar(A).
+
+t3() ->
+ 3.14 = t3(foo).
+
+-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/test/specdiffs_SUITE_data/dialyzer_options b/lib/dialyzer/test/specdiffs_SUITE_data/dialyzer_options
index 56b36f2ed4..f7076e34da 100644
--- a/lib/dialyzer/test/specdiffs_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/specdiffs_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{warnings, [specdiffs]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [specdiffs]}]}.
diff --git a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options
index f7197ac30f..1a9734deb2 100644
--- a/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/underspecs_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{warnings, [underspecs]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [underspecs]}]}.
diff --git a/lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options b/lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options
index 49ac917f61..7de9d6f962 100644
--- a/lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/unmatched_returns_SUITE_data/dialyzer_options
@@ -1 +1 @@
-{dialyzer_options, [{warnings, [unmatched_returns]}]}.
+{dialyzer_options, [{indent_opt, false}, {warnings, [unmatched_returns]}]}.
diff --git a/lib/dialyzer/test/user_SUITE_data/dialyzer_options b/lib/dialyzer/test/user_SUITE_data/dialyzer_options
index 513ed7752b..0a944966f0 100644
--- a/lib/dialyzer/test/user_SUITE_data/dialyzer_options
+++ b/lib/dialyzer/test/user_SUITE_data/dialyzer_options
@@ -1,2 +1,2 @@
-{dialyzer_options, []}.
+{dialyzer_options, [{indent_opt, false}]}.
{time_limit, 3}. \ No newline at end of file
diff --git a/lib/dialyzer/vsn.mk b/lib/dialyzer/vsn.mk
index 7221993963..466bbfd0f2 100644
--- a/lib/dialyzer/vsn.mk
+++ b/lib/dialyzer/vsn.mk
@@ -1 +1 @@
-DIALYZER_VSN = 3.3.2
+DIALYZER_VSN = 4.0.1
diff --git a/lib/edoc/doc/src/notes.xml b/lib/edoc/doc/src/notes.xml
index 145856bcaa..48bc5d9c74 100644
--- a/lib/edoc/doc/src/notes.xml
+++ b/lib/edoc/doc/src/notes.xml
@@ -32,6 +32,20 @@
<p>This document describes the changes made to the EDoc
application.</p>
+<section><title>Edoc 0.11</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Correct links in the documentation. </p>
+ <p>
+ Own Id: OTP-15761</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Edoc 0.10</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/edoc/vsn.mk b/lib/edoc/vsn.mk
index b6e1422623..3510fdfccf 100644
--- a/lib/edoc/vsn.mk
+++ b/lib/edoc/vsn.mk
@@ -1 +1 @@
-EDOC_VSN = 0.10
+EDOC_VSN = 0.11
diff --git a/lib/eldap/doc/src/notes.xml b/lib/eldap/doc/src/notes.xml
index bf9358c4d1..946db5b93d 100644
--- a/lib/eldap/doc/src/notes.xml
+++ b/lib/eldap/doc/src/notes.xml
@@ -31,6 +31,42 @@
</header>
<p>This document describes the changes made to the Eldap application.</p>
+<section><title>Eldap 1.2.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix dialyzer warnings in eldap when not matching the
+ return value of ssl:close/1.</p>
+ <p>
+ Own Id: OTP-15775</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Eldap 1.2.7</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Back port of bug fix ERL-893 from OTP-22 and document
+ enhancements that will solve dialyzer warnings for users
+ of the ssl application.</p>
+ <p>
+ This change also affects public_key, eldap (and inet
+ doc).</p>
+ <p>
+ Own Id: OTP-15785 Aux Id: ERL-929, ERL-893, PR-2215 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Eldap 1.2.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/eldap/vsn.mk b/lib/eldap/vsn.mk
index 6d541e4689..8f969e6945 100644
--- a/lib/eldap/vsn.mk
+++ b/lib/eldap/vsn.mk
@@ -1 +1 @@
-ELDAP_VSN = 1.2.6
+ELDAP_VSN = 1.2.8
diff --git a/lib/erl_docgen/doc/src/notes.xml b/lib/erl_docgen/doc/src/notes.xml
index 54f0a36b27..f25361a202 100644
--- a/lib/erl_docgen/doc/src/notes.xml
+++ b/lib/erl_docgen/doc/src/notes.xml
@@ -31,7 +31,24 @@
</header>
<p>This document describes the changes made to the <em>erl_docgen</em> application.</p>
- <section><title>Erl_Docgen 0.9</title>
+ <section><title>Erl_Docgen 0.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Docgen 0.9</title>
<section><title>Improvements and New Features</title>
<list>
diff --git a/lib/erl_docgen/vsn.mk b/lib/erl_docgen/vsn.mk
index 6321f229dd..fece2456c1 100644
--- a/lib/erl_docgen/vsn.mk
+++ b/lib/erl_docgen/vsn.mk
@@ -1 +1 @@
-ERL_DOCGEN_VSN = 0.9
+ERL_DOCGEN_VSN = 0.9.1
diff --git a/lib/erl_interface/doc/src/ei.xml b/lib/erl_interface/doc/src/ei.xml
index 7808bfd94f..70af5642da 100644
--- a/lib/erl_interface/doc/src/ei.xml
+++ b/lib/erl_interface/doc/src/ei.xml
@@ -183,7 +183,7 @@ typedef enum {
</func>
<func>
- <name since="OTP @OTP-15712@"><ret>int</ret><nametext>ei_decode_bitstring(const char *buf, int *index, const char **pp, unsigned int *bitoffsp, size_t *nbitsp)</nametext></name>
+ <name since="OTP 22.0"><ret>int</ret><nametext>ei_decode_bitstring(const char *buf, int *index, const char **pp, unsigned int *bitoffsp, size_t *nbitsp)</nametext></name>
<fsummary>Decode a bitstring.</fsummary>
<desc>
<p>Decodes a bit string from the binary format.</p>
@@ -498,9 +498,9 @@ typedef enum {
</func>
<func>
- <name since="OTP @OTP-15712@"><ret>int</ret>
+ <name since="OTP 22.0"><ret>int</ret>
<nametext>ei_encode_bitstring(char *buf, int *index, const char *p, size_t bitoffs, size_t nbits)</nametext></name>
- <name since="OTP @OTP-15712@"><ret>int</ret>
+ <name since="OTP 22.0"><ret>int</ret>
<nametext>ei_x_encode_bitstring(ei_x_buff* x, const char *p, size_t bitoffs, size_t nbits)</nametext></name>
<fsummary>Encode a bitstring.</fsummary>
<desc>
@@ -849,30 +849,48 @@ ei_encode_tuple_header(buf, &amp;i, 0);</pre>
</type>
<desc>
<marker id="ei_set_compat_rel"></marker>
- <p>By default, the <c>ei</c> library is only guaranteed
- to be compatible with other Erlang/OTP components from the same
- release as the <c>ei</c> library itself. For example,
- <c>ei</c> from
- Erlang/OTP R10 is not compatible with an Erlang emulator
- from Erlang/OTP R9 by default.</p>
- <p>A call to <c>ei_set_compat_rel(release_number)</c> sets
- the <c>ei</c> library in compatibility mode of release
- <c>release_number</c>. Valid range of
- <c>release_number</c>
- is <c>[7, current release]</c>. This makes it possible to
- communicate with Erlang/OTP components from earlier releases.</p>
+ <p>In general, the <c>ei</c> library is guaranteed
+ to be compatible with other Erlang/OTP components that are 2 major
+ releases older or newer than the <c>ei</c> library itself.</p>
+ <p>Sometimes an exception to the above rule has to be made to make new
+ features (or even bug fixes) possible. A call to
+ <c>ei_set_compat_rel(release_number)</c> sets
+ the <c>ei</c> library in compatibility mode of OTP release
+ <c>release_number</c>.</p>
+ <p>The only useful value for <c>release_number</c> is currently
+ <c>21</c>. This will only be useful and have an effect if <em>bit
+ strings</em> or <em>export funs</em> are received from a connected
+ node. Before OTP 22, bit strings and export funs were not supported by
+ <c>ei</c>. They were instead encoded using an undocumented fallback
+ tuple format when sent from the emulator to <c>ei</c>:</p>
+ <taglist>
+ <tag><c>Bit string</c></tag>
+ <item><p>The term <c>&lt;&lt;42, 1:1>></c> was encoded as
+ <c>{&lt;&lt;42, 128>>, 1}</c>. The first element of the tuple is a
+ binary and the second element denotes how many bits of the last bytes
+ are part of the bit string. In this example only the most significant
+ bit of the last byte (128) is part of the bit string.</p>
+ </item>
+ <tag><c>Export fun</c></tag>
+ <item><p>The term <c>fun lists:map/2</c> was encoded as
+ <c>{lists,map}</c>. A tuple with the module, function and a missing
+ arity.</p>
+ </item>
+ </taglist>
+ <p>If <c>ei_set_compat_rel(21)</c> is <em>not</em> called then a connected
+ emulator will send bit strings and export funs correctly encoded. The
+ functions <seealso marker="#ei_decode_bitstring"><c>ei_decode_bitstring</c></seealso>
+ and <seealso marker="#ei_decode_fun"><c>ei_decode_fun</c></seealso>
+ has to be used to decode such terms. Calling
+ <c>ei_set_compat_rel(21)</c> should only be done as a workaround to
+ keep an old implementation alive, which expects to receive the
+ undocumented tuple formats for bit strings and/or export funs.
+ </p>
<note>
<p>If this function is called, it can only be called once
and must be called before any other functions in the
<c>ei</c> library are called.</p>
</note>
- <warning>
- <p>You can run into trouble if this feature is used
- carelessly. Always ensure that all communicating
- components are either from the same Erlang/OTP release, or
- from release X and release Y where all components
- from release Y are in compatibility mode of release X.</p>
- </warning>
</desc>
</func>
diff --git a/lib/erl_interface/doc/src/notes.xml b/lib/erl_interface/doc/src/notes.xml
index fc6a1bb548..c47f0d2bd1 100644
--- a/lib/erl_interface/doc/src/notes.xml
+++ b/lib/erl_interface/doc/src/notes.xml
@@ -31,6 +31,148 @@
</header>
<p>This document describes the changes made to the Erl_interface application.</p>
+<section><title>Erl_Interface 3.12</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ The vxworks configure has been updated to respect the
+ environment CFLAGS.</p>
+ <p>
+ Own Id: OTP-15773</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Minor adjustments made to build system for parallel
+ configure.</p>
+ <p>
+ Own Id: OTP-15340 Aux Id: OTP-14625 </p>
+ </item>
+ <item>
+ <p>
+ The limited support for VxWorks is deprecated as of OTP
+ 22, and will be removed in OTP 23.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15621</p>
+ </item>
+ <item>
+ <p>The old legacy <c>erl_interface</c> library (functions
+ with prefix <c>erl_</c>) is deprecated as of OTP 22, and
+ will be removed in OTP 23. This does not apply to the
+ <c>ei</c> library. Reasonably new <c>gcc</c> compilers
+ will issue deprecation warnings. In order to disable
+ these warnings, define the macro
+ <c>EI_NO_DEPR_WARN</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15622</p>
+ </item>
+ <item>
+ <p>Added support to receive, decode, encode and send both
+ bit strings and export funs (<c>fun M:F/A</c>).</p>
+ <p>New functions <c>ei_decode_bitstring</c> and
+ <c>ei_encode_bitstring</c> have been added in order to
+ decode and encode bit strings where the number of bits is
+ not necessary divisible by 8 (a whole number of bytes).
+ The existing functions <c>ei_decode_fun</c> and
+ <c>ei_encode_fun</c> can now also handle export funs.</p>
+ <p>Before this change, bit strings and export funs sent
+ to an erl_interface c-node were encoded using an
+ undocumented fallback tuple format. For bit strings
+ <c>{Binary,BitsInLastByte}</c> and for export funs
+ <c>{M,F}</c>. Existing c-node implementations expecting
+ these tuples must be changed to instead use
+ <c>ei_decode_bitstring</c> and <c>ei_decode_fun</c>. As a
+ temporary solution you can also build erl_interface with
+ macro <c>EI_COMPAT=21</c> or call
+ <c>ei_set_compat_rel(21)</c> to receive the old fallback
+ tuples.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15712 Aux Id: OTP-15774 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Interface 3.11.3</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ <c>erl_interface</c>/<c>ei</c> refused to use node names
+ with an alive name (the part of the node name preceding
+ the @ sign) longer than 63 characters and a host name
+ longer than 64 characters. The total amount of characters
+ allowed in a node name (alivename@hostname) was thus
+ limited to 128 characters. These limits applied both to
+ the own node name as well as node names of other nodes.
+ Ordinary Erlang nodes limit the node name length to 256
+ characters, which meant that you could not communicate
+ with certain Erlang nodes due to their node name used.</p>
+ <p>
+ <c>erl_interface</c>/<c>ei</c> now allow the total amount
+ of characters in a node name to be up to 256 characters.
+ These characters may be distributed between alive name
+ and host name in whatever way needed. That is, the
+ maximum amount of characters in the alive name may be 254
+ and the maximum amount of characters in the host name may
+ be 254, but in total the node name must not exceed 256
+ characters.</p>
+ <p>
+ Own Id: OTP-15781 Aux Id: ERIERL-356 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Erl_Interface 3.11.2.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ <c>erl_interface</c>/<c>ei</c> refused to use node names
+ with an alive name (the part of the node name preceding
+ the @ sign) longer than 63 characters and a host name
+ longer than 64 characters. The total amount of characters
+ allowed in a node name (alivename@hostname) was thus
+ limited to 128 characters. These limits applied both to
+ the own node name as well as node names of other nodes.
+ Ordinary Erlang nodes limit the node name length to 256
+ characters, which meant that you could not communicate
+ with certain Erlang nodes due to their node name used.</p>
+ <p>
+ <c>erl_interface</c>/<c>ei</c> now allow the total amount
+ of characters in a node name to be up to 256 characters.
+ These characters may be distributed between alive name
+ and host name in whatever way needed. That is, the
+ maximum amount of characters in the alive name may be 254
+ and the maximum amount of characters in the host name may
+ be 254, but in total the node name must not exceed 256
+ characters.</p>
+ <p>
+ Own Id: OTP-15781 Aux Id: ERIERL-356 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Erl_Interface 3.11.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/erl_interface/include/ei.h b/lib/erl_interface/include/ei.h
index ed0420300d..b138118f04 100644
--- a/lib/erl_interface/include/ei.h
+++ b/lib/erl_interface/include/ei.h
@@ -213,12 +213,12 @@ extern volatile int __erl_errno;
* library and when using the library we set a value that we use
*/
-#define EI_MAXHOSTNAMELEN 64
-#define EI_MAXALIVELEN 63
#define EI_MAX_COOKIE_SIZE 512
#define MAXATOMLEN (255 + 1)
#define MAXATOMLEN_UTF8 (255*4 + 1)
-#define MAXNODELEN EI_MAXALIVELEN+1+EI_MAXHOSTNAMELEN
+#define EI_MAXHOSTNAMELEN (MAXATOMLEN - 2)
+#define EI_MAXALIVELEN (MAXATOMLEN - 2)
+#define MAXNODELEN MAXATOMLEN
typedef enum {
ERLANG_ASCII = 1,
diff --git a/lib/erl_interface/src/connect/ei_connect.c b/lib/erl_interface/src/connect/ei_connect.c
index 0cbad235cc..1b1479d2e9 100644
--- a/lib/erl_interface/src/connect/ei_connect.c
+++ b/lib/erl_interface/src/connect/ei_connect.c
@@ -791,14 +791,17 @@ int ei_connect_init_ussi(ei_cnode* ec, const char* this_node_name,
if (strcmp(hp->h_name, "localhost") == 0) {
/* We use a short node name */
if ((ct = strchr(thishostname, '.')) != NULL) *ct = '\0';
- sprintf(thisnodename, "%s@%s", this_node_name, thishostname);
} else {
/* We use a short node name */
if ((ct = strchr(hp->h_name, '.')) != NULL) *ct = '\0';
strcpy(thishostname, hp->h_name);
- sprintf(thisnodename, "%s@%s", this_node_name, hp->h_name);
}
}
+ if (strlen(this_node_name) + 1 + strlen(thishostname) > MAXNODELEN) {
+ EI_TRACE_ERR0("ei_connect_init_ussi","this node name is too long");
+ return ERL_ERROR;
+ }
+ sprintf(thisnodename, "%s@%s", this_node_name, thishostname);
res = ei_connect_xinit_ussi(ec, thishostname, thisalivename, thisnodename,
(struct in_addr *)*hp->h_addr_list, cookie, creation,
cbs, cbs_sz, setup_context);
@@ -889,6 +892,11 @@ int ei_connect_tmo(ei_cnode* ec, char *nodename, unsigned ms)
int ei_h_errno;
#endif /* !win32 */
int res;
+
+ if (strlen(nodename) > MAXNODELEN) {
+ EI_TRACE_ERR0("ei_connect","Too long nodename");
+ return ERL_ERROR;
+ }
/* extract the host and alive parts from nodename */
if (!(hostname = strchr(nodename,'@'))) {
diff --git a/lib/erl_interface/vsn.mk b/lib/erl_interface/vsn.mk
index 5e63f75ab5..cc72ed639a 100644
--- a/lib/erl_interface/vsn.mk
+++ b/lib/erl_interface/vsn.mk
@@ -1,2 +1,2 @@
-EI_VSN = 3.11.2
+EI_VSN = 3.12
ERL_INTERFACE_VSN = $(EI_VSN)
diff --git a/lib/hipe/cerl/erl_types.erl b/lib/hipe/cerl/erl_types.erl
index d61cd8664c..badf58936f 100644
--- a/lib/hipe/cerl/erl_types.erl
+++ b/lib/hipe/cerl/erl_types.erl
@@ -5338,7 +5338,15 @@ t_form_to_string({type, _L, tuple, any}) -> "tuple()";
t_form_to_string({type, _L, tuple, Args}) ->
"{" ++ flat_join(t_form_to_string_list(Args), ",") ++ "}";
t_form_to_string({type, _L, union, Args}) ->
- flat_join(t_form_to_string_list(Args), " | ");
+ flat_join(lists:map(fun(Arg) ->
+ case Arg of
+ {ann_type, _AL, _} ->
+ "(" ++ t_form_to_string(Arg) ++ ")";
+ _ ->
+ t_form_to_string(Arg)
+ end
+ end, Args),
+ " | ");
t_form_to_string({type, _L, Name, []} = T) ->
try
M = mod,
diff --git a/lib/hipe/doc/src/notes.xml b/lib/hipe/doc/src/notes.xml
index 9a803cb9df..a2e0766bb7 100644
--- a/lib/hipe/doc/src/notes.xml
+++ b/lib/hipe/doc/src/notes.xml
@@ -31,6 +31,52 @@
</header>
<p>This document describes the changes made to HiPE.</p>
+<section><title>Hipe 3.19</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Add function <c>hipe:erllvm_is_supported</c> to check for
+ the presences of a suitable version of the LLVM tool
+ chain as well as supported hardware architecture. The old
+ <c>hipe:llvm_support_available</c> has been removed.</p>
+ <p>
+ Own Id: OTP-15385 Aux Id: PR-1986 </p>
+ </item>
+ <item>
+ <p>
+ Fix hipe LLVM for FreeBSD and other non-linux unix to use
+ /tmp/ instead of /dev/shm/.</p>
+ <p>
+ Own Id: OTP-15386 Aux Id: PR-1963 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>In OTP 22, HiPE (the native code compiler) is not
+ fully functional. The reasons for this are:</p>
+ <p>There are new BEAM instructions for binary matching
+ that the HiPE native code compiler does not support.</p>
+ <p>The new optimizations in the Erlang compiler create
+ new combination of instructions that HiPE currently does
+ not handle correctly.</p>
+ <p>If erlc is invoked with the <c>+native</c> option, and
+ if any of the new binary matching instructions are used,
+ the compiler will issue a warning and produce a BEAM file
+ without native code.</p>
+ <p>
+ Own Id: OTP-15596</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Hipe 3.18.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/hipe/vsn.mk b/lib/hipe/vsn.mk
index 39565d721f..a91d92ca14 100644
--- a/lib/hipe/vsn.mk
+++ b/lib/hipe/vsn.mk
@@ -1 +1 @@
-HIPE_VSN = 3.18.3
+HIPE_VSN = 3.19
diff --git a/lib/inets/doc/src/notes.xml b/lib/inets/doc/src/notes.xml
index 2710ea2f2f..03bd1d8042 100644
--- a/lib/inets/doc/src/notes.xml
+++ b/lib/inets/doc/src/notes.xml
@@ -33,7 +33,24 @@
<file>notes.xml</file>
</header>
- <section><title>Inets 7.0.7</title>
+ <section><title>Inets 7.0.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Inets 7.0.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/inets/vsn.mk b/lib/inets/vsn.mk
index fd248e793a..5dbec9e7b3 100644
--- a/lib/inets/vsn.mk
+++ b/lib/inets/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = inets
-INETS_VSN = 7.0.7
+INETS_VSN = 7.0.8
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(INETS_VSN)$(PRE_VSN)"
diff --git a/lib/jinterface/doc/src/notes.xml b/lib/jinterface/doc/src/notes.xml
index e4bfddcd17..e79ada47f1 100644
--- a/lib/jinterface/doc/src/notes.xml
+++ b/lib/jinterface/doc/src/notes.xml
@@ -31,6 +31,42 @@
</header>
<p>This document describes the changes made to the Jinterface application.</p>
+<section><title>Jinterface 1.10</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>Added support to receive export funs (<c>fun
+ M:F/A</c>).</p>
+ <p>Before this change, export funs sent to a jinterface
+ node were encoded using an undocumented fallback tuple
+ format <c>{M,F}</c>. Existing jinterface implementations
+ expecting these tuples must be changed to instead use the
+ existing <c>OtpErlangExternalFun</c> class.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15774 Aux Id: OTP-15712 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Jinterface 1.9.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/jinterface/vsn.mk b/lib/jinterface/vsn.mk
index a8dc815145..95c7c95726 100644
--- a/lib/jinterface/vsn.mk
+++ b/lib/jinterface/vsn.mk
@@ -1 +1 @@
-JINTERFACE_VSN = 1.9.1
+JINTERFACE_VSN = 1.10
diff --git a/lib/kernel/doc/src/notes.xml b/lib/kernel/doc/src/notes.xml
index 61bd598145..6f68a67174 100644
--- a/lib/kernel/doc/src/notes.xml
+++ b/lib/kernel/doc/src/notes.xml
@@ -31,6 +31,104 @@
</header>
<p>This document describes the changes made to the Kernel application.</p>
+<section><title>Kernel 6.4</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix so that when multiple <c>-sname</c> or <c>-name</c>
+ are given to <c>erl</c> the first one is chosen. Before
+ this fix distribution was not started at all when
+ multiple name options were given.</p>
+ <p>
+ Own Id: OTP-15786 Aux Id: ERL-918 </p>
+ </item>
+ <item>
+ <p>
+ Fix <c>inet_res</c> configuration pointing to
+ non-existing files to work again. This was broken in
+ KERNEL-6.3 (OTP-21.3).</p>
+ <p>
+ Own Id: OTP-15806</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A simple socket API is provided through the socket
+ module. This is a low level API that does *not* replace
+ gen_[tcp|udp|sctp]. It is intended to *eventually*
+ replace the inet driver, but not the high level
+ gen-modules (gen_tcp, gen_udp and gen_sctp). It also
+ provides a basic API that facilitates the implementation
+ of other protocols, that is TCP, UDP and SCTP. </p>
+ <p>
+ Known issues are; No support for the Windows OS
+ (currently).</p>
+ <p>
+ Own Id: OTP-14831</p>
+ </item>
+ <item>
+ <p>
+ Improved the documentation for the linger option.</p>
+ <p>
+ Own Id: OTP-15491 Aux Id: PR-2019 </p>
+ </item>
+ <item>
+ <p> Global no longer tries more than once when connecting
+ to other nodes. </p>
+ <p>
+ Own Id: OTP-15607 Aux Id: ERIERL-280 </p>
+ </item>
+ <item>
+ <p>
+ The dist messages EXIT, EXIT2 and MONITOR_DOWN have been
+ updated with new versions that send the reason term as
+ part of the payload of the message instead of as part of
+ the control message.</p>
+ <p>
+ The old versions are still present and can be used when
+ communicating with nodes that don't support the new
+ versions.</p>
+ <p>
+ Own Id: OTP-15611</p>
+ </item>
+ <item>
+ <p>
+ Kernel configuration parameter <c>start_distribution =
+ boolean()</c> is added. If set to <c>false</c>, the
+ system is started with all distribution functionality
+ disabled. Defaults to <c>true</c>.</p>
+ <p>
+ Own Id: OTP-15668 Aux Id: PR-2088 </p>
+ </item>
+ <item>
+ <p>
+ In OTP-21.3, a warning was introduced for duplicated
+ applications/keys in configuration. This warning would be
+ displayed both when the configuration was given as a file
+ on system start, and during runtime via
+ <c>application:set_env/1,2</c>.</p>
+ <p>
+ The warning is now changed to a <c>badarg</c> exception
+ in <c>application:set_env/1,2</c>. If the faulty
+ configuration is given in a configuration file on system
+ start, the startup will fail.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15692 Aux Id: PR-2170 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Kernel 6.3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/kernel/src/inet_db.erl b/lib/kernel/src/inet_db.erl
index 3f5a2ea5ee..630ef5d2f7 100644
--- a/lib/kernel/src/inet_db.erl
+++ b/lib/kernel/src/inet_db.erl
@@ -1226,7 +1226,8 @@ handle_set_file(Option, Fname, TagTm, TagInfo, ParseFun, From,
_ ->
ets:insert(Db, {TagInfo, undefined}),
TimeZero = - (?RES_FILE_UPDATE_TM + 1), % Early enough
- ets:insert(Db, {TagTm, TimeZero})
+ ets:insert(Db, {TagTm, TimeZero}),
+ <<>>
end,
handle_set_file(ParseFun, Bin, From, State);
false -> {reply,error,State}
diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src
index aca3247c8f..cd0397a98c 100644
--- a/lib/kernel/src/kernel.appup.src
+++ b/lib/kernel/src/kernel.appup.src
@@ -19,23 +19,15 @@
%%
%% We allow upgrade from, and downgrade to all previous
%% versions from the following OTP releases:
-%% - OTP 20
%% - OTP 21
+%% - OTP 22
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
%% stated previous versions.
%%
{"%VSN%",
- [{<<"^5\\.3$">>,[restart_new_emulator]},
- {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^5\\.4$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^6\\.0$">>,[restart_new_emulator]},
+ [{<<"^6\\.0$">>,[restart_new_emulator]},
{<<"^6\\.0\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.0\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.1$">>,[restart_new_emulator]},
@@ -45,16 +37,9 @@
{<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.3$">>,[restart_new_emulator]},
- {<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
- [{<<"^5\\.3$">>,[restart_new_emulator]},
- {<<"^5\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^5\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^5\\.4$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^5\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^6\\.0$">>,[restart_new_emulator]},
+ {<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.3\\.1(?:\\.[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]},
{<<"^6\\.1$">>,[restart_new_emulator]},
@@ -64,4 +49,5 @@
{<<"^6\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^6\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^6\\.3$">>,[restart_new_emulator]},
- {<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
+ {<<"^6\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
+ {<<"^6\\.3\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
diff --git a/lib/kernel/test/inet_res_SUITE.erl b/lib/kernel/test/inet_res_SUITE.erl
index cbec8d430c..6b545fa414 100644
--- a/lib/kernel/test/inet_res_SUITE.erl
+++ b/lib/kernel/test/inet_res_SUITE.erl
@@ -46,15 +46,16 @@
%% a temporary local nameserver BIND 8 or 9 that must be installed
%% on your machine.
%%
-%% For example, on Ubuntu 14.04, as root:
+%% For example, on Ubuntu 16.04 / 18.04, as root:
%% apt-get install bind9
%% Now, that is not enough since Apparmor will not allow
%% the nameserver daemon /usr/sbin/named to read from the test directory.
%% Assuming that you run tests in /ldisk/daily_build, and still on
-%% Ubuntu 14.04, make /usr/apparmor.d/local/usr.sbin.named contain:
+%% Ubuntu 14.04, make /etc/apparmor.d/local/usr.sbin.named contain:
%% /ldisk/daily_build/** r,
%% And yes; the trailing comma must be there...
-
+%% And yes; create the file if it does not exist.
+%% And yes; restart the apparmor daemon using "service apparmor restart"
suite() ->
diff --git a/lib/kernel/vsn.mk b/lib/kernel/vsn.mk
index b1ae513223..765e890157 100644
--- a/lib/kernel/vsn.mk
+++ b/lib/kernel/vsn.mk
@@ -1 +1 @@
-KERNEL_VSN = 6.3.1
+KERNEL_VSN = 6.4
diff --git a/lib/megaco/doc/src/notes.xml b/lib/megaco/doc/src/notes.xml
index b697c3f631..6f33ae390c 100644
--- a/lib/megaco/doc/src/notes.xml
+++ b/lib/megaco/doc/src/notes.xml
@@ -37,7 +37,30 @@
section is the version number of Megaco.</p>
- <section><title>Megaco 3.18.4</title>
+ <section><title>Megaco 3.18.5</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Minor updates to build system necessary due to move of
+ configuration of <c>crypto</c> application.</p>
+ <p>
+ Own Id: OTP-15262 Aux Id: OTP-15129 </p>
+ </item>
+ <item>
+ <p>
+ Minor adjustments made to build system for parallel
+ configure.</p>
+ <p>
+ Own Id: OTP-15340 Aux Id: OTP-14625 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Megaco 3.18.4</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/megaco/vsn.mk b/lib/megaco/vsn.mk
index f4c82c537a..843a3dccc5 100644
--- a/lib/megaco/vsn.mk
+++ b/lib/megaco/vsn.mk
@@ -19,6 +19,6 @@
# %CopyrightEnd%
APPLICATION = megaco
-MEGACO_VSN = 3.18.4
+MEGACO_VSN = 3.18.5
PRE_VSN =
APP_VSN = "$(APPLICATION)-$(MEGACO_VSN)$(PRE_VSN)"
diff --git a/lib/mnesia/doc/src/notes.xml b/lib/mnesia/doc/src/notes.xml
index 01d1666b8d..2d38e4d01c 100644
--- a/lib/mnesia/doc/src/notes.xml
+++ b/lib/mnesia/doc/src/notes.xml
@@ -39,7 +39,51 @@
thus constitutes one section in this document. The title of each
section is the version number of Mnesia.</p>
- <section><title>Mnesia 4.15.6</title>
+ <section><title>Mnesia 4.16</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Optimize mnesia:read/1 if data have been written in the
+ same transaction.</p>
+ <p>
+ Own Id: OTP-15550 Aux Id: PR-2029 </p>
+ </item>
+ <item>
+ <p>
+ Fixed bugs in table index plugin handling.</p>
+ <p>
+ Own Id: OTP-15689 Aux Id: PR-1695 ERL-556 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Optimized dumping of tables with plugin backends.</p>
+ <p>
+ Own Id: OTP-15588 Aux Id: PR-2102 </p>
+ </item>
+ <item>
+ <p>
+ Include stacktrace in exception if a dirty activity
+ errors, thus if user have matched on the error thrown it
+ may not match any more.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15804 Aux Id: PR-2216 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Mnesia 4.15.6</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/mnesia/src/mnesia_tm.erl b/lib/mnesia/src/mnesia_tm.erl
index cbf7db28f0..8b79fca1d7 100644
--- a/lib/mnesia/src/mnesia_tm.erl
+++ b/lib/mnesia/src/mnesia_tm.erl
@@ -743,8 +743,9 @@ non_transaction(OldState, Fun, Args, ActivityKind, Mod) ->
{aborted, Reason} -> mnesia:abort(Reason);
Res -> Res
catch
- throw:Throw -> throw(Throw);
- _:Reason -> exit(Reason)
+ throw:Throw -> throw(Throw);
+ error:Reason:ST -> exit({Reason, ST});
+ exit:Reason -> exit(Reason)
after
case OldState of
undefined -> erase(mnesia_activity_state);
diff --git a/lib/mnesia/test/mnesia_dirty_access_test.erl b/lib/mnesia/test/mnesia_dirty_access_test.erl
index 67ef1fe901..984f43582c 100644
--- a/lib/mnesia/test/mnesia_dirty_access_test.erl
+++ b/lib/mnesia/test/mnesia_dirty_access_test.erl
@@ -48,7 +48,7 @@
del_table_copy_1/1, del_table_copy_2/1, del_table_copy_3/1,
add_table_copy_1/1, add_table_copy_2/1, add_table_copy_3/1,
add_table_copy_4/1, move_table_copy_1/1, move_table_copy_2/1,
- move_table_copy_3/1, move_table_copy_4/1]).
+ move_table_copy_3/1, move_table_copy_4/1, dirty_error_stacktrace/1]).
-export([update_trans/3]).
@@ -64,7 +64,7 @@ all() ->
{group, dirty_update_counter}, {group, dirty_delete},
{group, dirty_delete_object},
{group, dirty_match_object}, {group, dirty_index},
- {group, dirty_iter}, {group, admin_tests}].
+ {group, dirty_iter}, {group, admin_tests}, dirty_error_stacktrace].
groups() ->
[{dirty_write, [],
@@ -114,6 +114,36 @@ init_per_group(_GroupName, Config) ->
end_per_group(_GroupName, Config) ->
Config.
+%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+%% Errors in dirty activity should have stacktrace
+dirty_error_stacktrace(Config) ->
+ %% Custom errors should have stacktrace
+ try
+ mnesia:async_dirty(fun() -> error(custom_error) end)
+ catch
+ exit:{custom_error, _} -> ok
+ end,
+
+ %% Undef error should have unknown module and function in the stacktrace
+ try
+ mnesia:async_dirty(fun() -> unknown_module:unknown_fun(arg) end)
+ catch
+ exit:{undef, [{unknown_module, unknown_fun, [arg], []} | _]} -> ok
+ end,
+
+ %% Exists don't have stacktrace
+ try
+ mnesia:async_dirty(fun() -> exit(custom_error) end)
+ catch
+ exit:custom_error -> ok
+ end,
+
+ %% Aborts don't have a stacktrace (unfortunately)
+ try
+ mnesia:async_dirty(fun() -> mnesia:abort(custom_abort) end)
+ catch
+ exit:{aborted, custom_abort} -> ok
+ end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Write records dirty
diff --git a/lib/mnesia/vsn.mk b/lib/mnesia/vsn.mk
index 781a4830a0..aa5d9adb6d 100644
--- a/lib/mnesia/vsn.mk
+++ b/lib/mnesia/vsn.mk
@@ -1 +1 @@
-MNESIA_VSN = 4.15.6
+MNESIA_VSN = 4.16
diff --git a/lib/observer/doc/src/notes.xml b/lib/observer/doc/src/notes.xml
index 2d914f8c61..f05e58dc21 100644
--- a/lib/observer/doc/src/notes.xml
+++ b/lib/observer/doc/src/notes.xml
@@ -32,6 +32,23 @@
<p>This document describes the changes made to the Observer
application.</p>
+<section><title>Observer 2.9.1</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Observer 2.9</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/observer/vsn.mk b/lib/observer/vsn.mk
index 0e9c8b302c..c16c43f942 100644
--- a/lib/observer/vsn.mk
+++ b/lib/observer/vsn.mk
@@ -1 +1 @@
-OBSERVER_VSN = 2.9
+OBSERVER_VSN = 2.9.1
diff --git a/lib/odbc/doc/src/notes.xml b/lib/odbc/doc/src/notes.xml
index 696fcaa479..8d708162e4 100644
--- a/lib/odbc/doc/src/notes.xml
+++ b/lib/odbc/doc/src/notes.xml
@@ -32,7 +32,23 @@
<p>This document describes the changes made to the odbc application.
</p>
- <section><title>ODBC 2.12.3</title>
+ <section><title>ODBC 2.12.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Minor adjustments made to build system for parallel
+ configure.</p>
+ <p>
+ Own Id: OTP-15340 Aux Id: OTP-14625 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>ODBC 2.12.3</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/odbc/vsn.mk b/lib/odbc/vsn.mk
index ff023e666b..df6db09f2f 100644
--- a/lib/odbc/vsn.mk
+++ b/lib/odbc/vsn.mk
@@ -1 +1 @@
-ODBC_VSN = 2.12.3
+ODBC_VSN = 2.12.4
diff --git a/lib/os_mon/doc/src/notes.xml b/lib/os_mon/doc/src/notes.xml
index 64e9f281e3..1f169263e9 100644
--- a/lib/os_mon/doc/src/notes.xml
+++ b/lib/os_mon/doc/src/notes.xml
@@ -31,6 +31,35 @@
</header>
<p>This document describes the changes made to the OS_Mon application.</p>
+<section><title>Os_Mon 2.5</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Fix typespec of <c>cpu_sup:util()</c>.</p>
+ <p>
+ Own Id: OTP-15770 Aux Id: PR-2208 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The application otp_mibs has been removed from OTP. Some
+ of its components (mibs) have been moved to other apps
+ (snmp), or removed completely (os_mon).</p>
+ <p>
+ Own Id: OTP-14984 Aux Id: OTP-15329 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Os_Mon 2.4.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/os_mon/vsn.mk b/lib/os_mon/vsn.mk
index 9713f6bc6b..845443d329 100644
--- a/lib/os_mon/vsn.mk
+++ b/lib/os_mon/vsn.mk
@@ -1 +1 @@
-OS_MON_VSN = 2.4.7
+OS_MON_VSN = 2.5
diff --git a/lib/public_key/doc/src/notes.xml b/lib/public_key/doc/src/notes.xml
index f6bc0dc797..d13c9a520a 100644
--- a/lib/public_key/doc/src/notes.xml
+++ b/lib/public_key/doc/src/notes.xml
@@ -35,6 +35,78 @@
<file>notes.xml</file>
</header>
+<section><title>Public_Key 1.6.7</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ RSA options passed to crypto for encrypt and decrypt with
+ public or private key.</p>
+ <p>
+ Own Id: OTP-15754 Aux Id: ERL-878 </p>
+ </item>
+ <item>
+ <p>
+ Fix dialyzer warnings caused by a faulty type
+ specification for digest_type().</p>
+ <p>
+ This change updates digest_type() and the functions
+ operating with this argument type to accept both 'sha1'
+ and 'sha' as digest_type().</p>
+ <p>
+ Own Id: OTP-15776</p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Add possibility to read PEM files encrypted with old PEM
+ encryption using AES-256</p>
+ <p>
+ Own Id: OTP-13726</p>
+ </item>
+ <item>
+ <p>
+ Relax decoding of certificates to so that "harmless"
+ third party encoding errors may be accepted but not
+ created by the public_key application. This adds
+ acceptance of using an incorrect three character country
+ code, the PKIX standard use two character country codes.
+ It is also accepted that the country code is utf8 encoded
+ but the specification says it should be ASCII.</p>
+ <p>
+ Own Id: OTP-15687 Aux Id: PR-2162 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Public_Key 1.6.6</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Back port of bug fix ERL-893 from OTP-22 and document
+ enhancements that will solve dialyzer warnings for users
+ of the ssl application.</p>
+ <p>
+ This change also affects public_key, eldap (and inet
+ doc).</p>
+ <p>
+ Own Id: OTP-15785 Aux Id: ERL-929, ERL-893, PR-2215 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Public_Key 1.6.5</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/public_key/vsn.mk b/lib/public_key/vsn.mk
index 11c06fb158..a5e4ec8d5a 100644
--- a/lib/public_key/vsn.mk
+++ b/lib/public_key/vsn.mk
@@ -1 +1 @@
-PUBLIC_KEY_VSN = 1.6.5
+PUBLIC_KEY_VSN = 1.6.7
diff --git a/lib/reltool/doc/src/notes.xml b/lib/reltool/doc/src/notes.xml
index 165ae6db6a..ee00b6086d 100644
--- a/lib/reltool/doc/src/notes.xml
+++ b/lib/reltool/doc/src/notes.xml
@@ -38,7 +38,40 @@
thus constitutes one section in this document. The title of each
section is the version number of Reltool.</p>
- <section><title>Reltool 0.7.8</title>
+ <section><title>Reltool 0.8</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ A new element, <c>Opts</c>, can now be included in a
+ <c>rel</c> tuple in the reltool release specific
+ configuration format: {rel, Name, Vsn, RelApps, Opts}.</p>
+ <p>
+ This supports the use of <c>{rel, Name, Vsn, RelApps,
+ [{load_dot_erlang, false}]}</c> to prevent the boot
+ script from running the <c>.erlang</c> file.</p>
+ <p>
+ The incompatibilities are as follows:</p>
+ <p>
+ * The return from <c>reltool:get_config/1</c> and
+ <c>reltool:get_config/3</c> includes the new <c>rel</c>
+ tuple for all releases where the <c>load_dot_erlang</c>
+ option is set to <c>false</c>.<br/> * The return from
+ <c>reltool:get_config/3</c> includes the new <c>rel</c>
+ tuple for ALL releases if the <c>InclDefs</c> parameter
+ is set to <c>true</c>.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15571</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>Reltool 0.7.8</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/reltool/vsn.mk b/lib/reltool/vsn.mk
index a649a3e0c0..c5aacfba38 100644
--- a/lib/reltool/vsn.mk
+++ b/lib/reltool/vsn.mk
@@ -1 +1 @@
-RELTOOL_VSN = 0.7.8
+RELTOOL_VSN = 0.8
diff --git a/lib/runtime_tools/doc/src/notes.xml b/lib/runtime_tools/doc/src/notes.xml
index 58a2a66c4b..1b94c3e6d9 100644
--- a/lib/runtime_tools/doc/src/notes.xml
+++ b/lib/runtime_tools/doc/src/notes.xml
@@ -32,6 +32,22 @@
<p>This document describes the changes made to the Runtime_Tools
application.</p>
+<section><title>Runtime_Tools 1.13.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Minor updates to build system necessary due to move of
+ configuration of <c>crypto</c> application.</p>
+ <p>
+ Own Id: OTP-15262 Aux Id: OTP-15129 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Runtime_Tools 1.13.2</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/runtime_tools/vsn.mk b/lib/runtime_tools/vsn.mk
index fa2f338ec2..3f38574be4 100644
--- a/lib/runtime_tools/vsn.mk
+++ b/lib/runtime_tools/vsn.mk
@@ -1 +1 @@
-RUNTIME_TOOLS_VSN = 1.13.2
+RUNTIME_TOOLS_VSN = 1.13.3
diff --git a/lib/sasl/doc/src/notes.xml b/lib/sasl/doc/src/notes.xml
index 982c874117..a1ae404776 100644
--- a/lib/sasl/doc/src/notes.xml
+++ b/lib/sasl/doc/src/notes.xml
@@ -31,6 +31,23 @@
</header>
<p>This document describes the changes made to the SASL application.</p>
+<section><title>SASL 3.4</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Change the first module called by erts to be named
+ erl_init instead of otp_ring0. systools in sasl have been
+ updated to reflect this change.</p>
+ <p>
+ Own Id: OTP-15336 Aux Id: PR-1825 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SASL 3.3</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src
index 26127eae84..22a9027b7c 100644
--- a/lib/sasl/src/sasl.appup.src
+++ b/lib/sasl/src/sasl.appup.src
@@ -19,27 +19,21 @@
%%
%% We allow upgrade from, and downgrade to all previous
%% versions from the following OTP releases:
-%% - OTP 20
%% - OTP 21
+%% - OTP 22
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
%% stated previous versions.
%%
{"%VSN%",
- [{<<"^3\\.0\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.1$">>,[restart_new_emulator]},
- {<<"^3\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.1\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.2$">>,[restart_new_emulator]},
+ [{<<"^3\\.2$">>,[restart_new_emulator]},
{<<"^3\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}],
- [{<<"^3\\.0\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.1$">>,[restart_new_emulator]},
- {<<"^3\\.1\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.1\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.1\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.2$">>,[restart_new_emulator]},
+ {<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.3$">>,[restart_new_emulator]},
+ {<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
+ [{<<"^3\\.2$">>,[restart_new_emulator]},
{<<"^3\\.2\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]}]}.
+ {<<"^3\\.2\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
+ {<<"^3\\.3$">>,[restart_new_emulator]},
+ {<<"^3\\.3\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}]}.
diff --git a/lib/sasl/vsn.mk b/lib/sasl/vsn.mk
index c1f80752a7..8838b514da 100644
--- a/lib/sasl/vsn.mk
+++ b/lib/sasl/vsn.mk
@@ -1 +1 @@
-SASL_VSN = 3.3
+SASL_VSN = 3.4
diff --git a/lib/snmp/doc/src/notes.xml b/lib/snmp/doc/src/notes.xml
index a6c3d57148..780e0cae76 100644
--- a/lib/snmp/doc/src/notes.xml
+++ b/lib/snmp/doc/src/notes.xml
@@ -34,7 +34,32 @@
</header>
- <section><title>SNMP 5.2.12</title>
+ <section><title>SNMP 5.3</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ The application otp_mibs has been removed from OTP. Some
+ of its components (mibs) have been moved to other apps
+ (snmp), or removed completely (os_mon).</p>
+ <p>
+ Own Id: OTP-14984 Aux Id: OTP-15329 </p>
+ </item>
+ <item>
+ <p>
+ [snmp|agent] Add a get-mechanism callback module (and a
+ corresponding behaviour). The agent calls this module to
+ handle each get (get, get-next and get-bulk) request.</p>
+ <p>
+ Own Id: OTP-15691 Aux Id: ERIERL-324 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SNMP 5.2.12</title>
<section><title>Fixed Bugs and Malfunctions</title>
<list>
diff --git a/lib/ssh/doc/src/notes.xml b/lib/ssh/doc/src/notes.xml
index 78990c48f2..60f20c7c3f 100644
--- a/lib/ssh/doc/src/notes.xml
+++ b/lib/ssh/doc/src/notes.xml
@@ -30,6 +30,21 @@
<file>notes.xml</file>
</header>
+<section><title>Ssh 4.7.7</title>
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ SSH uses the new crypto API.</p>
+ <p>
+ Own Id: OTP-15673</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Ssh 4.7.6</title>
<section><title>Improvements and New Features</title>
diff --git a/lib/ssh/src/ssh.app.src b/lib/ssh/src/ssh.app.src
index 7449405d20..2193c14611 100644
--- a/lib/ssh/src/ssh.app.src
+++ b/lib/ssh/src/ssh.app.src
@@ -44,7 +44,7 @@
{env, []},
{mod, {ssh_app, []}},
{runtime_dependencies, [
- "crypto-@OTP-15644@",
+ "crypto-4.5",
"erts-9.0",
"kernel-5.3",
"public_key-1.6.1",
diff --git a/lib/ssh/vsn.mk b/lib/ssh/vsn.mk
index 837da27ab0..bb87dd388c 100644
--- a/lib/ssh/vsn.mk
+++ b/lib/ssh/vsn.mk
@@ -1,4 +1,4 @@
#-*-makefile-*- ; force emacs to enter makefile-mode
-SSH_VSN = 4.7.6
+SSH_VSN = 4.7.7
APP_VSN = "ssh-$(SSH_VSN)"
diff --git a/lib/ssl/doc/src/notes.xml b/lib/ssl/doc/src/notes.xml
index f0231da2ad..5c213402f4 100644
--- a/lib/ssl/doc/src/notes.xml
+++ b/lib/ssl/doc/src/notes.xml
@@ -27,6 +27,162 @@
</header>
<p>This document describes the changes made to the SSL application.</p>
+<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>
+ <list>
+ <item>
+ <p>
+ The distribution handshake with TLS distribution
+ (<c>inet_tls_dist</c>) does now utilize the socket option
+ <c>{nodelay, true}</c>, which decreases the distribution
+ setup time significantly.</p>
+ <p>
+ Own Id: OTP-14792</p>
+ </item>
+ <item>
+ <p>
+ Correct shutdown reason to avoid an incorrect crash
+ report</p>
+ <p>
+ Own Id: OTP-15710 Aux Id: ERL-893 </p>
+ </item>
+ <item>
+ <p>
+ Enhance documentation and type specifications.</p>
+ <p>
+ Own Id: OTP-15746 Aux Id: ERIERL-333 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ TLS-1.0, TLS-1.1 and DTLS-1.0 are now considered legacy
+ and not supported by default</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-14865</p>
+ </item>
+ <item>
+ <p>
+ Use new logger API in ssl. Introduce log levels and
+ verbose debug logging for SSL.</p>
+ <p>
+ Own Id: OTP-15055</p>
+ </item>
+ <item>
+ <p>
+ Add new API function str_to_suite/1, cipher_suites/3
+ (list cipher suites as rfc or OpenSSL name strings) and
+ suite_to_openssl_str/1</p>
+ <p>
+ Own Id: OTP-15483 Aux Id: ERL-924 </p>
+ </item>
+ <item>
+ <p>
+ Basic support for TLS 1.3 Server for experimental use.
+ The client is not yet functional, for more information
+ see the Standards Compliance chapter of the User's Guide.</p>
+ <p>
+ Own Id: OTP-15591</p>
+ </item>
+ <item>
+ <p>
+ Add support for PSK CCM ciphers from RFC 6655</p>
+ <p>
+ Own Id: OTP-15626</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>SSL 9.2.3</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-15802 Aux Id: ERL-934 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>
+ Back port of bug fix ERL-893 from OTP-22 and document
+ enhancements that will solve dialyzer warnings for users
+ of the ssl application.</p>
+ <p>
+ This change also affects public_key, eldap (and inet
+ doc).</p>
+ <p>
+ Own Id: OTP-15785 Aux Id: ERL-929, ERL-893, PR-2215 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>SSL 9.2.2</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/ssl/doc/src/ssl.xml b/lib/ssl/doc/src/ssl.xml
index d626748af6..3aa6e09c2c 100644
--- a/lib/ssl/doc/src/ssl.xml
+++ b/lib/ssl/doc/src/ssl.xml
@@ -1132,6 +1132,15 @@ fun(srp, Username :: string(), UserState :: term()) ->
</func>
<func>
+ <name name="cipher_suites" arity="3" since="OTP 22.0"/>
+ <fsummary>Returns a list of RFC or OpenSSL names</fsummary>
+ <desc><p>Same as <seealso marker="#cipher_suites-2">cipher_suites/2</seealso>
+ but lists RFC or OpenSSL string names instead of <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ </p>
+ </desc>
+ </func>
+
+ <func>
<name name="eccs" arity="0" since="OTP 19.2"/>
<name name="eccs" arity="1" since="OTP 19.2"/>
<fsummary>Returns a list of supported ECCs.</fsummary>
@@ -1563,12 +1572,36 @@ fun(srp, Username :: string(), UserState :: term()) ->
<p>Stops the SSL application.</p>
</desc>
</func>
+
+ <func>
+ <name since="OTP 22.0" name="str_to_suite" arity="1" />
+ <fsummary>Converts an RFC or OpenSSL name string to an erlang cipher suite format</fsummary>
+ <desc>
+ <p>Converts an RFC or OpenSSL name string to an
+ <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ Returns an error if the cipher suite is not supported or the name is not a valid cipher suite name.</p>
+ </desc>
+ </func>
<func>
+ <name since="OTP 22.0" name="suite_to_openssl_str" arity="1" />
+ <fsummary>Converts erlang cipher suite format to an OpenSSL name string.
+ </fsummary>
+ <desc>
+ <p>Converts <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ to OpenSSL name string. </p>
+
+ <p>PRE TLS-1.3 these names differ for RFC names</p>
+
+ </desc>
+ </func>
+
+ <func>
<name since="OTP 21.0" name="suite_to_str" arity="1" clause_i="1" />
- <fsummary>Returns the string representation of a cipher suite.</fsummary>
+ <fsummary>Converts an erlang cipher suite to an RFC name string.</fsummary>
<desc>
- <p>Returns the string representation of a cipher suite.</p>
+ <p>Converts <seealso marker="#type-erl_cipher_suite">erl_cipher_suite()</seealso>
+ to RFC name string.</p>
</desc>
</func>
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.erl b/lib/ssl/src/ssl.erl
index e3bb4df1ac..6af65e09f2 100644
--- a/lib/ssl/src/ssl.erl
+++ b/lib/ssl/src/ssl.erl
@@ -35,27 +35,69 @@
-include("ssl_srp.hrl").
%% Application handling
--export([start/0, start/1, stop/0, clear_pem_cache/0]).
+-export([start/0,
+ start/1,
+ stop/0,
+ clear_pem_cache/0]).
%% Socket handling
--export([connect/3, connect/2, connect/4,
- listen/2, transport_accept/1, transport_accept/2,
- handshake/1, handshake/2, handshake/3, handshake_continue/2,
- handshake_continue/3, handshake_cancel/1,
- ssl_accept/1, ssl_accept/2, ssl_accept/3,
- controlling_process/2, peername/1, peercert/1, sockname/1,
- close/1, close/2, shutdown/2, recv/2, recv/3, send/2,
- getopts/2, setopts/2, getstat/1, getstat/2
+-export([connect/3,
+ connect/2,
+ connect/4,
+ listen/2,
+ transport_accept/1,
+ transport_accept/2,
+ handshake/1,
+ handshake/2,
+ handshake/3,
+ handshake_continue/2,
+ handshake_continue/3,
+ handshake_cancel/1,
+ ssl_accept/1,
+ ssl_accept/2,
+ ssl_accept/3,
+ controlling_process/2,
+ peername/1,
+ peercert/1,
+ sockname/1,
+ close/1,
+ close/2,
+ shutdown/2,
+ recv/2,
+ recv/3,
+ send/2,
+ getopts/2,
+ setopts/2,
+ getstat/1,
+ getstat/2
]).
%% SSL/TLS protocol handling
--export([cipher_suites/0, cipher_suites/1, cipher_suites/2, filter_cipher_suites/2,
- prepend_cipher_suites/2, append_cipher_suites/2,
- eccs/0, eccs/1, versions/0, groups/0, groups/1,
- format_error/1, renegotiate/1, prf/5, negotiated_protocol/1,
- connection_information/1, connection_information/2]).
+-export([cipher_suites/0,
+ cipher_suites/1,
+ cipher_suites/2,
+ cipher_suites/3,
+ filter_cipher_suites/2,
+ prepend_cipher_suites/2,
+ append_cipher_suites/2,
+ eccs/0,
+ eccs/1,
+ versions/0,
+ groups/0,
+ groups/1,
+ format_error/1,
+ renegotiate/1,
+ prf/5,
+ negotiated_protocol/1,
+ connection_information/1,
+ connection_information/2]).
%% Misc
--export([handle_options/2, tls_version/1, new_ssl_options/3, suite_to_str/1]).
+-export([handle_options/2,
+ tls_version/1,
+ new_ssl_options/3,
+ suite_to_str/1,
+ suite_to_openssl_str/1,
+ str_to_suite/1]).
-deprecated({ssl_accept, 1, eventually}).
-deprecated({ssl_accept, 2, eventually}).
@@ -945,6 +987,29 @@ cipher_suites(Base, Version) ->
[ssl_cipher_format:suite_bin_to_map(Suite) || Suite <- supported_suites(Base, Version)].
%%--------------------------------------------------------------------
+-spec cipher_suites(Supported, Version, rfc | openssl) -> [string()] when
+ Supported :: default | all | anonymous,
+ Version :: protocol_version().
+
+%% Description: Returns all default and all supported cipher suites for a
+%% TLS/DTLS version
+%%--------------------------------------------------------------------
+cipher_suites(Base, Version, StringType) when Version == 'tlsv1.2';
+ Version == 'tlsv1.1';
+ Version == tlsv1;
+ Version == sslv3 ->
+ cipher_suites(Base, tls_record:protocol_version(Version), StringType);
+cipher_suites(Base, Version, StringType) when Version == 'dtlsv1.2';
+ Version == 'dtlsv1'->
+ cipher_suites(Base, dtls_record:protocol_version(Version), StringType);
+cipher_suites(Base, Version, rfc) ->
+ [ssl_cipher_format:suite_map_to_str(ssl_cipher_format:suite_bin_to_map(Suite))
+ || Suite <- supported_suites(Base, Version)];
+cipher_suites(Base, Version, openssl) ->
+ [ssl_cipher_format:suite_map_to_openssl_str(ssl_cipher_format:suite_bin_to_map(Suite))
+ || Suite <- supported_suites(Base, Version)].
+
+%%--------------------------------------------------------------------
-spec filter_cipher_suites(Suites, Filters) -> Ciphers when
Suites :: ciphers(),
Filters :: cipher_filters(),
@@ -1325,9 +1390,39 @@ tls_version({254, _} = Version) ->
suite_to_str(Cipher) ->
ssl_cipher_format:suite_map_to_str(Cipher).
+%%--------------------------------------------------------------------
+-spec suite_to_openssl_str(CipherSuite) -> string() when
+ CipherSuite :: erl_cipher_suite().
+%%
+%% Description: Return the string representation of a cipher suite.
+%%--------------------------------------------------------------------
+suite_to_openssl_str(Cipher) ->
+ ssl_cipher_format:suite_map_to_openssl_str(Cipher).
+
+%%
+%%--------------------------------------------------------------------
+-spec str_to_suite(CipherSuiteName) -> erl_cipher_suite() when
+ CipherSuiteName :: string() | {error, {not_recognized, CipherSuiteName :: string()}}.
+%%
+%% Description: Return the map representation of a cipher suite.
+%%--------------------------------------------------------------------
+str_to_suite(CipherSuiteName) ->
+ try
+ %% Note in TLS-1.3 OpenSSL conforms to RFC names
+ %% so if CipherSuiteName starts with TLS this
+ %% function will call ssl_cipher_format:suite_str_to_map
+ %% so both RFC names and legacy OpenSSL names of supported
+ %% cipher suites will be handled
+ ssl_cipher_format:suite_openssl_str_to_map(CipherSuiteName)
+ catch
+ _:_ ->
+ {error, {not_recognized, CipherSuiteName}}
+ end.
+
%%%--------------------------------------------------------------
%%% Internal functions
%%%--------------------------------------------------------------------
+
%% Possible filters out suites not supported by crypto
available_suites(default) ->
Version = tls_record:highest_protocol_version([]),
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_format.erl b/lib/ssl/src/ssl_cipher_format.erl
index 577156a4b5..bca1022b5f 100644
--- a/lib/ssl/src/ssl_cipher_format.erl
+++ b/lib/ssl/src/ssl_cipher_format.erl
@@ -93,10 +93,16 @@ suite_str_to_map("TLS_EMPTY_RENEGOTIATION_INFO_SCSV") ->
mac => null,
prf => null};
suite_str_to_map(SuiteStr)->
- Str0 = string:trim(SuiteStr, leading, "TLS_"),
+ Str0 = string:prefix(SuiteStr, "TLS_"),
case string:split(Str0, "_WITH_") of
[Rest] ->
tls_1_3_suite_str_to_map(Rest);
+ [Prefix, Kex | Rest] when Prefix == "SPR";
+ Prefix == "PSK";
+ Prefix == "DHE";
+ Prefix == "ECDHE"
+ ->
+ pre_tls_1_3_suite_str_to_map(Prefix ++ "_" ++ Kex, Rest);
[Kex| Rest] ->
pre_tls_1_3_suite_str_to_map(Kex, Rest)
end.
@@ -108,9 +114,15 @@ suite_map_to_openssl_str(#{key_exchange := any,
suite_map_to_openssl_str(#{key_exchange := null} = Suite) ->
%% TLS_EMPTY_RENEGOTIATION_INFO_SCSV
suite_map_to_str(Suite);
+suite_map_to_openssl_str(#{key_exchange := rsa = Kex,
+ cipher := Cipher,
+ mac := Mac}) when Cipher == "des_cbc";
+ Cipher == "3des_ede_cbc" ->
+ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher))) ++
+ "-" ++ string:to_upper(atom_to_list(Mac));
suite_map_to_openssl_str(#{key_exchange := Kex,
- cipher := chacha20_poly1305 = Cipher,
- mac := aead}) ->
+ cipher := chacha20_poly1305 = Cipher,
+ mac := aead}) ->
openssl_suite_start(string:to_upper(atom_to_list(Kex)))
++ openssl_cipher_name(Kex, string:to_upper(atom_to_list(Cipher)));
suite_map_to_openssl_str(#{key_exchange := Kex,
@@ -130,6 +142,12 @@ suite_map_to_openssl_str(#{key_exchange := Kex,
suite_openssl_str_to_map("TLS_" ++ _ = SuiteStr) ->
suite_str_to_map(SuiteStr);
+suite_openssl_str_to_map("DES-CBC-SHA") ->
+ suite_str_to_map("TLS_RSA_WITH_DES_CBC_SHA");
+suite_openssl_str_to_map("DES-CBC3-SHA") ->
+ suite_str_to_map("TLS_RSA_WITH_3DES_EDE_CBC_SHA");
+suite_openssl_str_to_map("SRP-DSS-DES-CBC3-SHA") ->
+ suite_str_to_map("TLS_SRP_SHA_DSS_WITH_3DES_EDE_CBC_SHA");
suite_openssl_str_to_map("DHE-RSA-" ++ Rest) ->
suite_openssl_str_to_map("DHE-RSA", Rest);
suite_openssl_str_to_map("DHE-DSS-" ++ Rest) ->
@@ -164,6 +182,8 @@ suite_openssl_str_to_map("PSK-" ++ Rest) ->
suite_openssl_str_to_map("PSK", Rest);
suite_openssl_str_to_map("SRP-RSA-" ++ Rest) ->
suite_openssl_str_to_map("SRP-RSA", Rest);
+suite_openssl_str_to_map("SRP-DSS-" ++ Rest) ->
+ suite_openssl_str_to_map("SRP-DSS", Rest);
suite_openssl_str_to_map("SRP-" ++ Rest) ->
suite_openssl_str_to_map("SRP", Rest).
@@ -451,7 +471,7 @@ suite_bin_to_map(?TLS_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => psk,
cipher => aes_256_cbc,
mac => sha384,
- prf => default_prf};
+ prf => sha384};
suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => dhe_psk,
cipher => aes_128_cbc,
@@ -461,7 +481,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => dhe_psk,
cipher => aes_256_cbc,
mac => sha384,
- prf => default_prf};
+ prf => sha384};
suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_128_CBC_SHA256) ->
#{key_exchange => rsa_psk,
cipher => aes_128_cbc,
@@ -471,7 +491,7 @@ suite_bin_to_map(?TLS_RSA_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => rsa_psk,
cipher => aes_256_cbc,
mac => sha384,
- prf => default_prf};
+ prf => sha384};
suite_bin_to_map(?TLS_PSK_WITH_NULL_SHA256) ->
#{key_exchange => psk,
cipher => null,
@@ -481,7 +501,7 @@ suite_bin_to_map(?TLS_PSK_WITH_NULL_SHA384) ->
#{key_exchange => psk,
cipher => null,
mac => sha384,
- prf => default_prf};
+ prf => sha384};
suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA256) ->
#{key_exchange => dhe_psk,
cipher => null,
@@ -491,7 +511,7 @@ suite_bin_to_map(?TLS_DHE_PSK_WITH_NULL_SHA384) ->
#{key_exchange => dhe_psk,
cipher => null,
mac => sha384,
- prf => default_prf};
+ prf => sha384};
suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA256) ->
#{key_exchange => rsa_psk,
cipher => null,
@@ -501,7 +521,7 @@ suite_bin_to_map(?TLS_RSA_PSK_WITH_NULL_SHA384) ->
#{key_exchange => rsa_psk,
cipher => null,
mac => sha384,
- prf => default_prf};
+ prf => sha384};
%%% ECDHE PSK Cipher Suites RFC 5489
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_RC4_128_SHA) ->
#{key_exchange => ecdhe_psk,
@@ -532,7 +552,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_256_CBC_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => aes_256_cbc,
mac => sha384,
- prf => default_prf};
+ prf => sha384};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => null,
@@ -541,7 +561,7 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA256) ->
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_NULL_SHA384) ->
#{key_exchange => ecdhe_psk,
cipher => null, mac => sha384,
- prf => default_prf};
+ prf => sha384};
%%% ECDHE_PSK with AES-GCM and AES-CCM Cipher Suites, draft-ietf-tls-ecdhe-psk-aead-05
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_GCM_SHA256) ->
#{key_exchange => ecdhe_psk,
@@ -557,12 +577,12 @@ suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_ccm,
mac => null,
- prf =>sha256};
+ prf => sha256};
suite_bin_to_map(?TLS_ECDHE_PSK_WITH_AES_128_CCM_8_SHA256) ->
#{key_exchange => ecdhe_psk,
cipher => aes_128_ccm_8,
mac => null,
- prf =>sha256};
+ prf => sha256};
%%% SRP Cipher Suites RFC 5054
suite_bin_to_map(?TLS_SRP_SHA_WITH_3DES_EDE_CBC_SHA) ->
#{key_exchange => srp_anon,
@@ -1704,7 +1724,7 @@ suite_map_to_bin(#{key_exchange := any,
tls_1_3_suite_str_to_map(CipherStr) ->
- {Cipher, Mac, Prf} = cipher_str_to_algs(CipherStr, ""),
+ {Cipher, Mac, Prf} = cipher_str_to_algs(any, CipherStr, ""),
#{key_exchange => any,
mac => Mac,
cipher => Cipher,
@@ -1714,37 +1734,56 @@ tls_1_3_suite_str_to_map(CipherStr) ->
pre_tls_1_3_suite_str_to_map(KexStr, Rest) ->
Kex = algo_str_to_atom(KexStr),
[CipherStr, AlgStr] = string:split(Rest, "_", trailing),
- {Cipher, Mac, Prf} = cipher_str_to_algs(CipherStr, AlgStr),
+ {Cipher, Mac, Prf} = cipher_str_to_algs(Kex, CipherStr, AlgStr),
#{key_exchange => Kex,
mac => Mac,
cipher => Cipher,
prf => Prf
}.
-cipher_str_to_algs(CipherStr, "CCM"= End) -> %% PRE TLS 1.3
+cipher_str_to_algs(_, CipherStr, "CCM"= End) -> %% PRE TLS 1.3
Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
{Cipher, aead, sha256};
-cipher_str_to_algs(CipherStr, "8" = End) -> %% PRE TLS 1.3
+cipher_str_to_algs(_, CipherStr, "8" = End) -> %% PRE TLS 1.3
Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
{Cipher, aead, sha256};
-cipher_str_to_algs(CipherStr, "CHACHA20_POLY1305" = End) -> %% PRE TLS 1.3
+cipher_str_to_algs(_, CipherStr, "CHACHA20_POLY1305" = End) -> %% PRE TLS 1.3
Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
{Cipher, aead, sha256};
-cipher_str_to_algs(CipherStr0, "") -> %% TLS 1.3
+cipher_str_to_algs(_, CipherStr0, "") -> %% TLS 1.3
[CipherStr, AlgStr] = string:split(CipherStr0, "_", trailing),
Hash = algo_str_to_atom(AlgStr),
Cipher = algo_str_to_atom(CipherStr),
{Cipher, aead, Hash};
-cipher_str_to_algs(CipherStr, HashStr) -> %% PRE TLS 1.3
+cipher_str_to_algs(Kex, CipherStr, HashStr) -> %% PRE TLS 1.3
Hash = algo_str_to_atom(HashStr),
Cipher = algo_str_to_atom(CipherStr),
case is_aead_cipher(CipherStr) of
true ->
{Cipher, aead, Hash};
false ->
- {Cipher, Hash, default_prf}
+ {Cipher, Hash, default_prf(Kex, Hash)}
end.
+default_prf(_, md5) ->
+ default_prf;
+default_prf(_, sha) ->
+ default_prf;
+default_prf(ecdhe_ecdsa, sha256) ->
+ sha256;
+default_prf(ecdhe_rsa, sha256) ->
+ sha256;
+default_prf(dhe_rsa, sha256) ->
+ default_prf;
+default_prf(dhe_dss, sha256) ->
+ default_prf;
+default_prf(rsa, sha256) ->
+ default_prf;
+default_prf(rsa_psk, sha256) ->
+ default_prf;
+default_prf(_, Hash) ->
+ Hash.
+
%% PRE TLS 1.3
is_aead_cipher("CHACHA20_POLY1305") ->
true;
@@ -1762,10 +1801,13 @@ openssl_is_aead_cipher(CipherStr) ->
false
end.
+algo_str_to_atom("SRP_SHA_DSS") ->
+ srp_dss;
algo_str_to_atom(AlgoStr) ->
erlang:list_to_existing_atom(string:to_lower(AlgoStr)).
-
+openssl_cipher_name(_, "3DES_EDE_CBC" ++ _) ->
+ "DES-CBC3";
openssl_cipher_name(Kex, "AES_128_CBC" ++ _ = CipherStr) when Kex == rsa;
Kex == dhe_rsa;
Kex == ecdhe_rsa;
@@ -1828,6 +1870,10 @@ cipher_name_from_openssl("AES128-GCM") ->
"AES_128_GCM";
cipher_name_from_openssl("AES256-GCM") ->
"AES_256_GCM";
+cipher_name_from_openssl("DES-CBC") ->
+ "DES_CBC";
+cipher_name_from_openssl("DES-CBC3") ->
+ "3DES_EDE_CBC";
cipher_name_from_openssl("RC4") ->
"RC4_128";
cipher_name_from_openssl(Str) ->
@@ -1842,7 +1888,7 @@ openssl_name_concat(Str0) ->
suite_openssl_str_to_map(Kex0, Rest) ->
Kex = algo_str_to_atom(kex_name_from_openssl(Kex0)),
[CipherStr, AlgStr] = string:split(Rest, "-", trailing),
- {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(CipherStr, AlgStr),
+ {Cipher, Mac, Prf} = openssl_cipher_str_to_algs(Kex, CipherStr, AlgStr),
#{key_exchange => Kex,
mac => Mac,
cipher => Cipher,
@@ -1850,31 +1896,24 @@ suite_openssl_str_to_map(Kex0, Rest) ->
}.
%% Does only need own implementation PRE TLS 1.3
-openssl_cipher_str_to_algs(CipherStr, "CCM"= End) ->
+openssl_cipher_str_to_algs(_, CipherStr, "CCM"= End) ->
Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
{Cipher, aead, sha256};
-openssl_cipher_str_to_algs(CipherStr, "8" = End) ->
+openssl_cipher_str_to_algs(_, CipherStr, "8" = End) ->
Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
{Cipher, aead, sha256};
-openssl_cipher_str_to_algs(CipherStr, "POLY1305" = End) ->
+openssl_cipher_str_to_algs(_, CipherStr, "POLY1305" = End) ->
Cipher = algo_str_to_atom(CipherStr ++ "_" ++ End),
{Cipher, aead, sha256};
-openssl_cipher_str_to_algs(CipherStr, HashStr) ->
+openssl_cipher_str_to_algs(Kex, CipherStr, HashStr) ->
Hash = algo_str_to_atom(HashStr),
Cipher = algo_str_to_atom(cipher_name_from_openssl(CipherStr)),
case openssl_is_aead_cipher(CipherStr) of
true ->
{Cipher, aead, Hash};
false ->
- {Cipher, Hash, openssl_prf(Hash)}
+ {Cipher, Hash, default_prf(Kex, Hash)}
end.
-openssl_prf(sha256)->
- sha256;
-openssl_prf(sha384) ->
- sha384;
-openssl_prf(_) ->
- default_prf.
-
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..c8135e6b94 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 ->
diff --git a/lib/ssl/src/tls_connection.erl b/lib/ssl/src/tls_connection.erl
index a05858221a..7cc2adfda1 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)
+ {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/test/inet_crypto_dist.erl b/lib/ssl/test/inet_crypto_dist.erl
index 5aafaac983..63c19d9438 100644
--- a/lib/ssl/test/inet_crypto_dist.erl
+++ b/lib/ssl/test/inet_crypto_dist.erl
@@ -29,14 +29,8 @@
-define(DRIVER, inet_tcp).
-define(FAMILY, inet).
--define(PROTOCOL, inet_crypto_dist_v1).
--define(DEFAULT_BLOCK_CRYPTO, aes_128_gcm).
--define(DEFAULT_HASH_ALGORITHM, sha256).
--define(DEFAULT_REKEY_INTERVAL, 32768).
-
-export([listen/1, accept/1, accept_connection/5,
setup/5, close/1, select/1, is_node_name/1]).
--export([is_supported/0]).
%% Generalized dist API, for sibling IPv6 module inet6_crypto_dist
-export([gen_listen/2, gen_accept/2, gen_accept_connection/6,
@@ -52,20 +46,136 @@
-include_lib("kernel/include/dist.hrl").
-include_lib("kernel/include/dist_util.hrl").
-%% Test if crypto has got enough capabilities for this module to run
+-define(PACKET_SIZE, 65536).
+-define(BUFFER_SIZE, (?PACKET_SIZE bsl 4)).
+
+%% -------------------------------------------------------------------------
+
+-record(params,
+ {socket,
+ dist_handle,
+ hmac_algorithm = sha256,
+ aead_cipher = aes_gcm,
+ rekey_key,
+ iv = 12,
+ key = 16,
+ tag_len = 16,
+ rekey_interval = 262144
+ }).
+
+params(Socket) ->
+ #params{socket = Socket}.
+
+
+-record(key_pair,
+ {type = ecdh,
+ %% The curve choice greatly affects setup time,
+ %% we really want an Edwards curve but that would
+ %% require a very new openssl version.
+ %% Twisted brainpool curves (*t1) are faster than
+ %% non-twisted (*r1), 256 is much faster than 384,
+ %% and so on...
+%%% params = brainpoolP384t1,
+ params = brainpoolP256t1,
+ public,
+ private}).
+
+-define(KEY_PAIR_LIFE_TIME, 3600000). % 1 hour
+-define(KEY_PAIR_LIFE_COUNT, 256). % Number of connection setups
+
+
+%% -------------------------------------------------------------------------
+%% Keep the node's public/private key pair in the process state
+%% of a key pair server linked to the acceptor process.
+%% Create the key pair the first time it is needed
+%% so crypto gets time to start first.
%%
-is_supported() ->
- try {crypto:cipher_info(?DEFAULT_BLOCK_CRYPTO),
- crypto:hash_info(?DEFAULT_HASH_ALGORITHM)}
- of
- {#{block_size := _, iv_length := _, key_length := _},
- #{size := _}} ->
- true
- catch
- error:undef ->
- false
+
+start_key_pair_server() ->
+ monitor_dist_proc(
+ spawn_link(
+ fun () ->
+ register(?MODULE, self()),
+ key_pair_server()
+ end)).
+
+key_pair_server() ->
+ key_pair_server(undefined, undefined, undefined).
+%%
+key_pair_server(KeyPair) ->
+ key_pair_server(
+ KeyPair,
+ erlang:start_timer(?KEY_PAIR_LIFE_TIME, self(), discard),
+ ?KEY_PAIR_LIFE_COUNT).
+%%
+key_pair_server(_KeyPair, Timer, 0) ->
+ cancel_timer(Timer),
+ key_pair_server();
+key_pair_server(KeyPair, Timer, Count) ->
+ receive
+ {Pid, Tag, get_key_pair} ->
+ case KeyPair of
+ undefined ->
+ KeyPair_1 = generate_key_pair(),
+ Pid ! {Tag, KeyPair_1},
+ key_pair_server(KeyPair_1);
+ #key_pair{} ->
+ Pid ! {Tag, KeyPair},
+ key_pair_server(KeyPair, Timer, Count - 1)
+ end;
+ {Pid, Tag, get_new_key_pair} ->
+ cancel_timer(Timer),
+ KeyPair_1 = generate_key_pair(),
+ Pid ! {Tag, KeyPair_1},
+ key_pair_server(KeyPair_1);
+ {timeout, Timer, discard} when is_reference(Timer) ->
+ key_pair_server()
+ end.
+
+generate_key_pair() ->
+ #key_pair{type = Type, params = Params} = #key_pair{},
+ {Public, Private} =
+ crypto:generate_key(Type, Params),
+ #key_pair{public = Public, private = Private}.
+
+cancel_timer(undefined) ->
+ ok;
+cancel_timer(Timer) ->
+ case erlang:cancel_timer(Timer) of
+ false ->
+ receive
+ {timeout, Timer, _} -> ok
+ end;
+ _RemainingTime ->
+ ok
+ end.
+
+get_key_pair() ->
+ call_key_pair_server(get_key_pair).
+
+get_new_key_pair() ->
+ call_key_pair_server(get_new_key_pair).
+
+call_key_pair_server(Request) ->
+ Pid = whereis(?MODULE),
+ Ref = erlang:monitor(process, Pid),
+ Pid ! {self(), Ref, Request},
+ receive
+ {Ref, Reply} ->
+ erlang:demonitor(Ref, [flush]),
+ Reply;
+ {'DOWN', Ref, process, Pid, Reason} ->
+ error(Reason)
end.
+compute_shared_secret(
+ #key_pair{
+ type = PublicKeyType,
+ params = PublicKeyParams,
+ private = PrivKey}, PubKey) ->
+ %%
+ crypto:compute_key(PublicKeyType, PubKey, PrivKey, PublicKeyParams).
+
%% -------------------------------------------------------------------------
%% Erlang distribution plugin structure explained to myself
%% -------
@@ -80,7 +190,7 @@ is_supported() ->
%% is not one or two processes, but one port - a gen_tcp socket
%%
%% When the VM is started with the argument "-proto_dist inet_crypto"
-%% net_kernel registers the module inet_crypto_dist as distribution
+%% net_kernel registers the module inet_crypto_dist acli,oams distribution
%% module. net_kernel calls listen/1 to create a listen socket
%% and then accept/1 with the listen socket as argument to spawn
%% the Acceptor process, which is linked to net_kernel. Apparently
@@ -159,6 +269,12 @@ is_supported() ->
%% terminate with reason 'normal'.
%% -------------------------------------------------------------------------
+-compile({inline, [socket_options/0]}).
+socket_options() ->
+ [binary, {active, false}, {packet, 2}, {nodelay, true},
+ {sndbuf, ?BUFFER_SIZE}, {recbuf, ?BUFFER_SIZE},
+ {buffer, ?BUFFER_SIZE}].
+
%% -------------------------------------------------------------------------
%% select/1 is called by net_kernel to ask if this distribution protocol
%% is willing to handle Node
@@ -195,7 +311,7 @@ listen(Name) ->
gen_listen(Name, Driver) ->
case inet_tcp_dist:gen_listen(Driver, Name) of
{ok, {Socket, Address, Creation}} ->
- inet:setopts(Socket, [binary, {nodelay, true}]),
+ inet:setopts(Socket, socket_options()),
{ok,
{Socket, Address#net_address{protocol = ?DIST_PROTO}, Creation}};
Other ->
@@ -217,24 +333,24 @@ gen_accept(Listen, Driver) ->
%%
%% Spawn Acceptor process
%%
- Config = config(),
monitor_dist_proc(
spawn_opt(
fun () ->
- accept_loop(Listen, Driver, NetKernel, Config)
+ start_key_pair_server(),
+ accept_loop(Listen, Driver, NetKernel)
end,
[link, {priority, max}])).
-accept_loop(Listen, Driver, NetKernel, Config) ->
- case Driver:accept(Listen) of
+accept_loop(Listen, Driver, NetKernel) ->
+ case Driver:accept(trace(Listen)) of
{ok, Socket} ->
wait_for_code_server(),
Timeout = net_kernel:connecttime(),
- DistCtrl = start_dist_ctrl(Socket, Config, Timeout),
+ DistCtrl = start_dist_ctrl(trace(Socket), Timeout),
%% DistCtrl is a "socket"
NetKernel !
- {accept,
- self(), DistCtrl, Driver:family(), ?DIST_PROTO},
+ trace({accept,
+ self(), DistCtrl, Driver:family(), ?DIST_PROTO}),
receive
{NetKernel, controller, Controller} ->
call_dist_ctrl(DistCtrl, {controller, Controller, self()}),
@@ -242,7 +358,7 @@ accept_loop(Listen, Driver, NetKernel, Config) ->
{NetKernel, unsupported_protocol} ->
exit(unsupported_protocol)
end,
- accept_loop(Listen, Driver, NetKernel, Config);
+ accept_loop(Listen, Driver, NetKernel);
AcceptError ->
exit({accept, AcceptError})
end.
@@ -292,12 +408,13 @@ gen_accept_connection(
fun() ->
do_accept(
Acceptor, DistCtrl,
- MyNode, Allowed, SetupTime, Driver, NetKernel)
+ trace(MyNode), Allowed, SetupTime, Driver, NetKernel)
end,
[link, {priority, max}])).
do_accept(
Acceptor, DistCtrl, MyNode, Allowed, SetupTime, Driver, NetKernel) ->
+ %%
receive
{Acceptor, controller, Socket} ->
Timer = dist_util:start_timer(SetupTime),
@@ -337,40 +454,42 @@ gen_setup(Node, Type, MyNode, LongOrShortNames, SetupTime, Driver) ->
-spec setup_fun(_,_,_,_,_,_,_) -> fun(() -> no_return()).
setup_fun(
Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) ->
+ %%
fun() ->
do_setup(
- Node, Type, MyNode, LongOrShortNames, SetupTime,
+ trace(Node), Type, MyNode, LongOrShortNames, SetupTime,
Driver, NetKernel)
end.
-spec do_setup(_,_,_,_,_,_,_) -> no_return().
do_setup(
Node, Type, MyNode, LongOrShortNames, SetupTime, Driver, NetKernel) ->
+ %%
{Name, Address} = split_node(Driver, Node, LongOrShortNames),
ErlEpmd = net_kernel:epmd_module(),
{ARMod, ARFun} = get_address_resolver(ErlEpmd, Driver),
Timer = trace(dist_util:start_timer(SetupTime)),
case ARMod:ARFun(Name, Address, Driver:family()) of
- {ok, Ip, TcpPort, Version} ->
- do_setup_connect(
- Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, Version);
+ {ok, Ip, TcpPort, Version} ->
+ do_setup_connect(
+ Node, Type, MyNode, Timer, Driver, NetKernel,
+ Ip, TcpPort, Version);
{ok, Ip} ->
case ErlEpmd:port_please(Name, Ip) of
{port, TcpPort, Version} ->
do_setup_connect(
Node, Type, MyNode, Timer, Driver, NetKernel,
- Ip, TcpPort, Version);
+ Ip, TcpPort, trace(Version));
Other ->
- ?shutdown2(
- Node,
- trace(
- {port_please_failed, ErlEpmd, Name, Ip, Other}))
+ _ = trace(
+ {ErlEpmd, port_please, [Name, Ip], Other}),
+ ?shutdown(Node)
end;
Other ->
- ?shutdown2(
- Node,
- trace({getaddr_failed, Driver, Address, Other}))
+ _ = trace(
+ {ARMod, ARFun, [Name, Address, Driver:family()],
+ Other}),
+ ?shutdown(Node)
end.
-spec do_setup_connect(_,_,_,_,_,_,_,_,_) -> no_return().
@@ -379,15 +498,15 @@ do_setup_connect(
Node, Type, MyNode, Timer, Driver, NetKernel,
Ip, TcpPort, Version) ->
dist_util:reset_timer(Timer),
- ConnectOpts =
- trace(
- connect_options(
- [binary, {active, false}, {packet, 2}, {nodelay, true}])),
+ ConnectOpts = trace(connect_options(socket_options())),
case Driver:connect(Ip, TcpPort, ConnectOpts) of
{ok, Socket} ->
- Config = config(),
DistCtrl =
- start_dist_ctrl(Socket, Config, net_kernel:connecttime()),
+ try start_dist_ctrl(Socket, net_kernel:connecttime())
+ catch error : {dist_ctrl, _} = DistCtrlError ->
+ _ = trace(DistCtrlError),
+ ?shutdown(Node)
+ end,
%% DistCtrl is a "socket"
HSData =
hs_data_common(
@@ -401,8 +520,10 @@ do_setup_connect(
request_type = Type},
dist_util:handshake_we_started(trace(HSData_1));
ConnectError ->
- ?shutdown2(Node,
- trace({connect_failed, Ip, TcpPort, ConnectError}))
+ _ = trace(
+ {Driver, connect, [Ip, TcpPort, ConnectOpts],
+ ConnectError}),
+ ?shutdown(Node)
end.
%% -------------------------------------------------------------------------
@@ -412,7 +533,7 @@ close(Socket) ->
gen_close(Socket, ?DRIVER).
gen_close(Socket, Driver) ->
- trace(Driver:close(Socket)).
+ Driver:close(trace(Socket)).
%% -------------------------------------------------------------------------
@@ -428,17 +549,23 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) ->
socket = DistCtrl,
timer = Timer,
%%
- f_send =
+ f_send = % -> ok | {error, closed}=>?shutdown()
fun (S, Packet) when S =:= DistCtrl ->
- call_dist_ctrl(S, {send, Packet})
+ try call_dist_ctrl(S, {send, Packet})
+ catch error : {dist_ctrl, Reason} ->
+ _ = trace(Reason),
+ {error, closed}
+ end
end,
- f_recv =
+ f_recv = % -> {ok, List} | Other=>?shutdown()
fun (S, 0, infinity) when S =:= DistCtrl ->
- case call_dist_ctrl(S, recv) of
+ try call_dist_ctrl(S, recv) of
{ok, Bin} when is_binary(Bin) ->
{ok, binary_to_list(Bin)};
Error ->
Error
+ catch error : {dist_ctrl, Reason} ->
+ {error, trace(Reason)}
end
end,
f_setopts_pre_nodeup =
@@ -453,9 +580,9 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) ->
fun (S) when S =:= DistCtrl ->
{ok, S} %% DistCtrl is the distribution port
end,
- f_address =
+ f_address = % -> #net_address{} | ?shutdown()
fun (S, Node) when S =:= DistCtrl ->
- case call_dist_ctrl(S, peername) of
+ try call_dist_ctrl(S, peername) of
{ok, Address} ->
case dist_util:split_node(Node) of
{node, _, Host} ->
@@ -465,13 +592,23 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) ->
protocol = ?DIST_PROTO,
family = Family};
_ ->
- {error, no_node}
- end
+ ?shutdown(Node)
+ end;
+ Error ->
+ _ = trace(Error),
+ ?shutdown(Node)
+ catch error : {dist_ctrl, Reason} ->
+ _ = trace(Reason),
+ ?shutdown(Node)
end
end,
- f_handshake_complete =
- fun (S, _Node, DistHandle) when S =:= DistCtrl ->
- call_dist_ctrl(S, {handshake_complete, DistHandle})
+ f_handshake_complete = % -> ok | ?shutdown()
+ fun (S, Node, DistHandle) when S =:= DistCtrl ->
+ try call_dist_ctrl(S, {handshake_complete, DistHandle})
+ catch error : {dist_ctrl, Reason} ->
+ _ = trace(Reason),
+ ?shutdown(Node)
+ end
end,
%%
%% mf_tick/1, mf_getstat/1, mf_setopts/2 and mf_getopts/2
@@ -481,7 +618,7 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) ->
fun (S) when S =:= DistCtrl ->
S ! dist_tick
end,
- mf_getstat =
+ mf_getstat = % -> {ok, RecvCnt, SendCnt, SendPend} | Other=>ignore_it
fun (S) when S =:= DistCtrl ->
case
inet:getstat(Socket, [recv_cnt, send_cnt, send_pend])
@@ -489,7 +626,7 @@ hs_data_common(NetKernel, MyNode, DistCtrl, Timer, Socket, Family) ->
{ok, Stat} ->
split_stat(Stat, 0, 0, 0);
Error ->
- Error
+ trace(Error)
end
end,
mf_setopts =
@@ -596,25 +733,6 @@ nodelay() ->
{nodelay, true}
end.
-config() ->
- case init:get_argument(?DIST_NAME) of
- error ->
- error({missing_argument, ?DIST_NAME});
- {ok, [[String]]} ->
- {ok, Tokens, _} = erl_scan:string(String ++ "."),
- case erl_parse:parse_term(Tokens) of
- {ok, #{secret := Secret} = Config}
- when is_binary(Secret); is_list(Secret) ->
- Config;
- {ok, #{} = Config} ->
- error({missing_secret, [{?DIST_NAME,Config}]});
- _ ->
- error({bad_argument_value, [{?DIST_NAME,String}]})
- end;
- {ok, Value} ->
- error({malformed_argument, [{?DIST_NAME,Value}]})
- end.
-
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%% The DistCtrl process(es).
@@ -625,40 +743,36 @@ config() ->
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% XXX Missing to "productified":
-%%% * Cryptoanalysis by experts
-%%% * Proof of usefulness
-%%% * Unifying exit reasons using a death_row() function
-%%% * Verification (and rejection) of other end's crypto parameters
-%%% * OTP:ification (proc_lib?)
-%%% * An application to belong to (crypto|kernel?)
-%%% * Secret on file (cookie as default?), parameter handling
-%%% * Restart and/or code reload policy
+%%% * Cryptoanalysis by experts, this is crypto amateur work.
+%%% * Is it useful over inet_tls_dist; i.e to not have to bother
+%%% with certificates but instead manage a secret cluster cookie?
+%%% * An application to belong to (kernel)
+%%% * Restart and/or code reload policy (not needed in kernel)
+%%% * Fitting into the epmd/Erlang distro protocol version framework
+%%% (something needs to be created for multiple protocols, epmd,
+%%% multiple address families, fallback to previous version, etc)
-%% Debug client and server
-test_config() ->
- #{secret => <<"Secret Cluster Password 123456">>}.
+%% Debug client and server
test_server() ->
- {ok, Listen} = gen_tcp:listen(0, [{packet, 2}, {active, false}, binary]),
+ {ok, Listen} = gen_tcp:listen(0, socket_options()),
{ok, Port} = inet:port(Listen),
io:format(?MODULE_STRING":test_client(~w).~n", [Port]),
{ok, Socket} = gen_tcp:accept(Listen),
test(Socket).
test_client(Port) ->
- {ok, Socket} =
- gen_tcp:connect(
- localhost, Port, [{packet, 2}, {active, false}, binary]),
+ {ok, Socket} = gen_tcp:connect(localhost, Port, socket_options()),
test(Socket).
test(Socket) ->
- start_dist_ctrl(Socket, test_config(), 10000).
+ start_dist_ctrl(Socket, 10000).
%% -------------------------------------------------------------------------
-start_dist_ctrl(Socket, Config, Timeout) ->
- Protocol = ?PROTOCOL,
+start_dist_ctrl(Socket, Timeout) ->
+ Secret = atom_to_binary(auth:get_cookie(), latin1),
Controller = self(),
Server =
monitor_dist_proc(
@@ -667,9 +781,9 @@ start_dist_ctrl(Socket, Config, Timeout) ->
receive
{?MODULE, From, start} ->
{SendParams, RecvParams} =
- init(Socket, Config, Protocol),
+ init(Socket, Secret),
reply(From, self()),
- handshake(SendParams, 0, RecvParams, 0, Controller)
+ handshake(SendParams, 1, RecvParams, 1, Controller)
end
end,
[link,
@@ -691,12 +805,13 @@ call_dist_ctrl(Server, Msg, Timeout) ->
erlang:demonitor(Ref, [flush]),
Res;
{'DOWN', Ref, process, Server, Reason} ->
- exit({?PROTOCOL, Reason})
- after Timeout ->
- exit(Server, timeout),
+ error({dist_ctrl, Reason})
+ after Timeout -> % Timeout < infinity is only used by start_dist_ctrl/2
receive
{'DOWN', Ref, process, Server, _} ->
- exit({?PROTOCOL, timeout})
+ receive {Ref, _} -> ok after 0 -> ok end,
+ error({dist_ctrl, timeout})
+ %% Server will be killed by link
end
end.
@@ -706,21 +821,9 @@ reply({Ref, Pid}, Msg) ->
%% -------------------------------------------------------------------------
--record(params,
- {protocol, % Encryption protocol tag
- socket,
- dist_handle,
- hash_algorithm,
- block_crypto,
- rekey_interval,
- iv,
- key,
- tag_len}).
-
--define(TCP_ACTIVE, 64).
--define(CHUNK_SIZE, (65536 - 512)).
-%% The start chunk starts with zeros, so it seems logical to not have
-%% a chunk type with value 0
+-define(TCP_ACTIVE, 16).
+-define(CHUNK_SIZE, (?PACKET_SIZE - 512)).
+
-define(HANDSHAKE_CHUNK, 1).
-define(DATA_CHUNK, 2).
-define(TICK_CHUNK, 3).
@@ -739,170 +842,189 @@ reply({Ref, Pid}, Msg) ->
%% and waits for the other end's start message. So if the send
%% blocks we have a deadlock.
%%
-%% The init message is unencrypted and contains the block cipher and hash
-%% algorithms the sender will use, the IV and a key salt. Both sides'
-%% key salt is used with the mutual secret as input to the hash algorithm
-%% to create different encryption/decryption keys for both directions.
+%% The init + start sequence tries to implement Password Encrypted
+%% Key Exchange using a node public/private key pair and the
+%% shared secret (the Cookie) to create session encryption keys
+%% that can not be re-created if the shared secret is compromized,
+%% which should create forward secrecy. You need both nodes'
+%% key pairs and the shared secret to decrypt the traffic
+%% between the nodes.
+%%
+%% All exchanged messages uses {packet, 2} i.e 16 bit size header.
+%%
+%% The init message contains a random number and encrypted: the public key
+%% and two random numbers. The encryption is done with Key and IV hashed
+%% from the unencrypted random number and the shared secret.
+%%
+%% The other node's public key is used with the own node's private
+%% key to create a shared key that is hashed with one of the encrypted
+%% random numbers from each side to create Key and IV for the session.
%%
-%% The start message is the first encrypted message and contains just
-%% encrypted zeros the width of the key, with the header of the init
-%% message as AAD data. Successfully decrypting this message
-%% verifies that we have an encrypted channel.
+%% The start message contains the two encrypted random numbers
+%% this time encrypted with the session keys for verification
+%% by the other side, plus the rekey interval. The rekey interval
+%% is just there to get an early check for if the other side's
+%% maximum rekey interal is acceptable, it is just an embryo
+%% of some better check. Any side may rekey earlier but if the
+%% rekey interval is exceeded the connection fails.
%%
%% Subsequent encrypted messages has the sequence number and the length
-%% of the message as AAD data. These messages has got a message type
-%% differentiating data from ticks. Ticks have a random size in an
-%% attempt to make them less obvious to spot.
+%% of the message as AAD data, and an incrementing IV. These messages
+%% has got a message type that differentiates data from ticks and rekeys.
+%% Ticks have a random size in an attempt to make them less obvious to spot.
%%
-%% The only reaction to errors is to crash noisily wich will bring
+%% Rekeying is done by the sender that creates a new key pair and
+%% a new shared secret from the other end's public key and with
+%% this and the current key and iv hashes a new key and iv.
+%% The new public key is sent to the other end that uses it
+%% and its old private key to create the same new shared
+%% secret and from that a new key and iv.
+%% So the receiver keeps its private key, and the sender keeps
+%% the receivers public key for the connection's life time.
+%% While the sender generates a new key pair at every rekey,
+%% which changes the shared secret at every rekey.
+%%
+%% The only reaction to errors is to crash noisily (?) wich will bring
%% down the connection and hopefully produce something useful
%% in the local log, but all the other end sees is a closed connection.
%% -------------------------------------------------------------------------
-init(Socket, Config, Protocol) ->
- Secret = maps:get(secret, Config),
- HashAlgorithm =
- maps:get(hash_algorithm, Config, ?DEFAULT_HASH_ALGORITHM),
- BlockCrypto =
- maps:get(block_crypto, Config, ?DEFAULT_BLOCK_CRYPTO),
- RekeyInterval =
- maps:get(rekey_interval, Config, ?DEFAULT_REKEY_INTERVAL),
+init(Socket, Secret) ->
+ #key_pair{public = PubKey} = KeyPair = get_key_pair(),
+ Params = params(Socket),
+ {R2, R3, Msg} = init_msg(Params, PubKey, Secret),
+ ok = gen_tcp:send(Socket, Msg),
+ init_recv(Params, Secret, KeyPair, R2, R3).
+
+init_recv(
+ #params{socket = Socket, iv = IVLen} = Params, Secret, KeyPair, R2, R3) ->
%%
- SendParams =
- init_params(
- Socket, Protocol, HashAlgorithm, BlockCrypto, RekeyInterval),
- send_init(SendParams, Secret).
+ {ok, InitMsg} = gen_tcp:recv(Socket, 0),
+ IVSaltLen = IVLen - 6,
+ try
+ case init_msg(Params, Secret, KeyPair, R2, R3, InitMsg) of
+ {#params{iv = <<IV2ASalt:IVSaltLen/binary, IV2ANo:48>>} =
+ SendParams,
+ RecvParams, SendStartMsg} ->
+ ok = gen_tcp:send(Socket, SendStartMsg),
+ {ok, RecvStartMsg} = gen_tcp:recv(Socket, 0),
+ #params{
+ iv = <<IV2BSalt:IVSaltLen/binary, IV2BNo:48>>} =
+ RecvParams_1 =
+ start_msg(RecvParams, R2, R3, RecvStartMsg),
+ {SendParams#params{iv = {IV2ASalt, IV2ANo}},
+ RecvParams_1#params{iv = {IV2BSalt, IV2BNo}}}
+ end
+ catch
+ error : Reason : Stacktrace->
+ _ = trace({Reason, Stacktrace}),
+ exit(connection_closed)
+ end.
-send_init(
+
+
+init_msg(
#params{
- protocol = Protocol,
- socket = Socket,
- block_crypto = BlockCrypto,
- iv = IVLen,
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
key = KeyLen,
- hash_algorithm = HashAlgorithm} = SendParams,
- Secret) ->
+ iv = IVLen,
+ tag_len = TagLen}, PubKeyA, Secret) ->
%%
- ProtocolString = atom_to_binary(Protocol, utf8),
- BlockCryptoString = atom_to_binary(BlockCrypto, utf8),
- HashAlgorithmString = atom_to_binary(HashAlgorithm, utf8),
- SendHeader =
- <<ProtocolString/binary, 0,
- HashAlgorithmString/binary, 0,
- BlockCryptoString/binary, 0>>,
- <<IV:IVLen/binary, KeySalt:KeyLen/binary>> = IV_KeySalt =
- crypto:strong_rand_bytes(IVLen + KeyLen),
- InitPacket = [SendHeader, IV_KeySalt],
- ok = gen_tcp:send(Socket, InitPacket),
- recv_init(SendParams#params{iv = IV, key = KeySalt}, Secret, SendHeader).
-
-recv_init(
+ RLen = KeyLen + IVLen,
+ <<R1A:RLen/binary, R2A:RLen/binary, R3A:RLen/binary>> =
+ crypto:strong_rand_bytes(3 * RLen),
+ {Key1A, IV1A} = hmac_key_iv(HmacAlgo, R1A, Secret, KeyLen, IVLen),
+ Plaintext = [R2A, R3A, PubKeyA],
+ MsgLen = byte_size(R1A) + TagLen + iolist_size(Plaintext),
+ AAD = [<<MsgLen:32>>, R1A],
+ {Ciphertext, Tag} =
+ crypto:block_encrypt(AeadCipher, Key1A, IV1A, {AAD, Plaintext, TagLen}),
+ Msg = [R1A, Tag, Ciphertext],
+ {R2A, R3A, Msg}.
+%%
+init_msg(
#params{
- socket = Socket,
- hash_algorithm = SendHashAlgorithm,
- key = SendKeySalt} = SendParams, Secret, SendHeader) ->
+ hmac_algorithm = HmacAlgo,
+ aead_cipher = AeadCipher,
+ key = KeyLen,
+ iv = IVLen,
+ tag_len = TagLen,
+ rekey_interval = RekeyInterval} = Params,
+ Secret, KeyPair, R2A, R3A, Msg) ->
%%
- {ok, InitPacket} = gen_tcp:recv(Socket, 0),
- [ProtocolString, Rest_1] = binary:split(InitPacket, <<0>>),
- Protocol = binary_to_existing_atom(ProtocolString, utf8),
- case Protocol of
- ?PROTOCOL ->
- [HashAlgorithmString, Rest_2] = binary:split(Rest_1, <<0>>),
- HashAlgorithm = binary_to_existing_atom(HashAlgorithmString, utf8),
- [BlockCryptoString, Rest_3] = binary:split(Rest_2, <<0>>),
- BlockCrypto = binary_to_existing_atom(BlockCryptoString, utf8),
- #params{
- hash_algorithm = RecvHashAlgorithm,
- iv = RecvIVLen,
- key = RecvKeyLen} = RecvParams =
- init_params(
- Socket, Protocol, HashAlgorithm, BlockCrypto, undefined),
- <<RecvIV:RecvIVLen/binary,
- RecvKeySalt:RecvKeyLen/binary>> = Rest_3,
- SendKey =
- hash_key(SendHashAlgorithm, SendKeySalt, [RecvKeySalt, Secret]),
- RecvKey =
- hash_key(RecvHashAlgorithm, RecvKeySalt, [SendKeySalt, Secret]),
- SendParams_1 = SendParams#params{key = SendKey},
- RecvParams_1 = RecvParams#params{iv = RecvIV, key = RecvKey},
- RecvHeaderLen = byte_size(InitPacket) - RecvIVLen - RecvKeyLen,
- <<RecvHeader:RecvHeaderLen/binary, _/binary>> = InitPacket,
- send_start(SendParams_1, SendHeader),
- RecvRekeyInterval = recv_start(RecvParams_1, RecvHeader),
- {SendParams_1,
- RecvParams_1#params{rekey_interval = RecvRekeyInterval}}
+ RLen = KeyLen + IVLen,
+ case Msg of
+ <<R1B:RLen/binary, Tag:TagLen/binary, Ciphertext/binary>> ->
+ {Key1B, IV1B} = hmac_key_iv(HmacAlgo, R1B, Secret, KeyLen, IVLen),
+ MsgLen = byte_size(Msg),
+ AAD = [<<MsgLen:32>>, R1B],
+ case
+ crypto:block_decrypt(
+ AeadCipher, Key1B, IV1B, {AAD, Ciphertext, Tag})
+ of
+ <<R2B:RLen/binary, R3B:RLen/binary, PubKeyB/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ %%
+ {Key2A, IV2A} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [R2A, R3B], KeyLen, IVLen),
+ SendParams =
+ Params#params{
+ rekey_key = PubKeyB,
+ key = Key2A, iv = IV2A},
+ %%
+ StartCleartext = [R2B, R3B, <<RekeyInterval:32>>],
+ StartMsgLen = TagLen + iolist_size(StartCleartext),
+ StartAAD = <<StartMsgLen:32>>,
+ {StartCiphertext, StartTag} =
+ crypto:block_encrypt(
+ AeadCipher, Key2A, IV2A,
+ {StartAAD, StartCleartext, TagLen}),
+ StartMsg = [StartTag, StartCiphertext],
+ %%
+ {Key2B, IV2B} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [R2B, R3A], KeyLen, IVLen),
+ RecvParams =
+ Params#params{
+ rekey_key = KeyPair,
+ key = Key2B, iv = IV2B},
+ %%
+ {SendParams, RecvParams, StartMsg}
+ end
end.
-send_start(
+start_msg(
#params{
- socket = Socket,
- block_crypto = BlockCrypto,
- rekey_interval= RekeyInterval,
- iv = IV,
- key = Key,
- tag_len = TagLen}, AAD) ->
+ aead_cipher = AeadCipher,
+ key = Key2B,
+ iv = IV2B,
+ tag_len = TagLen,
+ rekey_interval = RekeyIntervalA} = RecvParams, R2A, R3A, Msg) ->
%%
- KeyLen = byte_size(Key),
- Zeros = binary:copy(<<0>>, KeyLen),
- {Ciphertext, CipherTag} =
- crypto:block_encrypt(
- crypto_cipher_name(BlockCrypto),
- Key, IV, {AAD, [Zeros, <<RekeyInterval:32>>], TagLen}),
- ok = gen_tcp:send(Socket, [Ciphertext, CipherTag]).
-
-recv_start(
- #params{
- socket = Socket,
- block_crypto = BlockCrypto,
- iv = IV,
- key = Key,
- tag_len = TagLen}, AAD) ->
- {ok, Packet} = gen_tcp:recv(Socket, 0),
- KeyLen = byte_size(Key),
- PacketLen = KeyLen + 4,
- <<Ciphertext:PacketLen/binary, CipherTag:TagLen/binary>> = Packet,
- Zeros = binary:copy(<<0>>, KeyLen),
- case
- crypto:block_decrypt(
- crypto_cipher_name(BlockCrypto),
- Key, IV, {AAD, Ciphertext, CipherTag})
- of
- <<Zeros:KeyLen/binary, RekeyInterval:32>>
- when 1 =< RekeyInterval ->
- RekeyInterval;
- _ ->
- error(decrypt_error)
- end.
-
-init_params(Socket, Protocol, HashAlgorithm, BlockCrypto, RekeyInterval) ->
- #{block_size := 1,
- iv_length := IVLen,
- key_length := KeyLen} = crypto:cipher_info(BlockCrypto),
- case crypto:hash_info(HashAlgorithm) of
- #{size := HashSize} when HashSize >= KeyLen ->
- #params{
- socket = Socket,
- protocol = Protocol,
- hash_algorithm = HashAlgorithm,
- block_crypto = BlockCrypto,
- rekey_interval = RekeyInterval,
- iv = IVLen,
- key = KeyLen,
- tag_len = 16}
- end.
-
-crypto_cipher_name(BlockCrypto) ->
- case BlockCrypto of
- aes_128_gcm -> aes_gcm;
- aes_192_gcm -> aes_gcm;
- aes_256_gcm -> aes_gcm
+ case Msg of
+ <<Tag:TagLen/binary, Ciphertext/binary>> ->
+ KeyLen = byte_size(Key2B),
+ IVLen = byte_size(IV2B),
+ RLen = KeyLen + IVLen,
+ MsgLen = byte_size(Msg),
+ AAD = <<MsgLen:32>>,
+ case
+ crypto:block_decrypt(
+ AeadCipher, Key2B, IV2B, {AAD, Ciphertext, Tag})
+ of
+ <<R2A:RLen/binary, R3A:RLen/binary, RekeyIntervalB:32>>
+ when RekeyIntervalA =< (RekeyIntervalB bsl 2),
+ RekeyIntervalB =< (RekeyIntervalA bsl 2) ->
+ RecvParams#params{rekey_interval = RekeyIntervalB}
+ end
end.
-hash_key(HashAlgorithm, KeySalt, OtherSalt) ->
- KeyLen = byte_size(KeySalt),
- <<Key:KeyLen/binary, _/binary>> =
- crypto:hash(HashAlgorithm, [KeySalt, OtherSalt]),
- Key.
+hmac_key_iv(HmacAlgo, MacKey, Data, KeyLen, IVLen) ->
+ <<Key:KeyLen/binary, IV:IVLen/binary>> =
+ crypto:hmac(HmacAlgo, MacKey, Data, KeyLen + IVLen),
+ {Key, IV}.
%% -------------------------------------------------------------------------
%% net_kernel distribution handshake in progress
@@ -918,7 +1040,6 @@ handshake(
reply(From, Result),
handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller_1);
{?MODULE, From, {handshake_complete, DistHandle}} ->
- reply(From, ok),
InputHandler =
monitor_dist_proc(
spawn_opt(
@@ -945,25 +1066,38 @@ handshake(
ok = gen_tcp:controlling_process(Socket, InputHandler),
ok = erlang:dist_ctrl_input_handler(DistHandle, InputHandler),
InputHandler ! DistHandle,
+ crypto:rand_seed_alg(crypto_cache),
+ reply(From, ok),
process_flag(priority, normal),
erlang:dist_ctrl_get_data_notification(DistHandle),
- crypto:rand_seed_alg(crypto_cache),
output_handler(
SendParams#params{dist_handle = DistHandle}, SendSeq);
%%
{?MODULE, From, {send, Data}} ->
- {SendParams_1, SendSeq_1} =
+ case
encrypt_and_send_chunk(
- SendParams, SendSeq, [?HANDSHAKE_CHUNK, Data]),
- reply(From, ok),
- handshake(
- SendParams_1, SendSeq_1, RecvParams, RecvSeq, Controller);
+ SendParams, SendSeq, [?HANDSHAKE_CHUNK, Data])
+ of
+ {SendParams_1, SendSeq_1, ok} ->
+ reply(From, ok),
+ handshake(
+ SendParams_1, SendSeq_1, RecvParams, RecvSeq,
+ Controller);
+ {_, _, Error} ->
+ reply(From, {error, closed}),
+ death_row({send, trace(Error)})
+ end;
{?MODULE, From, recv} ->
- {RecvParams_1, RecvSeq_1, Reply} =
- recv_and_decrypt_chunk(RecvParams, RecvSeq),
- reply(From, Reply),
- handshake(
- SendParams, SendSeq, RecvParams_1, RecvSeq_1, Controller);
+ case recv_and_decrypt_chunk(RecvParams, RecvSeq) of
+ {RecvParams_1, RecvSeq_1, {ok, _} = Reply} ->
+ reply(From, Reply),
+ handshake(
+ SendParams, SendSeq, RecvParams_1, RecvSeq_1,
+ Controller);
+ {_, _, Error} ->
+ reply(From, Error),
+ death_row({recv, trace(Error)})
+ end;
{?MODULE, From, peername} ->
reply(From, inet:peername(Socket)),
handshake(SendParams, SendSeq, RecvParams, RecvSeq, Controller);
@@ -978,10 +1112,12 @@ recv_and_decrypt_chunk(#params{socket = Socket} = RecvParams, RecvSeq) ->
case decrypt_chunk(RecvParams, RecvSeq, Chunk) of
<<?HANDSHAKE_CHUNK, Cleartext/binary>> ->
{RecvParams, RecvSeq + 1, {ok, Cleartext}};
+ OtherChunk when is_binary(OtherChunk) ->
+ {RecvParams, RecvSeq + 1, {error, decrypt_error}};
#params{} = RecvParams_1 ->
recv_and_decrypt_chunk(RecvParams_1, 0);
- _ ->
- error(decrypt_error)
+ error ->
+ {RecvParams, RecvSeq, {error, decrypt_error}}
end;
Error ->
{RecvParams, RecvSeq, Error}
@@ -1001,8 +1137,9 @@ output_handler(Params, Seq) ->
output_handler_data(Params, Seq);
dist_tick ->
output_handler_tick(Params, Seq);
- _Other ->
+ Other ->
%% Ignore
+ _ = trace(Other),
output_handler(Params, Seq)
end
end.
@@ -1015,14 +1152,15 @@ output_handler_data(Params, Seq) ->
output_handler_data(Params, Seq);
dist_tick ->
output_handler_data(Params, Seq);
- _Other ->
+ Other ->
%% Ignore
+ _ = trace(Other),
output_handler_data(Params, Seq)
end
after 0 ->
DistHandle = Params#params.dist_handle,
Q = get_data(DistHandle, empty_q()),
- {Params_1, Seq_1} = output_handler_send(Params, Seq, Q, true),
+ {Params_1, Seq_1} = output_handler_send(Params, Seq, Q),
erlang:dist_ctrl_get_data_notification(DistHandle),
output_handler(Params_1, Seq_1)
end.
@@ -1035,39 +1173,56 @@ output_handler_tick(Params, Seq) ->
output_handler_data(Params, Seq);
dist_tick ->
output_handler_tick(Params, Seq);
- _Other ->
+ Other ->
%% Ignore
+ _ = trace(Other),
output_handler_tick(Params, Seq)
end
after 0 ->
- TickSize = 8 + rand:uniform(56),
+ TickSize = 7 + rand:uniform(56),
TickData = binary:copy(<<0>>, TickSize),
- {Params_1, Seq_1} =
- encrypt_and_send_chunk(Params, Seq, [?TICK_CHUNK, TickData]),
- output_handler(Params_1, Seq_1)
+ case
+ encrypt_and_send_chunk(Params, Seq, [?TICK_CHUNK, TickData])
+ of
+ {Params_1, Seq_1, ok} ->
+ output_handler(Params_1, Seq_1);
+ {_, _, Error} ->
+ _ = trace(Error),
+ death_row()
+ end
end.
-output_handler_send(
- #params{dist_handle = DistHandle} = Params, Seq, {_, Size, _} = Q, Retry) ->
- %%
+output_handler_send(Params, Seq, {_, Size, _} = Q) ->
if
?CHUNK_SIZE < Size ->
- {Cleartext, Q_1} = deq_iovec(?CHUNK_SIZE, Q),
- {Params_1, Seq_1} =
- encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext]),
- output_handler_send(Params_1, Seq_1, Q_1, Retry);
- Retry ->
- Q_1 = get_data(DistHandle, Q),
- output_handler_send(Params, Seq, Q_1, false);
+ output_handler_send(Params, Seq, Q, ?CHUNK_SIZE);
true ->
- {Cleartext, _} = deq_iovec(Size, Q),
- encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext])
+ case get_data(Params#params.dist_handle, Q) of
+ {_, 0, _} ->
+ {Params, Seq};
+ {_, Size, _} = Q_1 -> % Got no more
+ output_handler_send(Params, Seq, Q_1, Size);
+ Q_1 ->
+ output_handler_send(Params, Seq, Q_1)
+ end
+ end.
+
+output_handler_send(Params, Seq, Q, Size) ->
+ {Cleartext, Q_1} = deq_iovec(Size, Q),
+ case
+ encrypt_and_send_chunk(Params, Seq, [?DATA_CHUNK, Cleartext])
+ of
+ {Params_1, Seq_1, ok} ->
+ output_handler_send(Params_1, Seq_1, Q_1);
+ {_, _, Error} ->
+ _ = trace(Error),
+ death_row()
end.
%% -------------------------------------------------------------------------
%% Input handler process
%%
-%% Here is T 0 or infinity to steer if we should try to receive
+%% Here is T = 0|infinity to steer if we should try to receive
%% more data or not; start with infinity, and when we get some
%% data try with 0 to see if more is waiting
@@ -1086,11 +1241,12 @@ input_handler(#params{socket = Socket} = Params, Seq, Q, T) ->
end,
input_handler(Params, Seq, Q_1, infinity);
{tcp, Socket, Chunk} ->
- input_chunk(Params, Seq, Q, Chunk);
+ input_chunk(Params, Seq, Q, T, Chunk);
{tcp_closed, Socket} ->
- error(connection_closed);
- _Other ->
+ exit(connection_closed);
+ Other ->
%% Ignore...
+ _ = trace(Other),
input_handler(Params, Seq, Q, T)
end
after T ->
@@ -1098,16 +1254,20 @@ input_handler(#params{socket = Socket} = Params, Seq, Q, T) ->
input_handler(Params, Seq, Q_1, infinity)
end.
-input_chunk(Params, Seq, Q, Chunk) ->
+input_chunk(Params, Seq, Q, T, Chunk) ->
case decrypt_chunk(Params, Seq, Chunk) of
<<?DATA_CHUNK, Cleartext/binary>> ->
input_handler(Params, Seq + 1, enq_binary(Cleartext, Q), 0);
<<?TICK_CHUNK, _/binary>> ->
- input_handler(Params, Seq + 1, Q, 0);
+ input_handler(Params, Seq + 1, Q, T);
+ OtherChunk when is_binary(OtherChunk) ->
+ _ = trace(invalid_chunk),
+ exit(connection_closed);
#params{} = Params_1 ->
- input_handler(Params_1, 0, Q, 0);
- _ ->
- error(decrypt_error)
+ input_handler(Params_1, 0, Q, T);
+ error ->
+ _ = trace(decrypt_error),
+ exit(connection_closed)
end.
%% -------------------------------------------------------------------------
@@ -1198,64 +1358,110 @@ deliver_data(DistHandle, Front, Size, Rear, Bin) ->
encrypt_and_send_chunk(
#params{
- socket = Socket, rekey_interval = Seq,
- key = Key, iv = IV, hash_algorithm = HashAlgorithm} = Params,
+ socket = Socket,
+ rekey_interval = Seq,
+ rekey_key = PubKeyB,
+ key = Key,
+ iv = {IVSalt, IVNo},
+ hmac_algorithm = HmacAlgo} = Params,
Seq, Cleartext) ->
%%
KeyLen = byte_size(Key),
- IVLen = byte_size(IV),
- Chunk = <<IV_1:IVLen/binary, KeySalt:KeyLen/binary>> =
- crypto:strong_rand_bytes(IVLen + KeyLen),
- ok = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, Chunk])),
- Key_1 = hash_key(HashAlgorithm, Key, KeySalt),
- Params_1 = Params#params{key = Key_1, iv = IV_1},
- ok = gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)),
- {Params_1, 1};
+ IVSaltLen = byte_size(IVSalt),
+ #key_pair{public = PubKeyA} = KeyPair = get_new_key_pair(),
+ case
+ gen_tcp:send(
+ Socket, encrypt_chunk(Params, Seq, [?REKEY_CHUNK, PubKeyA]))
+ of
+ ok ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ IV = <<(IVNo + Seq):48>>,
+ {Key_1, <<IVSalt_1:IVSaltLen/binary, IVNo_1:48>>} =
+ hmac_key_iv(
+ HmacAlgo, SharedSecret, [Key, IVSalt, IV],
+ KeyLen, IVSaltLen + 6),
+ Params_1 = Params#params{key = Key_1, iv = {IVSalt_1, IVNo_1}},
+ Result =
+ gen_tcp:send(Socket, encrypt_chunk(Params_1, 0, Cleartext)),
+ {Params_1, 1, Result};
+ SendError ->
+ {Params, Seq + 1, SendError}
+ end;
encrypt_and_send_chunk(#params{socket = Socket} = Params, Seq, Cleartext) ->
- ok = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext)),
- {Params, Seq + 1}.
+ Result = gen_tcp:send(Socket, encrypt_chunk(Params, Seq, Cleartext)),
+ {Params, Seq + 1, Result}.
encrypt_chunk(
#params{
- block_crypto = BlockCrypto,
- iv = IV, key = Key, tag_len = TagLen}, Seq, Cleartext) ->
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen}, Seq, Cleartext) ->
%%
ChunkLen = iolist_size(Cleartext) + TagLen,
AAD = <<Seq:32, ChunkLen:32>>,
+ IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
{Ciphertext, CipherTag} =
- crypto:block_encrypt(
- crypto_cipher_name(BlockCrypto), Key, IV, {AAD, Cleartext, TagLen}),
+ crypto:block_encrypt(AeadCipher, Key, IVBin, {AAD, Cleartext, TagLen}),
Chunk = [Ciphertext,CipherTag],
Chunk.
decrypt_chunk(
#params{
- block_crypto = BlockCrypto,
- iv = IV, key = Key, tag_len = TagLen} = Params, Seq, Chunk) ->
+ aead_cipher = AeadCipher,
+ iv = {IVSalt, IVNo}, key = Key, tag_len = TagLen} = Params, Seq, Chunk) ->
%%
ChunkLen = byte_size(Chunk),
- true = TagLen =< ChunkLen, % Assert
- AAD = <<Seq:32, ChunkLen:32>>,
- CiphertextLen = ChunkLen - TagLen,
- <<Ciphertext:CiphertextLen/binary, CipherTag:TagLen/binary>> = Chunk,
- block_decrypt(
- Params, Seq, crypto_cipher_name(BlockCrypto),
- Key, IV, {AAD, Ciphertext, CipherTag}).
+ if
+ ChunkLen < TagLen ->
+ error;
+ true ->
+ AAD = <<Seq:32, ChunkLen:32>>,
+ IVBin = <<IVSalt/binary, (IVNo + Seq):48>>,
+ CiphertextLen = ChunkLen - TagLen,
+ case Chunk of
+ <<Ciphertext:CiphertextLen/binary,
+ CipherTag:TagLen/binary>> ->
+ block_decrypt(
+ Params, Seq, AeadCipher, Key, IVBin,
+ {AAD, Ciphertext, CipherTag});
+ _ ->
+ error
+ end
+ end.
block_decrypt(
- #params{rekey_interval = Seq} = Params, Seq, CipherName, Key, IV, Data) ->
+ #params{
+ rekey_key = #key_pair{public = PubKeyA} = KeyPair,
+ rekey_interval = RekeyInterval} = Params,
+ Seq, AeadCipher, Key, IV, Data) ->
%%
- KeyLen = byte_size(Key),
- IVLen = byte_size(IV),
- case crypto:block_decrypt(CipherName, Key, IV, Data) of
- <<?REKEY_CHUNK, IV_1:IVLen/binary, KeySalt:KeyLen/binary>> ->
- Key_1 = hash_key(Params#params.hash_algorithm, Key, KeySalt),
- Params#params{iv = IV_1, key = Key_1};
- _ ->
- error(decrypt_error)
- end;
-block_decrypt(_Params, _Seq, CipherName, Key, IV, Data) ->
- crypto:block_decrypt(CipherName, Key, IV, Data).
+ case crypto:block_decrypt(AeadCipher, Key, IV, Data) of
+ <<?REKEY_CHUNK, Rest/binary>> ->
+ PubKeyLen = byte_size(PubKeyA),
+ case Rest of
+ <<PubKeyB:PubKeyLen/binary>> ->
+ SharedSecret = compute_shared_secret(KeyPair, PubKeyB),
+ KeyLen = byte_size(Key),
+ IVLen = byte_size(IV),
+ IVSaltLen = IVLen - 6,
+ {Key_1, <<IVSalt:IVSaltLen/binary, IVNo:48>>} =
+ hmac_key_iv(
+ Params#params.hmac_algorithm,
+ SharedSecret, [Key, IV], KeyLen, IVLen),
+ Params#params{iv = {IVSalt, IVNo}, key = Key_1};
+ _ ->
+ error
+ end;
+ Chunk when is_binary(Chunk) ->
+ case Seq of
+ RekeyInterval ->
+ %% This was one chunk too many without rekeying
+ error;
+ _ ->
+ Chunk
+ end;
+ error ->
+ error
+ end.
%% -------------------------------------------------------------------------
%% Queue of binaries i.e an iovec queue
@@ -1289,34 +1495,46 @@ deq_iovec(GetSize, [Bin|Front], Size, Rear, Acc) ->
%% -------------------------------------------------------------------------
+death_row() -> death_row(connection_closed).
+%%
+death_row(normal) -> death_row(connection_closed);
+death_row(Reason) -> receive after 5000 -> exit(Reason) end.
+
+%% -------------------------------------------------------------------------
+
%% Trace point
trace(Term) -> Term.
%% Keep an eye on this Pid (debug)
+-ifndef(undefined).
+monitor_dist_proc(Pid) ->
+ Pid.
+-else.
monitor_dist_proc(Pid) ->
-%%% spawn(
-%%% fun () ->
-%%% MRef = erlang:monitor(process, Pid),
-%%% receive
-%%% {'DOWN', MRef, _, _, normal} ->
-%%% error_logger:error_report(
-%%% [dist_proc_died,
-%%% {reason, normal},
-%%% {pid, Pid}]);
-%%% {'DOWN', MRef, _, _, Reason} ->
-%%% error_logger:info_report(
-%%% [dist_proc_died,
-%%% {reason, Reason},
-%%% {pid, Pid}])
-%%% end
-%%% end),
+ spawn(
+ fun () ->
+ MRef = erlang:monitor(process, Pid),
+ receive
+ {'DOWN', MRef, _, _, normal} ->
+ error_logger:error_report(
+ [dist_proc_died,
+ {reason, normal},
+ {pid, Pid}]);
+ {'DOWN', MRef, _, _, Reason} ->
+ error_logger:info_report(
+ [dist_proc_died,
+ {reason, Reason},
+ {pid, Pid}])
+ end
+ end),
Pid.
+-endif.
dbg() ->
dbg:stop(),
dbg:tracer(),
dbg:p(all, c),
- dbg:tpl(?MODULE, cx),
+ dbg:tpl(?MODULE, trace, cx),
dbg:tpl(erlang, dist_ctrl_get_data_notification, cx),
dbg:tpl(erlang, dist_ctrl_get_data, cx),
dbg:tpl(erlang, dist_ctrl_put_data, cx),
diff --git a/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl b/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl
index 907de1abe2..6ce34ce7fa 100644
--- a/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl
+++ b/lib/ssl/test/openssl_server_cipher_suite_SUITE.erl
@@ -185,7 +185,13 @@ init_per_group(GroupName, Config) ->
true ->
case ssl_test_lib:supports_ssl_tls_version(GroupName) of
true ->
- do_init_per_group(GroupName, Config);
+ case ssl_test_lib:check_sane_openssl_version(GroupName) of
+ true ->
+ ssl_test_lib:init_tls_version(GroupName, Config),
+ do_init_per_group(GroupName, Config);
+ false ->
+ {skip, openssl_does_not_support_version}
+ end;
false ->
{skip, {openssl_does_not_support, GroupName}}
end;
diff --git a/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
new file mode 100644
index 0000000000..f225065ba6
--- /dev/null
+++ b/lib/ssl/test/property_test/ssl_eqc_cipher_format.erl
@@ -0,0 +1,96 @@
+%%
+%% %CopyrightBegin%
+%%
+%% Copyright Ericsson AB 2019-2019. All Rights Reserved.
+%%
+%% The contents of this file are subject to the Erlang Public License,
+%% Version 1.1, (the "License"); you may not use this file except in
+%% compliance with the License. You should have received a copy of the
+%% Erlang Public License along with this software. If not, it can be
+%% retrieved online at http://www.erlang.org/.
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
+%% the License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% %CopyrightEnd%
+%%
+%%
+
+-module(ssl_eqc_cipher_format).
+
+-compile(export_all).
+
+-proptest(eqc).
+-proptest([triq,proper]).
+
+-ifndef(EQC).
+-ifndef(PROPER).
+-ifndef(TRIQ).
+-define(EQC,true).
+-endif.
+-endif.
+-endif.
+
+-ifdef(EQC).
+-include_lib("eqc/include/eqc.hrl").
+-define(MOD_eqc,eqc).
+
+-else.
+-ifdef(PROPER).
+-include_lib("proper/include/proper.hrl").
+-define(MOD_eqc,proper).
+
+-else.
+-ifdef(TRIQ).
+-define(MOD_eqc,triq).
+-include_lib("triq/include/triq.hrl").
+
+-endif.
+-endif.
+-endif.
+
+-define('TLS_v1.3', 'tlsv1.3').
+-define('TLS_v1.2', 'tlsv1.2').
+-define('TLS_v1.1', 'tlsv1.1').
+-define('TLS_v1', 'tlsv1').
+-define('SSL_v3', 'sslv3').
+
+%%--------------------------------------------------------------------
+%% Properties --------------------------------------------------------
+%%--------------------------------------------------------------------
+
+prop_tls_cipher_suite_rfc_name() ->
+ ?FORALL({CipherSuite, TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}),
+ case ssl:str_to_suite(ssl:suite_to_str(CipherSuite)) of
+ CipherSuite ->
+ true;
+ _ ->
+ false
+ end
+ ).
+
+prop_tls_cipher_suite_openssl_name() ->
+ ?FORALL({CipherSuite, TLSVersion}, ?LET(Version, tls_version(), {cipher_suite(Version), Version}),
+ case ssl:str_to_suite(ssl:suite_to_openssl_str(CipherSuite)) of
+ CipherSuite ->
+ true;
+ _ ->
+ false
+ end
+ ).
+
+
+%%--------------------------------------------------------------------
+%% Generators -----------------------------------------------
+%%--------------------------------------------------------------------
+tls_version() ->
+ oneof([?'TLS_v1.2', ?'TLS_v1.1', ?'TLS_v1', ?'SSL_v3']).
+
+cipher_suite(Version) ->
+ oneof(cipher_suites(Version)).
+
+cipher_suites(Version) ->
+ ssl:cipher_suites(all, Version).
+
diff --git a/lib/ssl/test/ssl_basic_SUITE.erl b/lib/ssl/test/ssl_basic_SUITE.erl
index 20d9f28512..785ea98fa0 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,
@@ -488,6 +489,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 +520,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),
@@ -1975,6 +1989,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 +2019,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 +2245,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"}].
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_dist_bench_SUITE.erl b/lib/ssl/test/ssl_dist_bench_SUITE.erl
index 1fea6f6f72..67944c74d2 100644
--- a/lib/ssl/test/ssl_dist_bench_SUITE.erl
+++ b/lib/ssl/test/ssl_dist_bench_SUITE.erl
@@ -169,16 +169,10 @@ end_per_suite(Config) ->
init_per_group(ssl, Config) ->
[{ssl_dist, true}, {ssl_dist_prefix, "SSL"}|Config];
init_per_group(crypto, Config) ->
- case inet_crypto_dist:is_supported() of
- true ->
- [{ssl_dist, false}, {ssl_dist_prefix, "Crypto"},
- {ssl_dist_args,
- "-proto_dist inet_crypto "
- "-inet_crypto '#{secret => \"123456\"}'"}
- |Config];
- false ->
- {skip, "Not supported on this OTP version"}
- end;
+ [{ssl_dist, false}, {ssl_dist_prefix, "Crypto"},
+ {ssl_dist_args,
+ "-proto_dist inet_crypto"}
+ |Config];
init_per_group(plain, Config) ->
[{ssl_dist, false}, {ssl_dist_prefix, "Plain"}|Config];
init_per_group(_GroupName, Config) ->
diff --git a/lib/ssl/test/ssl_eqc_SUITE.erl b/lib/ssl/test/ssl_eqc_SUITE.erl
index bd36d35c02..bfdb1a3d54 100644
--- a/lib/ssl/test/ssl_eqc_SUITE.erl
+++ b/lib/ssl/test/ssl_eqc_SUITE.erl
@@ -27,7 +27,9 @@
all() ->
[
- tls_handshake_encoding
+ tls_handshake_encoding,
+ tls_cipher_suite_names,
+ tls_cipher_openssl_suite_names
].
%%--------------------------------------------------------------------
@@ -56,3 +58,13 @@ tls_handshake_encoding(Config) when is_list(Config) ->
%% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_hs_encode_decode()).
true = ct_property_test:quickcheck(ssl_eqc_handshake:prop_tls_hs_encode_decode(),
Config).
+
+tls_cipher_suite_names(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_rfc_name()).
+ true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_rfc_name(),
+ Config).
+
+tls_cipher_openssl_suite_names(Config) when is_list(Config) ->
+ %% manual test: proper:quickcheck(ssl_eqc_handshake:prop_tls_cipher_suite_openssl_name()).
+ true = ct_property_test:quickcheck(ssl_eqc_cipher_format:prop_tls_cipher_suite_openssl_name(),
+ Config).
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..832f8494c6 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
@@ -2149,7 +2168,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 98070f794c..4e387fa403 100644
--- a/lib/ssl/vsn.mk
+++ b/lib/ssl/vsn.mk
@@ -1 +1 @@
-SSL_VSN = 9.2.2
+SSL_VSN = 9.3.2
diff --git a/lib/stdlib/doc/src/notes.xml b/lib/stdlib/doc/src/notes.xml
index 65650a25c7..092056ffde 100644
--- a/lib/stdlib/doc/src/notes.xml
+++ b/lib/stdlib/doc/src/notes.xml
@@ -31,6 +31,299 @@
</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>
+ <list>
+ <item>
+ <p> Fix a bug in <c>string:lexemes/2</c>. </p> <p> The
+ bug was found when optimizing the handling of deep lists
+ of Unicode characters in the <c>string</c> module. </p>
+ <p>
+ Own Id: OTP-15649</p>
+ </item>
+ <item>
+ <p>A bug has been fixed in the <c>maps</c> implementation
+ that could cause a crash or memory usage to grow until
+ the machine ran out of memory. This could happen when
+ inserting a new key-value pair with a key <c>K1</c>
+ containing a binary <c>B1</c> into a map <c>M</c> having
+ a key <c>K2</c> with a binary <c>B2</c> if the following
+ conditions were met:</p> <list> <item><c>B1 =/=
+ B2</c></item> <item><c>size(B1) >= 4294967296</c></item>
+ <item><c>size(B2) >= 4294967296</c></item>
+ <item><c>size(M) >= 32</c></item> <item><c>(size(B1) rem
+ 4294967296) == (size(B2) rem 4294967296)</c></item>
+ <item>the first <c>(size(B1) rem 4294967296)</c> bytes
+ are the same both in <c>B1</c> and <c>B2</c></item>
+ <item>substituting <c>B1</c> in <c>K1</c> with <c>B2</c>
+ would create a term with the same value as
+ <c>K2</c></item> </list> <p>The root cause of the problem
+ is that the <c>maps</c> implementation only hashed the
+ first <c>(X rem 4294967296)</c> bytes of binaries so that
+ different binaries could get the same hash value
+ independently of the hash seed.</p>
+ <p>
+ Own Id: OTP-15707</p>
+ </item>
+ <item>
+ <p> Since the introduction of the stack trace variable,
+ the Erlang Pretty Printer has left out the exception
+ class <c>throw</c> even when the stack trace variable
+ cannot be left out, which is not correct Erlang code. The
+ fix is to always include the exception class
+ <c>throw</c>. </p>
+ <p>
+ Own Id: OTP-15751</p>
+ </item>
+ <item>
+ <p><c>record_info/2</c> is a pseudo-function that
+ requires literal arguments known at compile time.
+ Therefore, the following usage is illegal: <c>fun
+ record/info/2</c>. The compiler would crash when during
+ compilation of that kind of code. Corrected to issue a
+ compilation error.</p>
+ <p>
+ Own Id: OTP-15760 Aux Id: ERL-907 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> A new <c>rand</c> module algorithm, <c>exro928ss</c>
+ (Xoroshiro928**), has been implemented. It has got a
+ really long period and good statistical quality for all
+ output bits, while still being only about 50% slower than
+ the default algorithm. </p><p> The same generator is also
+ used as a long period counter in a new <c>crypto</c>
+ plugin for the <c>rand</c> module, algorithm
+ <c>crypto_aes</c>. This plugin uses AES-256 to scramble
+ the counter which buries any detectable statistical
+ artifacts. Scrambling is done in chunks which are cached
+ to get good amortized speed (about half of the default
+ algorithm). </p>
+ <p>
+ Own Id: OTP-14461 Aux Id: PR-1857 </p>
+ </item>
+ <item>
+ <p>
+ Types related to server naming and starting have been
+ exported from <c>gen_statem</c>. These are:
+ <c>server_name/0</c>, <c>server_ref/0</c>,
+ <c>start_opt/0</c>, <c>start_ret/0</c> and
+ <c>enter_loop_opt/0</c>.</p>
+ <p>
+ Own Id: OTP-14724 Aux Id: PR-2056 </p>
+ </item>
+ <item>
+ <p>
+ The default algorithm for the <c>rand</c> module has been
+ changed to <c>exsss</c> (Xorshift116**) which is a
+ combination of the Xorshift116 (<c>exsp</c>) state update
+ and a new scrambler "StarStar" from the 2018 paper
+ "Scrambled Linear Pseudorandom Number Generators" by
+ David Blackman and Sebastiano Vigna. This combination
+ should not have the caveat of weak low bits that the
+ previous default algorithm(s) have had, with the cost of
+ about 10% lower speed. See GitHub pull request #1969.</p>
+ <p>
+ Own Id: OTP-14731 Aux Id: PR-1969 </p>
+ </item>
+ <item>
+ <p>
+ The generic state machine behaviour <c>gen_statem</c> has
+ gotten code cleanup and documentation improvements from
+ GitHub Pull Request #1855, even though the PR itself was
+ rejected.</p>
+ <p>
+ Own Id: OTP-14737 Aux Id: PR-1855 </p>
+ </item>
+ <item>
+ <p>
+ Update Unicode specification to version 11.0.</p>
+ <p>
+ Own Id: OTP-15111</p>
+ </item>
+ <item>
+ <p>
+ ETS option <c>write_concurrency</c> now also affects and
+ improves the scalability of <c>ordered_set</c> tables.
+ The implementation is based on a data structure called
+ contention adapting search tree, where the lock
+ granularity adapts to the actual amount of concurrency
+ exploited by the applications in runtime.</p>
+ <p>
+ Own Id: OTP-15128</p>
+ </item>
+ <item>
+ <p>
+ Optimized <c>maps:new/0</c> with trivial Erlang
+ implementation, making use of literal terms (the empty
+ map) not needing dynamic heap allocation.</p>
+ <p>
+ Own Id: OTP-15200 Aux Id: PR-1878 </p>
+ </item>
+ <item>
+ <p>The <c>gen_*</c> behaviours have been changed so that
+ if logging of the last N messages through
+ <c>sys:log/2,3</c> is active for the server, this log is
+ included in the terminate report.</p> <p>To accomplish
+ this the format of "System Events" as defined in the man
+ page for <c>sys</c> has been clarified and cleaned up, a
+ new function <c>sys:get_log/1</c> has been added, and
+ <c>sys:get_debug/3</c> has been deprecated. Due to these
+ changes, code that relies on the internal badly
+ documented format of "System Events", need to be
+ corrected.</p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15381</p>
+ </item>
+ <item>
+ <p>
+ The <c>gen_statem</c> behaviour engine loop has been
+ optimized for better performance in particular when the
+ callback module returns some actions, that is better
+ performance for more realistic applications than the Echo
+ Benchmark.</p>
+ <p>
+ Own Id: OTP-15452</p>
+ </item>
+ <item>
+ <p> Do not allow function specifications for functions
+ residing in other modules. </p>
+ <p>
+ *** POTENTIAL INCOMPATIBILITY ***</p>
+ <p>
+ Own Id: OTP-15563 Aux Id: ERL-845, OTP-15562 </p>
+ </item>
+ <item>
+ <p>
+ The <c>persistent_term</c> functions <c>put/2</c> and
+ <c>erase/1</c> are now yielding.</p>
+ <p>
+ Own Id: OTP-15615</p>
+ </item>
+ <item>
+ <p>Previously, all ETS tables used centralized counter
+ variables to keep track of the number of items stored and
+ the amount of memory consumed. These counters can cause
+ scalability problems (especially on big NUMA systems).
+ This change adds an implementation of a decentralized
+ counter and modifies the implementation of ETS so that
+ ETS tables of type <c>ordered_set</c> with
+ <c>write_concurrency</c> enabled use the decentralized
+ counter. Experiments indicate that this change
+ substantially improves the scalability of ETS
+ <c>ordered_set</c> tables with <c>write_concurrency</c>
+ enabled in scenarios with frequent <c>ets:insert/2</c>
+ and <c>ets:delete/2</c> calls.</p>
+ <p>
+ Own Id: OTP-15623 Aux Id: PR-2190 </p>
+ </item>
+ <item>
+ <p> Use <c>ssh</c> instead of <c>rsh</c> as the default
+ remote shell. </p>
+ <p>
+ Own Id: OTP-15633 Aux Id: PR-1787 </p>
+ </item>
+ <item>
+ <p>Added <c>beam_lib:strip/2</c> and friends, which
+ accept a list of chunks that should be preserved when
+ stripping.</p>
+ <p>
+ Own Id: OTP-15680 Aux Id: PR-2114 </p>
+ </item>
+ <item>
+ <p> Optimize printing of maps with <c>io_lib:write()</c>.
+ Also optimize pretty printing of strings (<c>~s</c> and
+ <c>~ts</c>) when limiting the output with the
+ <c>chars_limit</c> option. </p>
+ <p>
+ Own Id: OTP-15705</p>
+ </item>
+ <item>
+ <p> There are new compiler options <c>nowarn_removed</c>
+ and <c>{nowarn_removed,Items}</c> to suppress warnings
+ for functions and modules that have been removed from
+ OTP.</p>
+ <p>
+ Own Id: OTP-15749 Aux Id: ERL-904 </p>
+ </item>
+ <item>
+ <p> Let the Erlang Pretty Printer put atomic parts on the
+ same line. </p>
+ <p>
+ Own Id: OTP-15755</p>
+ </item>
+ <item>
+ <p> Add option <c>quote_singleton_atom_types</c> to the
+ Erlang Pretty Printer's functions. Setting the option to
+ <c>true</c> adds quotes to all singleton atom types. </p>
+ <p>
+ Own Id: OTP-15756</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
+<section><title>STDLIB 3.8.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A bug in gen_statem has been fixed where the internal
+ timeout message could arrive as an info to the callback
+ module during high load due to incorrect use of
+ asynchronous timer cancel.</p>
+ <p>
+ Own Id: OTP-15295</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>STDLIB 3.8.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/stdlib/src/erl_lint.erl b/lib/stdlib/src/erl_lint.erl
index e0c37ca030..0cd0aef124 100644
--- a/lib/stdlib/src/erl_lint.erl
+++ b/lib/stdlib/src/erl_lint.erl
@@ -2,7 +2,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1996-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1996-2019. All Rights Reserved.
%%
%% Licensed under the Apache License, Version 2.0 (the "License");
%% you may not use this file except in compliance with the License.
@@ -386,6 +386,8 @@ format_error({redefine_callback, {F, A}}) ->
format_error({bad_callback, {M, F, A}}) ->
io_lib:format("explicit module not allowed for callback ~tw:~tw/~w",
[M, F, A]);
+format_error({bad_module, {M, F, A}}) ->
+ io_lib:format("spec for function ~w:~tw/~w from other module", [M, F, A]);
format_error({spec_fun_undefined, {F, A}}) ->
io_lib:format("spec for undefined function ~tw/~w", [F, A]);
format_error({missing_spec, {F,A}}) ->
@@ -3010,7 +3012,13 @@ spec_decl(Line, MFA0, TypeSpecs, St00 = #lint{specs = Specs, module = Mod}) ->
St1 = St0#lint{specs = dict:store(MFA, Line, Specs)},
case dict:is_key(MFA, Specs) of
true -> add_error(Line, {redefine_spec, MFA0}, St1);
- false -> check_specs(TypeSpecs, spec_wrong_arity, Arity, St1)
+ false ->
+ case MFA of
+ {Mod, _, _} ->
+ check_specs(TypeSpecs, spec_wrong_arity, Arity, St1);
+ _ ->
+ add_error(Line, {bad_module, MFA}, St1)
+ end
end.
%% callback_decl(Line, Fun, Types, State) -> State.
diff --git a/lib/stdlib/src/io_lib.erl b/lib/stdlib/src/io_lib.erl
index 2b5a374cf2..21d66c5529 100644
--- a/lib/stdlib/src/io_lib.erl
+++ b/lib/stdlib/src/io_lib.erl
@@ -412,14 +412,25 @@ write_port(Port) ->
write_ref(Ref) ->
erlang:ref_to_list(Ref).
+write_map(_, 1, _E) -> "#{}";
write_map(Map, D, E) when is_integer(D) ->
- [$#,${,write_map_body(maps:to_list(Map), D, D - 1, E),$}].
+ I = maps:iterator(Map),
+ case maps:next(I) of
+ {K, V, NextI} ->
+ D0 = D - 1,
+ W = write_map_assoc(K, V, D0, E),
+ [$#,${,[W | write_map_body(NextI, D0, D0, E)],$}];
+ none -> "#{}"
+ end.
-write_map_body(_, 1, _D0, _E) -> "...";
-write_map_body([], _, _D0, _E) -> [];
-write_map_body([{K,V}], _D, D0, E) -> write_map_assoc(K, V, D0, E);
-write_map_body([{K,V}|KVs], D, D0, E) ->
- [write_map_assoc(K, V, D0, E),$, | write_map_body(KVs, D - 1, D0, E)].
+write_map_body(_, 1, _D0, _E) -> ",...";
+write_map_body(I, D, D0, E) ->
+ case maps:next(I) of
+ {K, V, NextI} ->
+ W = write_map_assoc(K, V, D0, E),
+ [$,,W|write_map_body(NextI, D - 1, D0, E)];
+ none -> ""
+ end.
write_map_assoc(K, V, D, E) ->
[write1(K, D, E)," => ",write1(V, D, E)].
diff --git a/lib/stdlib/src/io_lib_format.erl b/lib/stdlib/src/io_lib_format.erl
index d1aa4cd157..157cc07e19 100644
--- a/lib/stdlib/src/io_lib_format.erl
+++ b/lib/stdlib/src/io_lib_format.erl
@@ -327,11 +327,11 @@ indentation([], I) -> I.
%% PadChar, Encoding, StringP, ChrsLim, Indentation) -> String
%% These are the dispatch functions for the various formatting controls.
-control_small($s, [A], F, Adj, P, Pad, latin1) when is_atom(A) ->
+control_small($s, [A], F, Adj, P, Pad, latin1=Enc) when is_atom(A) ->
L = iolist_to_chars(atom_to_list(A)),
- string(L, F, Adj, P, Pad);
-control_small($s, [A], F, Adj, P, Pad, unicode) when is_atom(A) ->
- string(atom_to_list(A), F, Adj, P, Pad);
+ string(L, F, Adj, P, Pad, Enc);
+control_small($s, [A], F, Adj, P, Pad, unicode=Enc) when is_atom(A) ->
+ string(atom_to_list(A), F, Adj, P, Pad, Enc);
control_small($e, [A], F, Adj, P, Pad, _Enc) when is_float(A) ->
fwrite_e(A, F, Adj, P, Pad);
control_small($f, [A], F, Adj, P, Pad, _Enc) when is_float(A) ->
@@ -371,12 +371,12 @@ control_small($n, [], F, Adj, P, Pad, _Enc) -> newline(F, Adj, P, Pad);
control_small($i, [_A], _F, _Adj, _P, _Pad, _Enc) -> [];
control_small(_C, _As, _F, _Adj, _P, _Pad, _Enc) -> not_small.
-control_limited($s, [L0], F, Adj, P, Pad, latin1, _Str, CL, _I) ->
- L = iolist_to_chars(L0),
- string(limit_string(L, F, CL), limit_field(F, CL), Adj, P, Pad);
-control_limited($s, [L0], F, Adj, P, Pad, unicode, _Str, CL, _I) ->
- L = cdata_to_chars(L0),
- uniconv(string(limit_string(L, F, CL), limit_field(F, CL), Adj, P, Pad));
+control_limited($s, [L0], F, Adj, P, Pad, latin1=Enc, _Str, CL, _I) ->
+ L = iolist_to_chars(L0, F, CL),
+ string(L, limit_field(F, CL), Adj, P, Pad, Enc);
+control_limited($s, [L0], F, Adj, P, Pad, unicode=Enc, _Str, CL, _I) ->
+ L = cdata_to_chars(L0, F, CL),
+ uniconv(string(L, limit_field(F, CL), Adj, P, Pad, Enc));
control_limited($w, [A], F, Adj, P, Pad, Enc, _Str, CL, _I) ->
Chars = io_lib:write(A, [{depth, -1}, {encoding, Enc}, {chars_limit, CL}]),
term(Chars, F, Adj, P, Pad);
@@ -718,7 +718,10 @@ fwrite_g(Fl, F, Adj, P, Pad) when P >= 1 ->
end.
-%% iolist_to_chars(iolist()) -> io_lib:chars()
+iolist_to_chars(Cs, F, CharsLimit) when CharsLimit < 0; CharsLimit >= F ->
+ iolist_to_chars(Cs);
+iolist_to_chars(Cs, _, CharsLimit) ->
+ limit_iolist_to_chars(Cs, sub(CharsLimit, 3), [], normal). % three dots
iolist_to_chars([C|Cs]) when is_integer(C), C >= $\000, C =< $\377 ->
[C | iolist_to_chars(Cs)];
@@ -729,12 +732,34 @@ iolist_to_chars([]) ->
iolist_to_chars(B) when is_binary(B) ->
binary_to_list(B).
-%% cdata() :: clist() | cbinary()
-%% clist() :: maybe_improper_list(char() | cbinary() | clist(),
-%% cbinary() | nil())
-%% cbinary() :: unicode:unicode_binary() | unicode:latin1_binary()
+limit_iolist_to_chars(Cs, 0, S, normal) ->
+ L = limit_iolist_to_chars(Cs, 4, S, final),
+ case iolist_size(L) of
+ N when N < 4 -> L;
+ 4 -> "..."
+ end;
+limit_iolist_to_chars(_Cs, 0, _S, final) -> [];
+limit_iolist_to_chars([C|Cs], Limit, S, Mode) when C >= $\000, C =< $\377 ->
+ [C | limit_iolist_to_chars(Cs, Limit - 1, S, Mode)];
+limit_iolist_to_chars([I|Cs], Limit, S, Mode) ->
+ limit_iolist_to_chars(I, Limit, [Cs|S], Mode);
+limit_iolist_to_chars([], _Limit, [], _Mode) ->
+ [];
+limit_iolist_to_chars([], Limit, [Cs|S], Mode) ->
+ limit_iolist_to_chars(Cs, Limit, S, Mode);
+limit_iolist_to_chars(B, Limit, S, Mode) when is_binary(B) ->
+ case byte_size(B) of
+ Sz when Sz > Limit ->
+ {B1, B2} = split_binary(B, Limit),
+ [binary_to_list(B1) | limit_iolist_to_chars(B2, 0, S, Mode)];
+ Sz ->
+ [binary_to_list(B) | limit_iolist_to_chars([], Limit-Sz, S, Mode)]
+ end.
-%% cdata_to_chars(cdata()) -> io_lib:chars()
+cdata_to_chars(Cs, F, CharsLimit) when CharsLimit < 0; CharsLimit >= F ->
+ cdata_to_chars(Cs);
+cdata_to_chars(Cs, _, CharsLimit) ->
+ limit_cdata_to_chars(Cs, sub(CharsLimit, 3), normal). % three dots
cdata_to_chars([C|Cs]) when is_integer(C), C >= $\000 ->
[C | cdata_to_chars(Cs)];
@@ -748,11 +773,25 @@ cdata_to_chars(B) when is_binary(B) ->
_ -> binary_to_list(B)
end.
-limit_string(S, F, CharsLimit) when CharsLimit < 0; CharsLimit >= F -> S;
-limit_string(S, _F, CharsLimit) ->
- case io_lib:chars_length(S) =< CharsLimit of
- true -> S;
- false -> [string:slice(S, 0, sub(CharsLimit, 3)), "..."]
+limit_cdata_to_chars(Cs, 0, normal) ->
+ L = limit_cdata_to_chars(Cs, 4, final),
+ case string:length(L) of
+ N when N < 4 -> L;
+ 4 -> "..."
+ end;
+limit_cdata_to_chars(_Cs, 0, final) -> [];
+limit_cdata_to_chars(Cs, Limit, Mode) ->
+ case string:next_grapheme(Cs) of
+ {error, <<C,Cs1/binary>>} ->
+ %% This is how ~ts handles Latin1 binaries with option
+ %% chars_limit.
+ [C | limit_cdata_to_chars(Cs1, Limit - 1, Mode)];
+ {error, [C|Cs1]} -> % not all versions of module string return this
+ [C | limit_cdata_to_chars(Cs1, Limit - 1, Mode)];
+ [] ->
+ [];
+ [GC|Cs1] ->
+ [GC | limit_cdata_to_chars(Cs1, Limit - 1, Mode)]
end.
limit_field(F, CharsLimit) when CharsLimit < 0; F =:= none ->
@@ -762,30 +801,30 @@ limit_field(F, CharsLimit) ->
%% string(String, Field, Adjust, Precision, PadChar)
-string(S, none, _Adj, none, _Pad) -> S;
-string(S, F, Adj, none, Pad) ->
- string_field(S, F, Adj, io_lib:chars_length(S), Pad);
-string(S, none, _Adj, P, Pad) ->
- string_field(S, P, left, io_lib:chars_length(S), Pad);
-string(S, F, Adj, P, Pad) when F >= P ->
+string(S, none, _Adj, none, _Pad, _Enc) -> S;
+string(S, F, Adj, none, Pad, Enc) ->
+ string_field(S, F, Adj, io_lib:chars_length(S), Pad, Enc);
+string(S, none, _Adj, P, Pad, Enc) ->
+ string_field(S, P, left, io_lib:chars_length(S), Pad, Enc);
+string(S, F, Adj, P, Pad, Enc) when F >= P ->
N = io_lib:chars_length(S),
if F > P ->
if N > P ->
- adjust(flat_trunc(S, P), chars(Pad, F-P), Adj);
+ adjust(flat_trunc(S, P, Enc), chars(Pad, F-P), Adj);
N < P ->
adjust([S|chars(Pad, P-N)], chars(Pad, F-P), Adj);
true -> % N == P
adjust(S, chars(Pad, F-P), Adj)
end;
true -> % F == P
- string_field(S, F, Adj, N, Pad)
+ string_field(S, F, Adj, N, Pad, Enc)
end.
-string_field(S, F, _Adj, N, _Pad) when N > F ->
- flat_trunc(S, F);
-string_field(S, F, Adj, N, Pad) when N < F ->
+string_field(S, F, _Adj, N, _Pad, Enc) when N > F ->
+ flat_trunc(S, F, Enc);
+string_field(S, F, Adj, N, Pad, _Enc) when N < F ->
adjust(S, chars(Pad, F-N), Adj);
-string_field(S, _, _, _, _) -> % N == F
+string_field(S, _, _, _, _, _) -> % N == F
S.
%% unprefixed_integer(Int, Field, Adjust, Base, PadChar, Lowercase)
@@ -837,7 +876,10 @@ adjust(Data, Pad, right) -> [Pad|Data].
%% Flatten and truncate a deep list to at most N elements.
-flat_trunc(List, N) when is_integer(N), N >= 0 ->
+flat_trunc(List, N, latin1) when is_integer(N), N >= 0 ->
+ {S, _} = lists:split(N, lists:flatten(List)),
+ S;
+flat_trunc(List, N, unicode) when is_integer(N), N >= 0 ->
string:slice(List, 0, N).
%% A deep version of lists:duplicate/2
diff --git a/lib/stdlib/src/io_lib_pretty.erl b/lib/stdlib/src/io_lib_pretty.erl
index 8f2fd7ea8f..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)
@@ -720,55 +722,40 @@ printable_list(_L, 1, _T, _Enc) ->
false;
printable_list(L, _D, T, latin1) when T < 0 ->
io_lib:printable_latin1_list(L);
-printable_list(L, _D, T, Enc) when T >= 0 ->
- case slice(L, tsub(T, 2), Enc) of
- false ->
- false;
- {prefix, Prefix} when Enc =:= latin1 ->
- io_lib:printable_latin1_list(Prefix) andalso {true, Prefix};
- {prefix, Prefix} ->
- %% Probably an overestimation.
- io_lib:printable_list(Prefix) andalso {true, Prefix};
- all when Enc =:= latin1 ->
- io_lib:printable_latin1_list(L);
+printable_list(L, _D, T, latin1) when T >= 0 ->
+ N = tsub(T, 2),
+ case printable_latin1_list(L, N) of
all ->
- io_lib:printable_list(L)
- end;
-printable_list(L, _D, T, _Uni) when T < 0->
- io_lib:printable_list(L).
-
-slice(L, N, latin1) ->
- try lists:split(N, L) of
- {_, []} ->
- all;
- {[], _} ->
- false;
- {L1, _} ->
- {prefix, L1}
- catch
- _:_ ->
- all
+ true;
+ 0 ->
+ {L1, _} = lists:split(N, L),
+ {true, L1};
+ _NC ->
+ false
end;
-slice(L, N, _Uni) ->
+printable_list(L, _D, T, _Unicode) when T >= 0 ->
+ N = tsub(T, 2),
%% Be careful not to traverse more of L than necessary.
try string:slice(L, 0, N) of
"" ->
false;
Prefix ->
- %% Assume no binaries are introduced by string:slice().
case is_flat(L, lists:flatlength(Prefix)) of
true ->
case string:equal(Prefix, L) of
true ->
- all;
+ io_lib:printable_list(L);
false ->
- {prefix, Prefix}
+ io_lib:printable_list(Prefix)
+ andalso {true, Prefix}
end;
false ->
false
end
catch _:_ -> false
- end.
+ end;
+printable_list(L, _D, T, _Uni) when T < 0->
+ io_lib:printable_list(L).
is_flat(_L, 0) ->
true;
@@ -795,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),
@@ -845,7 +834,7 @@ printable_bin1(Bin, Start, Len) ->
end.
%% -> all | integer() >=0. Adopted from io_lib.erl.
-% printable_latin1_list([_ | _], 0) -> 0;
+printable_latin1_list([_ | _], 0) -> 0;
printable_latin1_list([C | Cs], N) when C >= $\s, C =< $~ ->
printable_latin1_list(Cs, N - 1);
printable_latin1_list([C | Cs], N) when C >= $\240, C =< $\377 ->
diff --git a/lib/stdlib/src/stdlib.app.src b/lib/stdlib/src/stdlib.app.src
index 9cd425db9a..ecb514e9f3 100644
--- a/lib/stdlib/src/stdlib.app.src
+++ b/lib/stdlib/src/stdlib.app.src
@@ -108,7 +108,7 @@
dets]},
{applications, [kernel]},
{env, []},
- {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-@OTP-15128@","crypto-3.3",
+ {runtime_dependencies, ["sasl-3.0","kernel-6.0","erts-10.4","crypto-3.3",
"compiler-5.0"]}
]}.
diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src
index 08612ed17f..0c270e9dd5 100644
--- a/lib/stdlib/src/stdlib.appup.src
+++ b/lib/stdlib/src/stdlib.appup.src
@@ -19,22 +19,15 @@
%%
%% We allow upgrade from, and downgrade to all previous
%% versions from the following OTP releases:
-%% - OTP 20
%% - OTP 21
+%% - OTP 22
%%
%% We also allow upgrade from, and downgrade to all
%% versions that have branched off from the above
%% stated previous versions.
%%
{"%VSN%",
- [{<<"^3\\.4$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.5$">>,[restart_new_emulator]},
+ [{<<"^3\\.5$">>,[restart_new_emulator]},
{<<"^3\\.5\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.5\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.6$">>,[restart_new_emulator]},
@@ -43,15 +36,13 @@
{<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.8$">>,[restart_new_emulator]},
- {<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]}],
- [{<<"^3\\.4$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.2(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.3(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.4(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.4\\.5(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
- {<<"^3\\.5$">>,[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\\.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]},
{<<"^3\\.6$">>,[restart_new_emulator]},
@@ -60,4 +51,9 @@
{<<"^3\\.7\\.0(?:\\.[0-9]+)+$">>,[restart_new_emulator]},
{<<"^3\\.7\\.1(?:\\.[0-9]+)*$">>,[restart_new_emulator]},
{<<"^3\\.8$">>,[restart_new_emulator]},
- {<<"^3\\.8\\.0(?:\\.[0-9]+)+$">>,[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\\.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/src/string.erl b/lib/stdlib/src/string.erl
index 1f8bdc5432..a418754caf 100644
--- a/lib/stdlib/src/string.erl
+++ b/lib/stdlib/src/string.erl
@@ -128,7 +128,8 @@ length(CD) ->
to_graphemes(CD0) ->
case unicode_util:gc(CD0) of
[GC|CD] -> [GC|to_graphemes(CD)];
- [] -> []
+ [] -> [];
+ {error, Err} -> error({badarg, Err})
end.
%% Compare two strings return boolean, assumes that the input are
@@ -332,7 +333,10 @@ uppercase(<<CP1/utf8, Rest/binary>>=Orig) ->
catch unchanged -> Orig
end;
uppercase(<<>>) ->
- <<>>.
+ <<>>;
+uppercase(Bin) ->
+ error({badarg, Bin}).
+
%% Lowercase all chars in Str
-spec lowercase(String::unicode:chardata()) -> unicode:chardata().
@@ -346,7 +350,10 @@ lowercase(<<CP1/utf8, Rest/binary>>=Orig) ->
catch unchanged -> Orig
end;
lowercase(<<>>) ->
- <<>>.
+ <<>>;
+lowercase(Bin) ->
+ error({badarg, Bin}).
+
%% Make a titlecase of the first char in Str
-spec titlecase(String::unicode:chardata()) -> unicode:chardata().
@@ -375,7 +382,9 @@ casefold(<<CP1/utf8, Rest/binary>>=Orig) ->
catch unchanged -> Orig
end;
casefold(<<>>) ->
- <<>>.
+ <<>>;
+casefold(Bin) ->
+ error({badarg, Bin}).
-spec to_integer(String) -> {Int, Rest} | {'error', Reason} when
String :: unicode:chardata(),
@@ -544,7 +553,8 @@ length_1([CP1|[CP2|_]=Cont], N) when ?ASCII_LIST(CP1,CP2) ->
length_1(Str, N) ->
case unicode_util:gc(Str) of
[] -> N;
- [_|Rest] -> length_1(Rest, N+1)
+ [_|Rest] -> length_1(Rest, N+1);
+ {error, Err} -> error({badarg, Err})
end.
length_b(<<CP2/utf8, Rest/binary>>, CP1, N)
@@ -554,7 +564,8 @@ length_b(Bin0, CP1, N) ->
[_|Bin1] = unicode_util:gc([CP1|Bin0]),
case unicode_util:cp(Bin1) of
[] -> N+1;
- [CP3|Bin] -> length_b(Bin, CP3, N+1)
+ [CP3|Bin] -> length_b(Bin, CP3, N+1);
+ {error, Err} -> error({badarg, Err})
end.
equal_1([A|AR], [B|BR]) when is_integer(A), is_integer(B) ->
@@ -599,7 +610,8 @@ reverse_1([CP1|[CP2|_]=Cont], Acc) when ?ASCII_LIST(CP1,CP2) ->
reverse_1(CD, Acc) ->
case unicode_util:gc(CD) of
[GC|Rest] -> reverse_1(Rest, [GC|Acc]);
- [] -> Acc
+ [] -> Acc;
+ {error, Err} -> error({badarg, Err})
end.
reverse_b(<<CP2/utf8, Rest/binary>>, CP1, Acc)
@@ -609,7 +621,8 @@ reverse_b(Bin0, CP1, Acc) ->
[GC|Bin1] = unicode_util:gc([CP1|Bin0]),
case unicode_util:cp(Bin1) of
[] -> [GC|Acc];
- [CP3|Bin] -> reverse_b(Bin, CP3, [GC|Acc])
+ [CP3|Bin] -> reverse_b(Bin, CP3, [GC|Acc]);
+ {error, Err} -> error({badarg, Err})
end.
slice_l0(<<CP1/utf8, Bin/binary>>, N) when N > 0 ->
@@ -622,7 +635,8 @@ slice_l([CP1|[CP2|_]=Cont], N) when ?ASCII_LIST(CP1,CP2),N > 0 ->
slice_l(CD, N) when N > 0 ->
case unicode_util:gc(CD) of
[_|Cont] -> slice_l(Cont, N-1);
- [] -> []
+ [] -> [];
+ {error, Err} -> error({badarg, Err})
end;
slice_l(Cont, 0) ->
Cont.
@@ -634,7 +648,8 @@ slice_lb(Bin, CP1, N) ->
if N > 1 ->
case unicode_util:cp(Rest) of
[CP2|Cont] -> slice_lb(Cont, CP2, N-1);
- [] -> <<>>
+ [] -> <<>>;
+ {error, Err} -> error({badarg, Err})
end;
N =:= 1 ->
Rest
@@ -647,7 +662,10 @@ slice_trail(Orig, N) when is_binary(Orig) ->
Sz = byte_size(Orig) - Length,
<<Keep:Sz/binary, _/binary>> = Orig,
Keep;
- _ -> <<>>
+ <<_, _/binary>> when N > 0 ->
+ error({badarg, Orig});
+ _ ->
+ <<>>
end;
slice_trail(CD, N) when is_list(CD) ->
slice_list(CD, N).
@@ -657,7 +675,8 @@ slice_list([CP1|[CP2|_]=Cont], N) when ?ASCII_LIST(CP1,CP2),N > 0 ->
slice_list(CD, N) when N > 0 ->
case unicode_util:gc(CD) of
[GC|Cont] -> append(GC, slice_list(Cont, N-1));
- [] -> []
+ [] -> [];
+ {error, Err} -> error({badarg, Err})
end;
slice_list(_, 0) ->
[].
@@ -668,7 +687,8 @@ slice_bin(CD, CP1, N) when N > 0 ->
[_|Bin] = unicode_util:gc([CP1|CD]),
case unicode_util:cp(Bin) of
[CP2|Cont] -> slice_bin(Cont, CP2, N-1);
- [] -> 0
+ [] -> 0;
+ {error, Err} -> error({badarg, Err})
end;
slice_bin(CD, CP1, 0) ->
byte_size(CD)+byte_size(<<CP1/utf8>>).
@@ -703,14 +723,18 @@ uppercase_bin(CP1, Bin, Changed) ->
[] when Changed ->
[CP1];
[] ->
- throw(unchanged)
+ throw(unchanged);
+ {error, Err} ->
+ error({badarg, Err})
end;
[Char|CPs] ->
case unicode_util:cp(CPs) of
[Next|Rest] ->
[Char|uppercase_bin(Next, Rest, true)];
[] ->
- [Char]
+ [Char];
+ {error, Err} ->
+ error({badarg, Err})
end
end.
@@ -744,14 +768,18 @@ lowercase_bin(CP1, Bin, Changed) ->
[] when Changed ->
[CP1];
[] ->
- throw(unchanged)
+ throw(unchanged);
+ {error, Err} ->
+ error({badarg, Err})
end;
[Char|CPs] ->
case unicode_util:cp(CPs) of
[Next|Rest] ->
[Char|lowercase_bin(Next, Rest, true)];
[] ->
- [Char]
+ [Char];
+ {error, Err} ->
+ error({badarg, Err})
end
end.
@@ -785,14 +813,18 @@ casefold_bin(CP1, Bin, Changed) ->
[] when Changed ->
[CP1];
[] ->
- throw(unchanged)
+ throw(unchanged);
+ {error, Err} ->
+ error({badarg, Err})
end;
[Char|CPs] ->
case unicode_util:cp(CPs) of
[Next|Rest] ->
[Char|casefold_bin(Next, Rest, true)];
[] ->
- [Char]
+ [Char];
+ {error, Err} ->
+ error({badarg, Err})
end
end.
@@ -1634,7 +1666,9 @@ bin_search_inv_1(<<CP1/utf8, BinRest/binary>>=Bin0, Cont, Sep) ->
bin_search_inv_1(<<>>, Cont, _Sep) ->
{nomatch, Cont};
bin_search_inv_1([], Cont, _Sep) ->
- {nomatch, Cont}.
+ {nomatch, Cont};
+bin_search_inv_1(Bin, _, _) ->
+ error({badarg, Bin}).
bin_search_inv_n(<<CP1/utf8, BinRest/binary>>=Bin0, Cont, Seps) ->
@@ -1666,7 +1700,9 @@ bin_search_inv_n(<<CP1/utf8, BinRest/binary>>=Bin0, Cont, Seps) ->
bin_search_inv_n(<<>>, Cont, _Sep) ->
{nomatch, Cont};
bin_search_inv_n([], Cont, _Sep) ->
- {nomatch, Cont}.
+ {nomatch, Cont};
+bin_search_inv_n(Bin, _, _) ->
+ error({badarg, Bin}).
bin_search_str(Bin0, Start, [], SearchCPs) ->
Compiled = binary:compile_pattern(unicode:characters_to_binary(SearchCPs)),
diff --git a/lib/stdlib/test/erl_lint_SUITE.erl b/lib/stdlib/test/erl_lint_SUITE.erl
index fe98a3796d..e7882e0daf 100644
--- a/lib/stdlib/test/erl_lint_SUITE.erl
+++ b/lib/stdlib/test/erl_lint_SUITE.erl
@@ -1,7 +1,7 @@
%%
%% %CopyrightBegin%
%%
-%% Copyright Ericsson AB 1999-2018. All Rights Reserved.
+%% Copyright Ericsson AB 1999-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.
@@ -68,7 +68,7 @@
non_latin1_module/1, otp_14323/1,
stacktrace_syntax/1,
otp_14285/1, otp_14378/1,
- external_funs/1,otp_15456/1]).
+ external_funs/1,otp_15456/1,otp_15563/1]).
suite() ->
[{ct_hooks,[ts_install_cth]},
@@ -90,7 +90,7 @@ all() ->
otp_11851, otp_11879, otp_13230,
record_errors, otp_11879_cont, non_latin1_module, otp_14323,
stacktrace_syntax, otp_14285, otp_14378, external_funs,
- otp_15456].
+ otp_15456, otp_15563].
groups() ->
[{unused_vars_warn, [],
@@ -4010,6 +4010,8 @@ non_latin1_module(Config) ->
format_error(non_latin1_module_unsupported),
BadCallback =
{bad_callback,{'кирилли́ческий атом','кирилли́ческий атом',0}},
+ BadModule =
+ {bad_module,{'кирилли́ческий атом','кирилли́ческий атом',0}},
"explicit module not allowed for callback "
"'кирилли́ческий атом':'кирилли́ческий атом'/0" =
format_error(BadCallback),
@@ -4052,6 +4054,7 @@ non_latin1_module(Config) ->
{11,erl_lint,illegal_guard_expr},
{15,erl_lint,non_latin1_module_unsupported},
{17,erl_lint,non_latin1_module_unsupported},
+ {17,erl_lint,BadModule},
{20,erl_lint,non_latin1_module_unsupported},
{23,erl_lint,non_latin1_module_unsupported},
{25,erl_lint,non_latin1_module_unsupported}],
@@ -4229,6 +4232,21 @@ external_funs(Config) when is_list(Config) ->
run(Config, Ts),
ok.
+otp_15563(Config) when is_list(Config) ->
+ Ts = [{otp_15563,
+ <<"-type deep_list(A) :: [A | deep_list(A)].
+ -spec lists:flatten(deep_list(A)) -> [A].
+ -callback lists:concat([_]) -> string().
+ -spec ?MODULE:foo() -> any().
+ foo() -> a.
+ ">>,
+ [warn_unused_vars],
+ {errors,[{2,erl_lint,{bad_module,{lists,flatten,1}}},
+ {3,erl_lint,{bad_callback,{lists,concat,1}}}],
+ []}}],
+ [] = run(Config, Ts),
+ ok.
+
format_error(E) ->
lists:flatten(erl_lint:format_error(E)).
diff --git a/lib/stdlib/test/erl_pp_SUITE.erl b/lib/stdlib/test/erl_pp_SUITE.erl
index 3eb1670806..c0cfd26925 100644
--- a/lib/stdlib/test/erl_pp_SUITE.erl
+++ b/lib/stdlib/test/erl_pp_SUITE.erl
@@ -829,7 +829,7 @@ type_examples() ->
"(t24()) -> D when is_subtype(D, atom()),"
" is_subtype(D, t14()),"
" is_subtype(D, '\\'t::4'()).">>},
- {ex32,<<"-spec mod:t2() -> any(). ">>},
+ {ex32,<<"-spec erl_pp_test:t2() -> any(). ">>},
{ex33,<<"-opaque attributes_data() :: "
"[{'column', column()} | {'line', info_line()} |"
" {'text', string()}] | {line(),column()}. ">>},
diff --git a/lib/stdlib/test/io_SUITE.erl b/lib/stdlib/test/io_SUITE.erl
index 824f5d19f2..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_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_15639, otp_15705, otp_15847, otp_15875].
%% Error cases for output.
error_1(Config) when is_list(Config) ->
@@ -2504,9 +2504,11 @@ otp_14983(_Config) ->
trunc_string() ->
"str " = trf("str ", [], 10),
- "str ..." = trf("str ~s", ["str"], 6),
+ "str str" = trf("str ~s", ["str"], 6),
+ "str ..." = trf("str ~s", ["str1"], 6),
"str str" = trf("str ~s", ["str"], 7),
- "str ..." = trf("str ~8s", ["str"], 6),
+ "str str" = trf("str ~8s", ["str"], 6),
+ "str ..." = trf("str ~8s", ["str1"], 6),
Pa = filename:dirname(code:which(?MODULE)),
{ok, UNode} = test_server:start_node(printable_range_unicode, slave,
[{args, " +pc unicode -pa " ++ Pa}]),
@@ -2680,3 +2682,39 @@ otp_15639(_Config) ->
"\"12345678\"..." = pretty("123456789"++[x], UOpts),
"[[...]|...]" = pretty(["1","2","3","4","5","6","7","8"], UOpts),
ok.
+
+otp_15705(_Config) ->
+ L = [<<"an">>,["at"],[["om"]]],
+ "..." = trf("~s", [L], 0),
+ "..." = trf("~s", [L], 1),
+ "..." = trf("~s", [L], 2),
+ "..." = trf("~s", [L], 3),
+ "a..." = trf("~s", [L], 4),
+ "an..." = trf("~s", [L], 5),
+ "anatom" = trf("~s", [L], 6),
+ L2 = ["a",[<<"na">>],[["tom"]]],
+ "..." = trf("~s", [L2], 3),
+ "a..." = trf("~s", [L2], 4),
+ "an..." = trf("~s", [L2], 5),
+ "anatom" = trf("~s", [L2], 6),
+
+ A = [[<<"äpple"/utf8>>, "plus", <<"äpple">>]],
+ "äp..." = trf("~ts", [A], 5),
+ "äppleplusäpple" = trf("~ts", [A], 14),
+ U = [["ки"],"рилл","и́ческий атом"],
+ "ки..." = trf("~ts", [U], 5),
+ "кирилли́ческий..." = trf("~ts", [U], 16),
+ "кирилли́ческий атом" = trf("~ts", [U], 20),
+
+ "|кирилли́чес|" = 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/string_SUITE.erl b/lib/stdlib/test/string_SUITE.erl
index 248912c3f2..c9aadd7f10 100644
--- a/lib/stdlib/test/string_SUITE.erl
+++ b/lib/stdlib/test/string_SUITE.erl
@@ -103,6 +103,15 @@ debug() ->
test(?LINE,?FUNCTION_NAME,B,C,D, false),
test(?LINE,?FUNCTION_NAME,hd(C),[B|tl(C)],D, false)).
+-define(TRY(Exp),
+ fun() ->
+ try Exp
+ catch _E:Reason:_ST ->
+ %% io:format("~p:~w: ~p: ~.0p ~p~n",
+ %% [?FUNCTION_NAME, ?LINE,_E,Reason, hd(_ST)]),
+ {'EXIT', Reason}
+ end
+ end()).
is_empty(_) ->
?TEST("", [], true),
@@ -126,6 +135,10 @@ length(_) ->
?TEST(["abc"|<<"abc">>], [], 6),
?TEST(["abc",["def"]], [], 6),
?TEST([<<97/utf8, 778/utf8, 98/utf8>>, [776,111,776]], [], 3), %% åäö in nfd
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:length(InvalidUTF8)),
+ {'EXIT', {badarg, _}} = ?TRY(string:length(<<$a, InvalidUTF8/binary, $z>>)),
ok.
equal(_) ->
@@ -226,6 +239,8 @@ to_graphemes(_) ->
true = erlang:length(GCs) =:= erlang:length(string:to_graphemes(NFD)),
true = erlang:length(GCs) =:=
erlang:length(string:to_graphemes(unicode:characters_to_nfc_list(String))),
+
+ {'EXIT', {badarg, _}} = ?TRY(string:to_graphemes(<<$a,192,192,$z>>)),
ok.
reverse(_) ->
@@ -238,6 +253,11 @@ reverse(_) ->
?TEST(Str2, [], lists:reverse(Str2)),
?TEST(Str3, [], lists:reverse(Str3)),
true = string:reverse(Str3) =:= lists:reverse(string:to_graphemes(Str3)),
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:reverse(InvalidUTF8)),
+ {'EXIT', {badarg, _}} = ?TRY(string:reverse(<<$a, InvalidUTF8/binary, $z>>)),
+
ok.
slice(_) ->
@@ -258,6 +278,14 @@ slice(_) ->
?TEST([<<"aå"/utf8>>,"äöbcd"], [3,3], "öbc"),
?TEST([<<"aåä"/utf8>>,"öbcd"], [3,10], "öbcd"),
+ InvalidUTF8 = <<192,192>>,
+ [$b, $c|InvalidUTF8] = string:slice(["abc", InvalidUTF8], 1),
+ InvalidUTF8 = string:slice(["abc", InvalidUTF8], 3),
+ {'EXIT', {badarg, _}} = ?TRY(string:slice(["abc", InvalidUTF8], 1, 5)),
+ BadUtf8 = <<$a, InvalidUTF8/binary, "teststring">>,
+ {'EXIT', {badarg, _}} = ?TRY(string:slice(BadUtf8, 2)),
+ {'EXIT', {badarg, _}} = ?TRY(string:slice(BadUtf8, 1, 5)),
+ {'EXIT', {badarg, _}} = ?TRY(string:slice(BadUtf8, 0, 5)),
ok.
pad(_) ->
@@ -270,6 +298,10 @@ pad(_) ->
?TEST(Str, [10, trailing, $.], "Hallå....."),
?TEST(Str++["f"], [10, trailing, $.], "Hallåf...."),
?TEST(Str++[" flåwer"], [10, trailing, $.], "Hallå flåwer"),
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:pad(InvalidUTF8, 10, both, $.)),
+ {'EXIT', {badarg, _}} = ?TRY(string:pad(<<$a, InvalidUTF8/binary, $z>>, 10, both, $.)),
ok.
trim(_) ->
@@ -300,6 +332,11 @@ trim(_) ->
?TEST([[<<"!v">>|<<204,128,$v,204,129>>]],[trailing, [[$v,769]]], [$!,$v,768]),
?TEST([[[<<"v">>|<<204,129,118,204,128,118>>],769,118,769]], [trailing, [[118,769]]], [$v,769,$v,768]),
?TEST([<<"vv">>|<<204,128,118,204,128>>], [trailing, [[118,768]]], "v"),
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:trim(InvalidUTF8, both, "az")),
+ %% Not checked (using binary search)
+ %% {'EXIT', {badarg, _}} = ?TRY(string:trim(<<$a, $b, InvalidUTF8/binary, $z>>, both, "az")),
ok.
chomp(_) ->
@@ -400,6 +437,13 @@ take(_) ->
?TEST([<<"e">>,778,"åäöe", <<778/utf8>>, $e, 779], [[[$e,778]], true, trailing],
{[$e,778]++"åäöe"++[778], [$e,779]}),
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:take(InvalidUTF8, [$.], false, leading)),
+ %% Not checked (using binary search)
+ %% {'EXIT', {badarg, _}} = ?TRY(string:take(InvalidUTF8, [$.], true, leading)),
+ %% {'EXIT', {badarg, _}} = ?TRY(string:take(InvalidUTF8, [$.], false, trailing)),
+ {'EXIT', {badarg, _}} = ?TRY(string:take(InvalidUTF8, [$.], true, trailing)),
+
ok.
@@ -416,6 +460,11 @@ uppercase(_) ->
?TEST("ljLJ", [], "LJLJ"),
?TEST("LJlj", [], "LJLJ"),
?TEST("ß sharp s", [], "SS SHARP S"),
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:uppercase(InvalidUTF8)),
+ {'EXIT', {badarg, _}} = ?TRY(string:uppercase(<<$a, InvalidUTF8/binary, $z>>)),
+
ok.
lowercase(_) ->
@@ -429,6 +478,10 @@ lowercase(_) ->
?TEST(["Mic",<<"HAŁ"/utf8>>], [], "michał"),
?TEST("ß SHARP S", [], "ß sharp s"),
?TEST("İ I WITH DOT ABOVE", [], "i̇ i with dot above"),
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:lowercase(InvalidUTF8)),
+ {'EXIT', {badarg, _}} = ?TRY(string:lowercase(<<$a, InvalidUTF8/binary, $z>>)),
ok.
titlecase(_) ->
@@ -442,6 +495,10 @@ titlecase(_) ->
?TEST("ljLJ", [], "LjLJ"),
?TEST("LJlj", [], "Ljlj"),
?TEST("ß sharp s", [], "Ss sharp s"),
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:titlecase(InvalidUTF8)),
+ <<$A, _/binary>> = ?TRY(string:titlecase(<<$a, InvalidUTF8/binary, $z>>)),
ok.
casefold(_) ->
@@ -456,6 +513,10 @@ casefold(_) ->
?TEST("ß SHARP S", [], "ss sharp s"),
?TEST("ẞ SHARP S", [], "ss sharp s"),
?TEST("İ I WITH DOT ABOVE", [], "i̇ i with dot above"),
+
+ InvalidUTF8 = <<192,192>>,
+ {'EXIT', {badarg, _}} = ?TRY(string:casefold(InvalidUTF8)),
+ {'EXIT', {badarg, _}} = ?TRY(string:casefold(<<$a, InvalidUTF8/binary, $z>>)),
ok.
@@ -740,7 +801,7 @@ meas(Config) ->
_ -> % No scaling, run at most 1.5 min
Tester = spawn(Exec),
receive {test_done, Tester} -> ok
- after 90000 ->
+ after 118000 ->
io:format("Timelimit reached stopping~n",[]),
exit(Tester, die)
end,
diff --git a/lib/stdlib/test/unicode_util_SUITE.erl b/lib/stdlib/test/unicode_util_SUITE.erl
index 044b4e5834..6f55f204f4 100644
--- a/lib/stdlib/test/unicode_util_SUITE.erl
+++ b/lib/stdlib/test/unicode_util_SUITE.erl
@@ -428,7 +428,15 @@ mode(deep_l, Bin) -> [unicode:characters_to_list(Bin)].
fetch(Str, F) ->
case F(Str) of
[] -> [];
- [CP|R] -> [CP|fetch(R,F)]
+ [CP|R] ->
+ %% If input is a binary R should be binary
+ if is_binary(Str) == false -> ok;
+ is_binary(R); R =:= [] -> ok;
+ true ->
+ io:format("Char: ~tc Tail:~tP~n", [CP,R,10]),
+ exit({bug, F})
+ end,
+ [CP|fetch(R,F)]
end.
%% *Test.txt file helpers
diff --git a/lib/stdlib/uc_spec/gen_unicode_mod.escript b/lib/stdlib/uc_spec/gen_unicode_mod.escript
index 8636c69a0d..de67b18afc 100644
--- a/lib/stdlib/uc_spec/gen_unicode_mod.escript
+++ b/lib/stdlib/uc_spec/gen_unicode_mod.escript
@@ -202,7 +202,8 @@ gen_static(Fd) ->
io:put_chars(Fd, " {Upper,_} -> [Upper|Str];\n"),
io:put_chars(Fd, " {Upper,_,_,_} -> [Upper|Str]\n"),
io:put_chars(Fd, " end;\n"),
- io:put_chars(Fd, " [] -> []\n"),
+ io:put_chars(Fd, " [] -> [];\n"),
+ io:put_chars(Fd, " {error,Err} -> error({badarg, Err})\n"),
io:put_chars(Fd, " end.\n\n"),
io:put_chars(Fd, "-spec lowercase(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
@@ -213,7 +214,8 @@ gen_static(Fd) ->
io:put_chars(Fd, " {_,Lower} -> [Lower|Str];\n"),
io:put_chars(Fd, " {_,Lower,_,_} -> [Lower|Str]\n"),
io:put_chars(Fd, " end;\n"),
- io:put_chars(Fd, " [] -> []\n"),
+ io:put_chars(Fd, " [] -> [];\n"),
+ io:put_chars(Fd, " {error,Err} -> error({badarg, Err})\n"),
io:put_chars(Fd, " end.\n\n"),
io:put_chars(Fd, "-spec titlecase(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
@@ -224,7 +226,8 @@ gen_static(Fd) ->
io:put_chars(Fd, " {_,_,Title,_} -> [Title|Str];\n"),
io:put_chars(Fd, " {Upper,_} -> [Upper|Str]\n"),
io:put_chars(Fd, " end;\n"),
- io:put_chars(Fd, " [] -> []\n"),
+ io:put_chars(Fd, " [] -> [];\n"),
+ io:put_chars(Fd, " {error,Err} -> error({badarg, Err})\n"),
io:put_chars(Fd, " end.\n\n"),
io:put_chars(Fd, "-spec casefold(unicode:chardata()) -> "
"maybe_improper_list(gc(),unicode:chardata()).\n"),
@@ -235,7 +238,8 @@ gen_static(Fd) ->
io:put_chars(Fd, " {_,_,_,Fold} -> [Fold|Str];\n"),
io:put_chars(Fd, " {_,Lower} -> [Lower|Str]\n"),
io:put_chars(Fd, " end;\n"),
- io:put_chars(Fd, " [] -> []\n"),
+ io:put_chars(Fd, " [] -> [];\n"),
+ io:put_chars(Fd, " {error,Err} -> error({badarg, Err})\n"),
io:put_chars(Fd, " end.\n\n"),
ok.
@@ -619,7 +623,7 @@ gen_gc(Fd, GBP) ->
GenHangulT = fun(Range) -> io:format(Fd, "gc_1~s gc_h_T(R1,[CP]);\n", [gen_clause(Range)]) end,
[GenHangulT(CP) || CP <- merge_ranges(maps:get(t,GBP))],
io:put_chars(Fd, "%% Handle Hangul LV and LVT special, since they are large\n"),
- io:put_chars(Fd, "gc_1([CP|_]=R0) when 44000 < CP, CP < 56000 -> gc_h_lv_lvt(R0, []);\n"),
+ io:put_chars(Fd, "gc_1([CP|_]=R0) when 44000 < CP, CP < 56000 -> gc_h_lv_lvt(R0, R0, []);\n"),
io:put_chars(Fd, "\n%% Handle Regional\n"),
GenRegional = fun(Range) -> io:format(Fd, "gc_1~s gc_regional(R1,CP);\n", [gen_clause(Range)]) end,
@@ -748,7 +752,7 @@ gen_gc(Fd, GBP) ->
GenHangulL_2 = fun(Range) -> io:format(Fd, "~8c~s gc_h_V(R1,[CP|Acc]);\n",
[$\s,gen_case_clause(Range)]) end,
[GenHangulL_2(CP) || CP <- merge_ranges(maps:get(v,GBP))],
- io:put_chars(Fd, " R1 -> gc_h_lv_lvt(R1, Acc)\n end.\n\n"),
+ io:put_chars(Fd, " R1 -> gc_h_lv_lvt(R1, R0, Acc)\n end.\n\n"),
io:put_chars(Fd, "%% Handle Hangul V\n"),
io:put_chars(Fd, "gc_h_V(R0, Acc) ->\n case cp(R0) of\n"),
@@ -783,10 +787,10 @@ gen_gc(Fd, GBP) ->
GenHangulLVT = fun(Range) -> io:format(Fd, "gc_h_lv_lvt~s gc_h_T(R1,[CP|Acc]);\n",
[gen_clause2(Range)]) end,
[GenHangulLVT(CP) || CP <- merge_ranges(maps:get(lvt,GBP))],
- io:put_chars(Fd, "gc_h_lv_lvt([CP|R], []) -> gc_extend(cp(R), R, CP);\n"), %% From gc_1/1
+ io:put_chars(Fd, "gc_h_lv_lvt([CP|R1], _, []) -> gc_extend(cp(R1), R1, CP);\n"), %% From gc_1/1
io:put_chars(Fd, "%% Also handles error tuples\n"),
- io:put_chars(Fd, "gc_h_lv_lvt(R, [CP]) -> gc_extend(R, R, CP);\n"),
- io:put_chars(Fd, "gc_h_lv_lvt(R, Acc) -> gc_extend2(R, R, Acc).\n\n"),
+ io:put_chars(Fd, "gc_h_lv_lvt(R1, R0, [CP]) -> gc_extend(R1, R0, CP);\n"),
+ io:put_chars(Fd, "gc_h_lv_lvt(R1, R0, Acc) -> gc_extend2(R1, R0, Acc).\n\n"),
ok.
gen_compose_pairs(Fd, ExclData, Data) ->
@@ -887,9 +891,9 @@ gen_clause({R0, R1}) ->
io_lib:format("([CP|R1]=R0) when ~w =< CP, CP =< ~w ->", [R0,R1]).
gen_clause2({R0, undefined}) ->
- io_lib:format("([~w=CP|R1], Acc) ->", [R0]);
+ io_lib:format("([~w=CP|R1], R0, Acc) ->", [R0]);
gen_clause2({R0, R1}) ->
- io_lib:format("([CP|R1], Acc) when ~w =< CP, CP =< ~w ->", [R0,R1]).
+ io_lib:format("([CP|R1], R0, Acc) when ~w =< CP, CP =< ~w ->", [R0,R1]).
gen_case_clause({R0, undefined}) ->
io_lib:format("[~w=CP|R1] ->", [R0]);
diff --git a/lib/stdlib/vsn.mk b/lib/stdlib/vsn.mk
index 6471dc70e0..c2f586fef5 100644
--- a/lib/stdlib/vsn.mk
+++ b/lib/stdlib/vsn.mk
@@ -1 +1 @@
-STDLIB_VSN = 3.8.1
+STDLIB_VSN = 3.9.2
diff --git a/lib/syntax_tools/doc/src/notes.xml b/lib/syntax_tools/doc/src/notes.xml
index 772f5e6e04..a2dd78f280 100644
--- a/lib/syntax_tools/doc/src/notes.xml
+++ b/lib/syntax_tools/doc/src/notes.xml
@@ -32,6 +32,34 @@
<p>This document describes the changes made to the Syntax_Tools
application.</p>
+<section><title>Syntax_Tools 2.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p> Correct links in the documentation. </p>
+ <p>
+ Own Id: OTP-15761</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Syntax_Tools 2.1.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/syntax_tools/vsn.mk b/lib/syntax_tools/vsn.mk
index 538c71dc24..0ace11772d 100644
--- a/lib/syntax_tools/vsn.mk
+++ b/lib/syntax_tools/vsn.mk
@@ -1 +1 @@
-SYNTAX_TOOLS_VSN = 2.1.7
+SYNTAX_TOOLS_VSN = 2.2
diff --git a/lib/tools/doc/src/notes.xml b/lib/tools/doc/src/notes.xml
index 2191ebe2df..fd41e2cbeb 100644
--- a/lib/tools/doc/src/notes.xml
+++ b/lib/tools/doc/src/notes.xml
@@ -31,6 +31,49 @@
</header>
<p>This document describes the changes made to the Tools application.</p>
+<section><title>Tools 3.2</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ Add <c>cprof</c> and <c>tags</c> modules to .app file so
+ that they are included in releases.</p>
+ <p>
+ Own Id: OTP-15534 Aux Id: PR-2078 </p>
+ </item>
+ <item>
+ <p>
+ Improved documentation parsing in emacs erldoc
+ functionality.</p>
+ <p>
+ Own Id: OTP-15699 Aux Id: PR-2184 </p>
+ </item>
+ </list>
+ </section>
+
+
+ <section><title>Improvements and New Features</title>
+ <list>
+ <item>
+ <p>The <c>cover</c> tool now uses the <c>counters</c>
+ module instead of <c>ets</c> for updating the counters
+ for how many times a line has been executed. By default,
+ Cover will work with distributed nodes, but a new
+ function <c>cover:local_only/0</c> allows running the
+ Cover in a restricted but faster local-only mode.</p>
+ <p>The increase in speed will vary depending on the type
+ of code being cover-compiled, but as an example, the
+ compiler test suite runs more than twice as fast with the
+ new Cover.</p>
+ <p>
+ Own Id: OTP-15575</p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Tools 3.1</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/tools/vsn.mk b/lib/tools/vsn.mk
index 5700885549..191a458c62 100644
--- a/lib/tools/vsn.mk
+++ b/lib/tools/vsn.mk
@@ -1 +1 @@
-TOOLS_VSN = 3.1
+TOOLS_VSN = 3.2
diff --git a/lib/wx/doc/src/notes.xml b/lib/wx/doc/src/notes.xml
index 33d02e22ba..0c3374091d 100644
--- a/lib/wx/doc/src/notes.xml
+++ b/lib/wx/doc/src/notes.xml
@@ -32,6 +32,23 @@
<p>This document describes the changes made to the wxErlang
application.</p>
+<section><title>Wx 1.8.8</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ All incorrect (that is, all) uses of "can not" has been
+ corrected to "cannot" in source code comments,
+ documentation, examples, and so on.</p>
+ <p>
+ Own Id: OTP-14282 Aux Id: PR-1891 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Wx 1.8.7</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/wx/vsn.mk b/lib/wx/vsn.mk
index dac219fa98..91d15de3a9 100644
--- a/lib/wx/vsn.mk
+++ b/lib/wx/vsn.mk
@@ -1 +1 @@
-WX_VSN = 1.8.7
+WX_VSN = 1.8.8
diff --git a/lib/xmerl/doc/src/notes.xml b/lib/xmerl/doc/src/notes.xml
index d6b6dfdfb5..37973d0dba 100644
--- a/lib/xmerl/doc/src/notes.xml
+++ b/lib/xmerl/doc/src/notes.xml
@@ -32,6 +32,21 @@
<p>This document describes the changes made to the Xmerl application.</p>
+<section><title>Xmerl 1.3.21</title>
+
+ <section><title>Fixed Bugs and Malfunctions</title>
+ <list>
+ <item>
+ <p>
+ A typo in an error printout has been fixed.</p>
+ <p>
+ Own Id: OTP-14703 Aux Id: PR-1964 </p>
+ </item>
+ </list>
+ </section>
+
+</section>
+
<section><title>Xmerl 1.3.20</title>
<section><title>Fixed Bugs and Malfunctions</title>
diff --git a/lib/xmerl/vsn.mk b/lib/xmerl/vsn.mk
index 31ffa6e749..08696606e6 100644
--- a/lib/xmerl/vsn.mk
+++ b/lib/xmerl/vsn.mk
@@ -1 +1 @@
-XMERL_VSN = 1.3.20
+XMERL_VSN = 1.3.21
diff --git a/make/otp_patch_solve_forward_merge_version b/make/otp_patch_solve_forward_merge_version
index ec635144f6..f599e28b8a 100644
--- a/make/otp_patch_solve_forward_merge_version
+++ b/make/otp_patch_solve_forward_merge_version
@@ -1 +1 @@
-9
+10
diff --git a/make/otp_version_tickets b/make/otp_version_tickets
index b8220e1a87..e071f1a430 100644
--- a/make/otp_version_tickets
+++ b/make/otp_version_tickets
@@ -1 +1,9 @@
-DEVELOPMENT
+OTP-15844
+OTP-15861
+OTP-15862
+OTP-15864
+OTP-15865
+OTP-15871
+OTP-15872
+OTP-15873
+OTP-15875
diff --git a/otp_versions.table b/otp_versions.table
index 566aa3cb4c..8e3d127e4e 100644
--- a/otp_versions.table
+++ b/otp_versions.table
@@ -1,3 +1,9 @@
+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 :
OTP-21.3.7 : ssh-4.7.6 # 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 erl_interface-3.11.2 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 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 :
OTP-21.3.6 : ssl-9.2.2 # 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 erl_interface-3.11.2 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.5 stdlib-3.8.1 syntax_tools-2.1.7 tftp-1.0.1 tools-3.1 wx-1.8.7 xmerl-1.3.20 :
OTP-21.3.5 : diameter-2.2.1 erts-10.3.4 inets-7.0.7 # 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 edoc-0.10 eldap-1.2.6 erl_docgen-0.9 erl_interface-3.11.2 et-1.6.4 eunit-2.3.7 ftp-1.0.2 hipe-3.18.3 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.5 ssl-9.2.1 stdlib-3.8.1 syntax_tools-2.1.7 tftp-1.0.1 tools-3.1 wx-1.8.7 xmerl-1.3.20 :
diff --git a/scripts/run-dialyzer b/scripts/run-dialyzer
index 44436594d3..34724a8ca0 100755
--- a/scripts/run-dialyzer
+++ b/scripts/run-dialyzer
@@ -43,5 +43,5 @@ $ERL_TOP/bin/dialyzer --build_plt -Wunknown --apps $BASE_PLT $APP_PLT --statisti
$ERL_TOP/bin/dialyzer -n -Wunknown -Wunmatched_returns --apps $UNMATCHED --statistics
$ERL_TOP/bin/dialyzer -n -Wunknown --apps $NO_UNMATCHED --statistics
if [ "X$WARNINGS" != "X" ]; then
- $ERL_TOP/bin/dialyzer -n --apps $WARNINGS --statistics
+ $ERL_TOP/bin/dialyzer -n --apps $WARNINGS --statistics || true
fi