diff options
Diffstat (limited to 'lib')
31 files changed, 912 insertions, 427 deletions
diff --git a/lib/compiler/doc/src/compile.xml b/lib/compiler/doc/src/compile.xml index 1a71c83521..cfbd4c7fda 100644 --- a/lib/compiler/doc/src/compile.xml +++ b/lib/compiler/doc/src/compile.xml @@ -203,7 +203,8 @@ <tag><c>deterministic</c></tag> <item> <p>Omit the <c>options</c> and <c>source</c> tuples in - the list returned by <c>Module:module_info(compile)</c>. + the list returned by <c>Module:module_info(compile)</c>, and + reduce the paths in stack traces to the module name alone. This option will make it easier to achieve reproducible builds. </p> </item> @@ -347,8 +348,8 @@ module.beam: module.erl \ <tag><c>{source,FileName}</c></tag> <item> - <p>Sets the value of the source, as returned by - <c>module_info(compile)</c>.</p> + <p>Overrides the source file name as presented in + <c>module_info(compile)</c> and stack traces.</p> </item> <tag><c>{outdir,Dir}</c></tag> diff --git a/lib/compiler/src/compile.erl b/lib/compiler/src/compile.erl index 562d57a6ef..6510571441 100644 --- a/lib/compiler/src/compile.erl +++ b/lib/compiler/src/compile.erl @@ -931,11 +931,17 @@ parse_module(_Code, St0) -> end. do_parse_module(DefEncoding, #compile{ifile=File,options=Opts,dir=Dir}=St) -> + SourceName0 = proplists:get_value(source, Opts, File), + SourceName = case member(deterministic, Opts) of + true -> filename:basename(SourceName0); + false -> SourceName0 + end, R = epp:parse_file(File, - [{includes,[".",Dir|inc_paths(Opts)]}, - {macros,pre_defs(Opts)}, - {default_encoding,DefEncoding}, - extra]), + [{includes,[".",Dir|inc_paths(Opts)]}, + {source_name, SourceName}, + {macros,pre_defs(Opts)}, + {default_encoding,DefEncoding}, + extra]), case R of {ok,Forms,Extra} -> Encoding = proplists:get_value(encoding, Extra), diff --git a/lib/compiler/test/compile_SUITE.erl b/lib/compiler/test/compile_SUITE.erl index 1ecae06128..6b230710b3 100644 --- a/lib/compiler/test/compile_SUITE.erl +++ b/lib/compiler/test/compile_SUITE.erl @@ -36,7 +36,7 @@ core_roundtrip/1, asm/1, optimized_guards/1, sys_pre_attributes/1, dialyzer/1, warnings/1, pre_load_check/1, env_compiler_options/1, - bc_options/1, deterministic_include/1 + bc_options/1, deterministic_include/1, deterministic_paths/1 ]). suite() -> [{ct_hooks,[ts_install_cth]}]. @@ -53,7 +53,7 @@ all() -> cover, env, core_pp, core_roundtrip, asm, optimized_guards, sys_pre_attributes, dialyzer, warnings, pre_load_check, env_compiler_options, custom_debug_info, bc_options, - custom_compile_info, deterministic_include]. + custom_compile_info, deterministic_include, deterministic_paths]. groups() -> []. @@ -1531,6 +1531,30 @@ deterministic_include(Config) when is_list(Config) -> ok. +deterministic_paths(Config) when is_list(Config) -> + DataDir = proplists:get_value(data_dir, Config), + + %% Files without +deterministic should differ if they were compiled from a + %% different directory. + true = deterministic_paths_1(DataDir, "simple", []), + + %% ... but files with +deterministic shouldn't. + false = deterministic_paths_1(DataDir, "simple", [deterministic]), + + ok. + +deterministic_paths_1(DataDir, Name, Opts) -> + Simple = filename:join(DataDir, "simple"), + {ok, Cwd} = file:get_cwd(), + try + {ok,_,A} = compile:file(Simple, [binary | Opts]), + ok = file:set_cwd(DataDir), + {ok,_,B} = compile:file(Name, [binary | Opts]), + A =/= B + after + file:set_cwd(Cwd) + end. + %%% %%% Utilities. %%% diff --git a/lib/crypto/c_src/crypto.c b/lib/crypto/c_src/crypto.c index d40d285f86..b2d8123f00 100644 --- a/lib/crypto/c_src/crypto.c +++ b/lib/crypto/c_src/crypto.c @@ -189,6 +189,10 @@ # define HAVE_GCM # define HAVE_CCM # define HAVE_CMAC +# if defined(RSA_PKCS1_OAEP_PADDING) +# define HAVE_RSA_OAEP_PADDING +# endif +# define HAVE_RSA_MGF1_MD # if OPENSSL_VERSION_NUMBER < PACKED_OPENSSL_VERSION(1,0,1,'d') # define HAVE_GCM_EVP_DECRYPT_BUG # endif @@ -1340,7 +1344,7 @@ static ERL_NIF_TERM algo_mac[3]; /* increase when extending the list */ static int algo_curve_cnt, algo_curve_fips_cnt; static ERL_NIF_TERM algo_curve[87]; /* increase when extending the list */ static int algo_rsa_opts_cnt, algo_rsa_opts_fips_cnt; -static ERL_NIF_TERM algo_rsa_opts[10]; /* increase when extending the list */ +static ERL_NIF_TERM algo_rsa_opts[11]; /* increase when extending the list */ static void init_algorithms_types(ErlNifEnv* env) { @@ -1562,7 +1566,12 @@ static void init_algorithms_types(ErlNifEnv* env) algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_pss_padding"); algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pss_saltlen"); # endif +# ifdef HAVE_RSA_MGF1_MD algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_mgf1_md"); +# endif +# ifdef HAVE_RSA_OAEP_PADDING + algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_pkcs1_oaep_padding"); +# endif # ifdef HAVE_RSA_OAEP_MD algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_label"); algo_rsa_opts[algo_rsa_opts_cnt++] = enif_make_atom(env,"rsa_oaep_md"); @@ -4693,16 +4702,16 @@ printf("\r\n"); if (argv[0] == atom_rsa) { if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg; -#ifdef HAVE_RSA_PKCS1_PSS_PADDING +# ifdef HAVE_RSA_PKCS1_PSS_PADDING if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) { if (sig_opt.rsa_mgf1_md != NULL) { -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1) +# ifdef HAVE_RSA_MGF1_MD if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg; -#else +# else EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); return atom_notsup; -#endif +# endif } if (sig_opt.rsa_pss_saltlen > -2 && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0) @@ -4833,13 +4842,13 @@ static ERL_NIF_TERM pkey_verify_nif(ErlNifEnv *env, int argc, const ERL_NIF_TERM if (EVP_PKEY_CTX_set_rsa_padding(ctx, sig_opt.rsa_padding) <= 0) goto badarg; if (sig_opt.rsa_padding == RSA_PKCS1_PSS_PADDING) { if (sig_opt.rsa_mgf1_md != NULL) { -#if OPENSSL_VERSION_NUMBER >= PACKED_OPENSSL_VERSION_PLAIN(1,0,1) +# ifdef HAVE_RSA_MGF1_MD if (EVP_PKEY_CTX_set_rsa_mgf1_md(ctx, sig_opt.rsa_mgf1_md) <= 0) goto badarg; -#else +# else EVP_PKEY_CTX_free(ctx); EVP_PKEY_free(pkey); return atom_notsup; -#endif +# endif } if (sig_opt.rsa_pss_saltlen > -2 && EVP_PKEY_CTX_set_rsa_pss_saltlen(ctx, sig_opt.rsa_pss_saltlen) <= 0) @@ -4932,8 +4941,10 @@ static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NI ) { if (tpl_terms[1] == atom_rsa_pkcs1_padding) { opt->rsa_padding = RSA_PKCS1_PADDING; +#ifdef HAVE_RSA_OAEP_PADDING } else if (tpl_terms[1] == atom_rsa_pkcs1_oaep_padding) { opt->rsa_padding = RSA_PKCS1_OAEP_PADDING; +#endif #ifdef HAVE_RSA_SSLV23_PADDING } else if (tpl_terms[1] == atom_rsa_sslv23_padding) { opt->rsa_padding = RSA_SSLV23_PADDING; @@ -4952,7 +4963,7 @@ static int get_pkey_crypt_options(ErlNifEnv *env, ERL_NIF_TERM algorithm, ERL_NI } opt->signature_md = opt_md; } else if (tpl_terms[0] == atom_rsa_mgf1_md && enif_is_atom(env, tpl_terms[1])) { -#ifndef HAVE_RSA_OAEP_MD +#ifndef HAVE_RSA_MGF1_MD if (tpl_terms[1] != atom_sha) return PKEY_NOTSUP; #endif diff --git a/lib/crypto/doc/src/algorithm_details.xml b/lib/crypto/doc/src/algorithm_details.xml index 68ad264df7..4d58d26970 100644 --- a/lib/crypto/doc/src/algorithm_details.xml +++ b/lib/crypto/doc/src/algorithm_details.xml @@ -63,9 +63,9 @@ <row><cell><c>aes_ige256</c></cell><cell>16</cell><cell>32</cell><cell>16</cell></row> <row><cell><c>blowfish_cbc</c></cell> <cell>4-56</cell> <cell>8</cell> <cell>8</cell></row> - <row><cell><c>blowfish_cfb64</c></cell> <cell>1-</cell> <cell>8</cell> <cell>any</cell></row> - <row><cell><c>blowfish_ecb</c></cell><cell>1-</cell><cell> </cell><cell>8</cell></row> - <row><cell><c>blowfish_ofb64</c></cell><cell>1-</cell><cell>8</cell><cell>any</cell></row> + <row><cell><c>blowfish_cfb64</c></cell> <cell>≥1</cell> <cell>8</cell> <cell>any</cell></row> + <row><cell><c>blowfish_ecb</c></cell><cell>≥1</cell><cell> </cell><cell>8</cell></row> + <row><cell><c>blowfish_ofb64</c></cell><cell>≥1</cell><cell>8</cell><cell>any</cell></row> <row><cell><c>des3_cbc</c><br/><i>(=DES EDE3 CBC)</i></cell><cell>[8,8,8]</cell><cell>8</cell><cell>8</cell></row> <row><cell><c>des3_cfb</c><br/><i>(=DES EDE3 CFB)</i></cell><cell>[8,8,8]</cell><cell>8</cell><cell>any</cell></row> @@ -74,7 +74,7 @@ <row><cell><c>des_cfb</c></cell><cell>8</cell><cell>8</cell><cell>any</cell></row> <row><cell><c>des_ecb</c></cell><cell>8</cell><cell> </cell><cell>8</cell></row> <row><cell><c>des_ede3</c><br/><i>(=DES EDE3 CBC)</i></cell><cell>[8,8,8]</cell><cell>8</cell><cell>8</cell></row> - <row><cell><c>rc2_cbc</c></cell><cell>1-</cell><cell>8</cell><cell>8</cell></row> + <row><cell><c>rc2_cbc</c></cell><cell>≥1</cell><cell>8</cell><cell>8</cell></row> <tcaption>Block cipher key lengths</tcaption> </table> </section> @@ -90,9 +90,9 @@ </p> <table> <row><cell><strong>Cipher and Mode</strong></cell><cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell><cell><strong>IV length</strong><br/><strong>[bytes]</strong></cell><cell><strong>AAD length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Tag length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Block size</strong><br/><strong>[bytes]</strong></cell><cell><strong>Supported with</strong><br/><strong>OpenSSL versions</strong></cell></row> - <row><cell><c>aes_ccm</c></cell> <cell>16,24,32</cell> <cell>7-13</cell> <cell>any</cell> <cell>even 4-16<br/>default: 12</cell> <cell>any</cell><cell>1.1.0 -</cell></row> - <row><cell><c>aes_gcm</c></cell> <cell>16,24,32</cell> <cell>1-</cell> <cell>any</cell> <cell>1-16<br/>default: 16</cell> <cell>any</cell><cell>1.1.0 -</cell></row> - <row><cell><c>chacha20_poly1305</c></cell><cell>32</cell> <cell>1-16</cell> <cell>any</cell> <cell>16</cell> <cell>any</cell><cell>1.1.0 -</cell></row> + <row><cell><c>aes_ccm</c></cell> <cell>16,24,32</cell> <cell>7-13</cell> <cell>any</cell> <cell>even 4-16<br/>default: 12</cell> <cell>any</cell><cell>≥1.1.0</cell></row> + <row><cell><c>aes_gcm</c></cell> <cell>16,24,32</cell> <cell>≥1</cell> <cell>any</cell> <cell>1-16<br/>default: 16</cell> <cell>any</cell><cell>≥1.1.0</cell></row> + <row><cell><c>chacha20_poly1305</c></cell><cell>32</cell> <cell>1-16</cell> <cell>any</cell> <cell>16</cell> <cell>any</cell><cell>≥1.1.0</cell></row> <tcaption>AEAD cipher key lengths</tcaption> </table> </section> @@ -108,8 +108,8 @@ </p> <table> <row><cell><strong>Cipher and Mode</strong></cell><cell><strong>Key length</strong><br/><strong>[bytes]</strong></cell><cell><strong>IV length</strong><br/><strong>[bytes]</strong></cell><cell><strong>Supported with</strong><br/><strong>OpenSSL versions</strong></cell></row> - <row><cell><c>aes_ctr</c></cell><cell>16, 24, 32</cell><cell>16</cell><cell>1.0.1 -</cell></row> - <row><cell><c>rc4</c></cell><cell>1-</cell><cell> </cell> <cell>all</cell></row> + <row><cell><c>aes_ctr</c></cell><cell>16, 24, 32</cell><cell>16</cell><cell>≥1.0.1</cell></row> + <row><cell><c>rc4</c></cell><cell>≥1</cell><cell> </cell> <cell>all</cell></row> <tcaption>Stream cipher key lengths</tcaption> </table> </section> @@ -141,9 +141,9 @@ <row><cell><c>aes_cfb8</c></cell> <cell>16</cell><cell>1</cell></row> <row><cell><c>blowfish_cbc</c></cell> <cell>4-56</cell> <cell>8</cell></row> - <row><cell><c>blowfish_cfb64</c></cell> <cell>1-</cell> <cell>1</cell></row> - <row><cell><c>blowfish_ecb</c></cell><cell>1-</cell> <cell>8</cell></row> - <row><cell><c>blowfish_ofb64</c></cell><cell>1-</cell> <cell>1</cell></row> + <row><cell><c>blowfish_cfb64</c></cell> <cell>≥1</cell> <cell>1</cell></row> + <row><cell><c>blowfish_ecb</c></cell><cell>≥1</cell> <cell>8</cell></row> + <row><cell><c>blowfish_ofb64</c></cell><cell>≥1</cell> <cell>1</cell></row> <row><cell><c>des3_cbc</c><br/><i>(=DES EDE3 CBC)</i></cell><cell>[8,8,8]</cell><cell>8</cell></row> <row><cell><c>des3_cfb</c><br/><i>(=DES EDE3 CFB)</i></cell><cell>[8,8,8]</cell><cell>1</cell></row> @@ -152,7 +152,7 @@ <row><cell><c>des_cfb</c></cell><cell>8</cell><cell>1</cell></row> <row><cell><c>des_ecb</c></cell><cell>8</cell><cell>1</cell></row> - <row><cell><c>rc2_cbc</c></cell><cell>1-</cell><cell>8</cell></row> + <row><cell><c>rc2_cbc</c></cell><cell>≥1</cell><cell>8</cell></row> <tcaption>CMAC cipher key lengths</tcaption> </table> </section> @@ -195,7 +195,7 @@ </row> <row><cell>SHA1</cell><cell>sha</cell><cell>all</cell></row> <row><cell>SHA2</cell><cell>sha224, sha256, sha384, sha512</cell><cell>all</cell></row> - <row><cell>SHA3</cell><cell>sha3_224, sha3_256, sha3_384, sha3_512</cell><cell>1.1.1 -</cell></row> + <row><cell>SHA3</cell><cell>sha3_224, sha3_256, sha3_384, sha3_512</cell><cell>≥1.1.1</cell></row> <row><cell>MD4</cell><cell>md4</cell><cell>all</cell></row> <row><cell>MD5</cell><cell>md5</cell><cell>all</cell></row> <row><cell>RIPEMD</cell><cell>ripemd160</cell><cell>all</cell></row> @@ -221,18 +221,62 @@ without prior notice.</p> </warning> <table> - <row><cell><strong>Option</strong></cell> <cell><strong>sign/verify</strong></cell> <cell><strong>encrypt/decrypt</strong></cell> <cell><strong>Supported with</strong><br/><strong>OpenSSL versions</strong></cell> </row> - <row><cell>{rsa_mgf1_md,atom()}</cell> <cell>x</cell> <cell>x</cell> <cell>1.0.1</cell></row> - <row><cell>{rsa_oaep_label, binary()}</cell> <cell> </cell> <cell>x</cell> <cell></cell></row> - <row><cell>{rsa_oaep_md, atom()}</cell> <cell> </cell> <cell>x</cell> <cell></cell></row> - <row><cell>{rsa_padding,rsa_pkcs1_pss_padding}</cell> <cell>x</cell> <cell> </cell> <cell>1.0.0</cell></row> - <row><cell>{rsa_pss_saltlen, -2..}</cell> <cell>x</cell> <cell> </cell> <cell>1.0.0</cell></row> - <row><cell>{rsa_padding,rsa_no_padding}</cell> <cell>x</cell> <cell>x</cell> <cell></cell></row> - <row><cell>{rsa_padding,rsa_pkcs1_padding}</cell> <cell>x</cell> <cell>x</cell> <cell></cell></row> - <row><cell>{rsa_padding,rsa_sslv23_padding}</cell> <cell> </cell> <cell>x</cell> <cell></cell></row> - <row><cell>{rsa_padding,rsa_x931_padding}</cell> <cell>x</cell> <cell> </cell> <cell></cell></row> + <row><cell><strong>Option</strong></cell> + <cell><strong>sign/verify</strong></cell> + <cell><strong>public encrypt</strong><br/><strong>private decrypt</strong></cell> + <cell><strong>private encrypt</strong><br/><strong>public decrypt</strong></cell> + </row> + <row><cell>{rsa_padding,rsa_x931_padding}</cell> + <cell>x</cell> + <cell></cell> + <cell>x</cell> + </row> + <row><cell>{rsa_padding,rsa_pkcs1_padding}</cell> + <cell>x</cell> + <cell>x</cell> + <cell>x</cell> + </row> + <row><cell>{rsa_padding,rsa_pkcs1_pss_padding}<br/> + {rsa_pss_saltlen, -2..}<br/> + {rsa_mgf1_md, atom()} + </cell> + <cell>x (2)<br/> + x (2)<br/> + x (2)</cell> + <cell></cell> + <cell></cell> + </row> + <row><cell>{rsa_padding,rsa_pkcs1_oaep_padding}<br/> + {rsa_mgf1_md, atom()}<br/> + {rsa_oaep_label, binary()}}<br/> + {rsa_oaep_md, atom()} + </cell> + <cell></cell> + <cell>x (2)<br/> + x (2)<br/> + x (3)<br/> + x (3) + </cell> + <cell></cell> + </row> + <row><cell>{rsa_padding,rsa_no_padding}</cell> + <cell>x (1)</cell> + <cell></cell> + <cell></cell> + </row> + <!-- row><cell>{rsa_padding,rsa_sslv23_padding}</cell> + <cell></cell> + <cell></cell> + <cell></cell> + </row --> <tcaption></tcaption> </table> + <p>Notes:</p> + <list type="ordered"> + <item>(1) OpenSSL ≤ 1.0.0</item> + <item>(2) OpenSSL ≥ 1.0.1</item> + <item>(3) OpenSSL ≥ 1.1.0</item> + </list> </section> <section> diff --git a/lib/crypto/test/crypto_SUITE.erl b/lib/crypto/test/crypto_SUITE.erl index 495c2adb55..025c46aab0 100644 --- a/lib/crypto/test/crypto_SUITE.erl +++ b/lib/crypto/test/crypto_SUITE.erl @@ -495,14 +495,14 @@ sign_verify(Config) when is_list(Config) -> public_encrypt() -> [{doc, "Test public_encrypt/decrypt "}]. public_encrypt(Config) when is_list(Config) -> - Params = proplists:get_value(pub_priv_encrypt, Config), + Params = proplists:get_value(pub_pub_encrypt, Config, []), lists:foreach(fun do_public_encrypt/1, Params). %%-------------------------------------------------------------------- private_encrypt() -> [{doc, "Test private_encrypt/decrypt functions. "}]. private_encrypt(Config) when is_list(Config) -> - Params = proplists:get_value(pub_priv_encrypt, Config), + Params = proplists:get_value(pub_priv_encrypt, Config, []), lists:foreach(fun do_private_encrypt/1, Params). %%-------------------------------------------------------------------- @@ -943,30 +943,6 @@ negative_verify(Type, Hash, Msg, Signature, Public, Options) -> ok end. --define(PUB_PRIV_ENC_DEC_CATCH(Type,Padding), - CC:EE -> - ct:log("~p:~p in ~p:~p/~p, line ~p.~n" - "Type = ~p~nPadding = ~p", - [CC,EE,?MODULE,?FUNCTION_NAME,?FUNCTION_ARITY,?LINE,(Type),(Padding)]), - MaybeUnsupported = - case crypto:info_lib() of - [{<<"OpenSSL">>,_,_}] -> - is_list(Padding) andalso - lists:any(fun(P) -> lists:member(P,(Padding)) end, - [{rsa_padding, rsa_pkcs1_oaep_padding}, - {rsa_padding, rsa_sslv23_padding}, - {rsa_padding, rsa_x931_padding}]); - _ -> - false - end, - case CC of - error when MaybeUnsupported -> - ct:comment("Padding unsupported?",[]); - _ -> - ct:fail({?FUNCTION_NAME,CC,EE,(Type),(Padding)}) - end - ). - do_public_encrypt({Type, Public, Private, Msg, Padding}) -> try crypto:public_encrypt(Type, Msg, Public, Padding) @@ -980,10 +956,12 @@ do_public_encrypt({Type, Public, Private, Msg, Padding}) -> Other -> ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, Other}}) catch - ?PUB_PRIV_ENC_DEC_CATCH(Type, Padding) + CC:EE -> + ct:fail({{crypto, private_decrypt, [Type, PublicEcn, Private, Padding]}, {expected, Msg}, {got, {CC,EE}}}) end catch - ?PUB_PRIV_ENC_DEC_CATCH(Type, Padding) + CC:EE -> + ct:fail({{crypto, public_encrypt, [Type, Msg, Public, Padding]}, {got, {CC,EE}}}) end. @@ -1000,10 +978,12 @@ do_private_encrypt({Type, Public, Private, Msg, Padding}) -> Other -> ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, Other}}) catch - ?PUB_PRIV_ENC_DEC_CATCH(Type, Padding) + CC:EE -> + ct:fail({{crypto, public_decrypt, [Type, PrivEcn, Public, Padding]}, {expected, Msg}, {got, {CC,EE}}}) end catch - ?PUB_PRIV_ENC_DEC_CATCH(Type, Padding) + CC:EE -> + ct:fail({{crypto, private_encrypt, [Type, Msg, Private, Padding]}, {got, {CC,EE}}}) end. do_generate_compute({srp = Type, UserPrivate, UserGenParams, UserComParams, @@ -1395,36 +1375,42 @@ group_config(sha3_384 = Type, Config) -> group_config(sha3_512 = Type, Config) -> {Msgs,Digests} = sha3_test_vectors(Type), [{hash, {Type, Msgs, Digests}}, {hmac, hmac_sha3(Type)} | Config]; -group_config(rsa = Type, Config) -> +group_config(rsa, Config) -> Msg = rsa_plain(), Public = rsa_public(), Private = rsa_private(), PublicS = rsa_public_stronger(), PrivateS = rsa_private_stronger(), - SignVerify = - case ?config(fips, Config) of - true -> - %% Use only the strong keys in FIPS mode - sign_verify_tests(Type, Msg, - PublicS, PrivateS, - PublicS, PrivateS); - false -> - sign_verify_tests(Type, Msg, - Public, Private, - PublicS, PrivateS) - end, MsgPubEnc = <<"7896345786348 Asldi">>, - PubPrivEnc = [{rsa, PublicS, PrivateS, MsgPubEnc, rsa_pkcs1_padding}, - {rsa, PublicS, PrivateS, MsgPubEnc, [{rsa_padding, rsa_pkcs1_padding}]}, - {rsa, PublicS, PrivateS, MsgPubEnc, [{rsa_padding, rsa_sslv23_padding}]}, - {rsa, PublicS, PrivateS, MsgPubEnc, [{rsa_padding, rsa_x931_padding}]}, - rsa_oaep(), - %% rsa_oaep_label(), - %% rsa_oaep256(), - no_padding() + SignVerify_OptsToTry = [[{rsa_padding, rsa_x931_padding}], + [{rsa_padding, rsa_pkcs1_padding}], + [{rsa_padding, rsa_pkcs1_pss_padding}], + [{rsa_padding, rsa_pkcs1_pss_padding}, {rsa_pss_saltlen, -2}], + [{rsa_padding, rsa_pkcs1_pss_padding}, {rsa_pss_saltlen, 5}], + [{rsa_padding, rsa_pkcs1_pss_padding}, {rsa_mgf1_md,sha}], + [{rsa_padding, rsa_pkcs1_pss_padding}, {rsa_mgf1_md,sha}, {rsa_pss_saltlen, 5}] + ], + PrivEnc_OptsToTry = [rsa_pkcs1_padding, % Compatibility + [{rsa_pad, rsa_pkcs1_padding}], % Compatibility + [{rsa_padding, rsa_pkcs1_padding}], + [{rsa_padding,rsa_x931_padding}] + ], + PubEnc_OptsToTry = [rsa_pkcs1_padding, % Compatibility + [{rsa_pad, rsa_pkcs1_padding}], % Compatibility + [{rsa_padding, rsa_pkcs1_padding}], + [{rsa_padding,rsa_pkcs1_oaep_padding}], + [{rsa_padding,rsa_pkcs1_oaep_padding}, {rsa_oaep_label, <<"Hej hopp">>}], + [{rsa_padding,rsa_pkcs1_oaep_padding}, {rsa_oaep_md,sha}], + [{rsa_padding,rsa_pkcs1_oaep_padding}, {rsa_oaep_md,sha}, {rsa_oaep_label, <<"Hej hopp">>}], + [{rsa_padding,rsa_pkcs1_oaep_padding}, {rsa_mgf1_md,sha}], + [{rsa_padding,rsa_pkcs1_oaep_padding}, {rsa_mgf1_md,sha}, {rsa_oaep_label, <<"Hej hopp">>}], + [{rsa_padding,rsa_pkcs1_oaep_padding}, {rsa_mgf1_md,sha}, {rsa_oaep_md,sha}, {rsa_oaep_label, <<"Hej hopp">>}] ], - Generate = [{rsa, 1024, 3}, {rsa, 2048, 17}, {rsa, 3072, 65537}], - [{sign_verify, SignVerify}, {pub_priv_encrypt, PubPrivEnc}, {generate, Generate} | Config]; + [{sign_verify, rsa_sign_verify_tests(Config, Msg, Public, Private, PublicS, PrivateS, SignVerify_OptsToTry)}, + {pub_priv_encrypt, gen_rsa_pub_priv_tests(PublicS, PrivateS, MsgPubEnc, PrivEnc_OptsToTry)}, + {pub_pub_encrypt, gen_rsa_pub_priv_tests(PublicS, PrivateS, MsgPubEnc, PubEnc_OptsToTry)}, + {generate, [{rsa, 1024, 3}, {rsa, 2048, 17}, {rsa, 3072, 65537}]} + | Config]; group_config(dss = Type, Config) -> Msg = dss_plain(), Public = dss_params() ++ [dss_public()], @@ -1553,40 +1539,74 @@ group_config(aes_cbc, Config) -> group_config(_, Config) -> Config. -sign_verify_tests(Type, Msg, Public, Private, PublicS, PrivateS) -> - gen_sign_verify_tests(Type, [md5, ripemd160, sha, sha224, sha256], Msg, Public, Private, - [undefined, - [{rsa_padding, rsa_pkcs1_pss_padding}], - [{rsa_padding, rsa_pkcs1_pss_padding}, {rsa_pss_saltlen, 0}], - [{rsa_padding, rsa_x931_padding}] - ]) ++ - gen_sign_verify_tests(Type, [sha384, sha512], Msg, PublicS, PrivateS, - [undefined, - [{rsa_padding, rsa_pkcs1_pss_padding}], - [{rsa_padding, rsa_pkcs1_pss_padding}, {rsa_pss_saltlen, 0}], - [{rsa_padding, rsa_x931_padding}] - ]). - -gen_sign_verify_tests(Type, Hashs, Msg, Public, Private, Opts) -> +rsa_sign_verify_tests(Config, Msg, Public, Private, PublicS, PrivateS, OptsToTry) -> + case ?config(fips, Config) of + true -> + %% Use only the strong keys in FIPS mode + rsa_sign_verify_tests(Msg, + PublicS, PrivateS, + PublicS, PrivateS, + OptsToTry); + false -> + rsa_sign_verify_tests(Msg, + Public, Private, + PublicS, PrivateS, + OptsToTry) + end. + +rsa_sign_verify_tests(Msg, Public, Private, PublicS, PrivateS, OptsToTry) -> + gen_rsa_sign_verify_tests([md5, ripemd160, sha, sha224, sha256], Msg, Public, Private, + [undefined | OptsToTry]) ++ + gen_rsa_sign_verify_tests([sha384, sha512], Msg, PublicS, PrivateS, + [undefined | OptsToTry]). + +gen_rsa_sign_verify_tests(Hashs, Msg, Public, Private, Opts) -> + SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []), lists:foldr(fun(Hash, Acc0) -> case is_supported(Hash) of true -> lists:foldr(fun (undefined, Acc1) -> - [{Type, Hash, Public, Private, Msg} | Acc1]; + [{rsa, Hash, Public, Private, Msg} | Acc1]; ([{rsa_padding, rsa_x931_padding} | _], Acc1) when Hash =:= md5 orelse Hash =:= ripemd160 orelse Hash =:= sha224 -> Acc1; (Opt, Acc1) -> - [{Type, Hash, Public, Private, Msg, Opt} | Acc1] + case rsa_opt_is_supported(Opt, SupOpts) of + true -> + [{rsa, Hash, Public, Private, Msg, Opt} | Acc1]; + false -> + Acc1 + end end, Acc0, Opts); false -> Acc0 end end, [], Hashs). + +gen_rsa_pub_priv_tests(Public, Private, Msg, OptsToTry) -> + SupOpts = proplists:get_value(rsa_opts, crypto:supports(), []), + lists:foldr(fun(Opt, Acc) -> + case rsa_opt_is_supported(Opt, SupOpts) of + true -> + [{rsa, Public, Private, Msg, Opt} | Acc]; + false -> + Acc + end + end, [], OptsToTry). + + +rsa_opt_is_supported([_|_]=Opt, Sup) -> + lists:all(fun(O) -> rsa_opt_is_supported(O,Sup) end, Opt); +rsa_opt_is_supported({A,B}, Sup) -> + rsa_opt_is_supported(A,Sup) orelse rsa_opt_is_supported(B,Sup); +rsa_opt_is_supported(Opt, Sup) -> + lists:member(Opt, Sup). + + rfc_1321_msgs() -> [<<"">>, <<"a">>, @@ -2803,6 +2823,8 @@ ecdh() -> dh() -> {dh, 90970053988169282502023478715631717259407236400413906591937635666709823903223997309250405131675572047545403771567755831138144089197560332757755059848492919215391041119286178688014693040542889497092308638580104031455627238700168892909539193174537248629499995652186913900511641708112112482297874449292467498403, 2}. + + rsa_oaep() -> %% ftp://ftp.rsa.com/pub/rsalabs/tmp/pkcs1v15crypt-vectors.txt Public = [hexstr2bin("010001"), @@ -2877,13 +2899,6 @@ cmac_nist(Config, aes_cbc256 = Type) -> read_rsp(Config, Type, ["CMACGenAES256.rsp", "CMACVerAES256.rsp"]). -no_padding() -> - Public = [_, Mod] = rsa_public_stronger(), - Private = rsa_private_stronger(), - MsgLen = erlang:byte_size(int_to_bin(Mod)), - Msg = list_to_binary(lists:duplicate(MsgLen, $X)), - {rsa, Public, Private, Msg, rsa_no_padding}. - int_to_bin(X) when X < 0 -> int_to_bin_neg(X, []); int_to_bin(X) -> int_to_bin_pos(X, []). @@ -3063,7 +3078,7 @@ parse_rsp(Type, file := File, alen := Alen, plen := Plen, - nlen := Nlen, + nlen := _Nlen, tlen := Tlen, key := Key, nonce := IV, diff --git a/lib/inets/src/http_client/httpc_request.erl b/lib/inets/src/http_client/httpc_request.erl index 9b81bd7a80..0f20d93bc1 100644 --- a/lib/inets/src/http_client/httpc_request.erl +++ b/lib/inets/src/http_client/httpc_request.erl @@ -213,15 +213,18 @@ update_body(Headers, Body) -> update_headers(Headers, ContentType, Body, []) -> case Body of [] -> - Headers#http_request_h{'content-length' = "0"}; + Headers1 = Headers#http_request_h{'content-length' = "0"}, + handle_content_type(Headers1, ContentType); <<>> -> - Headers#http_request_h{'content-length' = "0"}; + Headers1 = Headers#http_request_h{'content-length' = "0"}, + handle_content_type(Headers1, ContentType); {Fun, _Acc} when is_function(Fun, 1) -> %% A client MUST NOT generate a 100-continue expectation in a request %% that does not include a message body. This implies that either the %% Content-Length or the Transfer-Encoding header MUST be present. %% DO NOT send content-type when Body is empty. - Headers#http_request_h{'content-type' = ContentType}; + Headers1 = Headers#http_request_h{'content-type' = ContentType}, + handle_transfer_encoding(Headers1); _ -> Headers#http_request_h{ 'content-length' = body_length(Body), @@ -230,12 +233,26 @@ update_headers(Headers, ContentType, Body, []) -> update_headers(_, _, _, HeadersAsIs) -> HeadersAsIs. +handle_transfer_encoding(Headers = #http_request_h{'transfer-encoding' = undefined}) -> + Headers; +handle_transfer_encoding(Headers) -> + %% RFC7230 3.3.2 + %% A sender MUST NOT send a 'Content-Length' header field in any message + %% that contains a 'Transfer-Encoding' header field. + Headers#http_request_h{'content-length' = undefined}. + body_length(Body) when is_binary(Body) -> integer_to_list(size(Body)); body_length(Body) when is_list(Body) -> integer_to_list(length(Body)). +%% Set 'Content-Type' when it is explicitly set. +handle_content_type(Headers, "") -> + Headers; +handle_content_type(Headers, ContentType) -> + Headers#http_request_h{'content-type' = ContentType}. + method(Method) -> http_util:to_upper(atom_to_list(Method)). diff --git a/lib/inets/test/httpc_SUITE.erl b/lib/inets/test/httpc_SUITE.erl index 3d375222b5..8357e02014 100644 --- a/lib/inets/test/httpc_SUITE.erl +++ b/lib/inets/test/httpc_SUITE.erl @@ -156,6 +156,7 @@ only_simulated() -> multipart_chunks, get_space, delete_no_body, + post_with_content_type, stream_fun_server_close ]. @@ -170,7 +171,8 @@ misc() -> server_does_not_exist, timeout_memory_leak, wait_for_whole_response, - post_204_chunked + post_204_chunked, + chunkify_fun ]. sim_mixed() -> @@ -1408,7 +1410,8 @@ post_204_chunked(_Config) -> {ok, ListenSocket} = gen_tcp:listen(0, [{active,once}, binary]), {ok,{_,Port}} = inet:sockname(ListenSocket), - spawn(fun () -> custom_server(Msg, Chunk, ListenSocket) end), + spawn(fun () -> custom_server(Msg, Chunk, ListenSocket, + fun post_204_receive/0) end), {ok,Host} = inet:gethostname(), End = "/cgi-bin/erl/httpd_example:post_204", @@ -1418,16 +1421,26 @@ post_204_chunked(_Config) -> %% Second request times out in the faulty case. {ok, _} = httpc:request(post, {URL, [], "text/html", []}, [], []). -custom_server(Msg, Chunk, ListenSocket) -> +post_204_receive() -> + receive + {tcp, _, Msg} -> + ct:log("Message received: ~p", [Msg]) + after + 1000 -> + ct:fail("Timeout: did not recive packet") + end. + +%% Custom server is used to test special cases when using chunked encoding +custom_server(Msg, Chunk, ListenSocket, ReceiveFun) -> {ok, Accept} = gen_tcp:accept(ListenSocket), - receive_packet(), + ReceiveFun(), send_response(Msg, Chunk, Accept), - custom_server_loop(Msg, Chunk, Accept). + custom_server_loop(Msg, Chunk, Accept, ReceiveFun). -custom_server_loop(Msg, Chunk, Accept) -> - receive_packet(), +custom_server_loop(Msg, Chunk, Accept, ReceiveFun) -> + ReceiveFun(), send_response(Msg, Chunk, Accept), - custom_server_loop(Msg, Chunk, Accept). + custom_server_loop(Msg, Chunk, Accept, ReceiveFun). send_response(Msg, Chunk, Socket) -> inet:setopts(Socket, [{active, once}]), @@ -1435,15 +1448,54 @@ send_response(Msg, Chunk, Socket) -> timer:sleep(250), gen_tcp:send(Socket, Chunk). -receive_packet() -> +%%-------------------------------------------------------------------- +chunkify_fun() -> + [{doc,"Test that a chunked encoded request does not include the 'Content-Length header'"}]. +chunkify_fun(_Config) -> + Msg = "HTTP/1.1 204 No Content\r\n" ++ + "Date: Thu, 23 Aug 2018 13:36:29 GMT\r\n" ++ + "Content-Type: text/html\r\n" ++ + "Server: inets/6.5.2.3\r\n" ++ + "Cache-Control: no-cache\r\n" ++ + "Pragma: no-cache\r\n" ++ + "Expires: Fri, 24 Aug 2018 07:49:35 GMT\r\n" ++ + "Transfer-Encoding: chunked\r\n" ++ + "\r\n", + Chunk = "0\r\n\r\n", + + {ok, ListenSocket} = gen_tcp:listen(0, [{active,once}, binary]), + {ok,{_,Port}} = inet:sockname(ListenSocket), + spawn(fun () -> custom_server(Msg, Chunk, ListenSocket, + fun chunkify_receive/0) end), + + {ok,Host} = inet:gethostname(), + End = "/cgi-bin/erl/httpd_example", + URL = ?URL_START ++ Host ++ ":" ++ integer_to_list(Port) ++ End, + Fun = fun(_) -> {ok,<<1>>,eof_body} end, + Acc = start, + + {ok, {{_,204,_}, _, _}} = + httpc:request(put, {URL, [], "text/html", {chunkify, Fun, Acc}}, [], []). + +chunkify_receive() -> + Error = "HTTP/1.1 500 Internal Server Error\r\n" ++ + "Content-Length: 0\r\n\r\n", receive - {tcp, _, Msg} -> - ct:log("Message received: ~p", [Msg]) + {tcp, Port, Msg} -> + case binary:match(Msg, <<"content-length">>) of + nomatch -> + ct:log("Message received: ~s", [binary_to_list(Msg)]); + {_, _} -> + ct:log("Message received (negative): ~s", [binary_to_list(Msg)]), + %% Signal a testcase failure when the received HTTP request + %% contains a 'Content-Length' header. + gen_tcp:send(Port, Error), + ct:fail("Content-Length present in received headers.") + end after 1000 -> ct:fail("Timeout: did not recive packet") end. - %%-------------------------------------------------------------------- stream_fun_server_close() -> [{doc, "Test that an error msg is received when using a receiver fun as stream target"}]. @@ -1550,6 +1602,15 @@ delete_no_body(Config) when is_list(Config) -> httpc:request(delete, {URL, [], "text/plain", "TEST"}, [], []). %%-------------------------------------------------------------------- +post_with_content_type(doc) -> + ["Test that a POST request with explicit 'Content-Type' does not drop the 'Content-Type' header - Solves ERL-736"]; +post_with_content_type(Config) when is_list(Config) -> + URL = url(group_name(Config), "/delete_no_body.html", Config), + %% Simulated server replies 500 if 'Content-Type' header is present + {ok, {{_,500,_}, _, _}} = + httpc:request(post, {URL, [], "application/x-www-form-urlencoded", ""}, [], []). + +%%-------------------------------------------------------------------- request_options() -> [{doc, "Test http get request with socket options against local server (IPv6)"}]. request_options(Config) when is_list(Config) -> diff --git a/lib/kernel/doc/src/inet.xml b/lib/kernel/doc/src/inet.xml index ed775d67eb..127c110df4 100644 --- a/lib/kernel/doc/src/inet.xml +++ b/lib/kernel/doc/src/inet.xml @@ -198,6 +198,79 @@ fe80::204:acff:fe17:bf38 </desc> </datatype> <datatype> + <name name="getifaddrs_ifopts"/> + <desc> + <p> + Interface address description list returned from + <seealso marker="#getifaddrs/0"><c>getifaddrs/0,1</c></seealso> + for a named interface, translated from the returned + data of the POSIX API function <c>getaddrinfo()</c>. + </p> + <p> + <c><anno>Hwaddr</anno></c> is hardware dependent, + for example, on Ethernet interfaces it is + the 6-byte Ethernet address (MAC address (EUI-48 address)). + </p> + <p> + The tuples <c>{addr,<anno>Addr</anno>}</c>, + <c>{netmask,<anno>Netmask</anno>}</c>, and possibly + <c>{broadaddr,<anno>Broadaddr</anno>}</c> or + <c>{dstaddr,<anno>Dstaddr</anno>}</c> + are repeated in the list + if the interface has got multiple addresses. + An interface may have multiple <c>{flag,_}</c> tuples + for example if it has different flags + for different address families. + Multiple <c>{hwaddr,<anno>Hwaddr</anno>}</c> tuples + is hard to say anything definite about, though. + The tuple <c>{flag,<anno>Flags</anno>}</c> is mandatory, + all others are optional. + </p> + <p> + Do not rely too much on the order + of <c><anno>Flags</anno></c> atoms + or the <c><anno>Ifopt</anno></c> tuples. + There are however some rules: + </p> + <list type="bulleted"> + <item><p> + A <c>{flag,_}</c> tuple applies to all other tuples that follow. + </p></item> + <item><p> + Immediately after <c>{addr,_}</c> follows <c>{netmask,_}</c>. + </p></item> + <item><p> + Immediately thereafter may <c>{broadaddr,_}</c> follow + if <c>broadcast</c> is member of <c><anno>Flags</anno></c>, + or <c>{dstaddr,_}</c> if <c>pointtopoint</c> + is member of <c><anno>Flags</anno></c>. + Both <c>{dstaddr,_}</c> and <c>{broadaddr,_}</c> + does not occur for the same <c>{addr,_}</c>. + </p></item> + <item><p> + Any <c>{netmask,_}</c>, <c>{broadaddr,_}</c>, or + <c>{dstaddr,_}</c> tuples that follow an + <c>{addr,<anno>Addr</anno>}</c> + tuple concerns the address <c><anno>Addr</anno></c>. + </p></item> + </list> + <p> + The tuple <c>{hwaddr,_}</c> is not returned on Solaris, as the + hardware address historically belongs to the link layer + and it is not returned by the Solaris API function + <c>getaddrinfo()</c>. + </p> + <warning> + <p> + On Windows, the data is fetched from different + OS API functions, so the <c><anno>Netmask</anno></c> + and <c><anno>Broadaddr</anno></c> values may be calculated, + just as some <c><anno>Flags</anno></c> values. + </p> + </warning> + </desc> + </datatype> + <datatype> <name name="posix"/> <desc> <p>An atom that is named from the POSIX error codes used in Unix, @@ -324,38 +397,64 @@ fe80::204:acff:fe17:bf38 <name name="getifaddrs" arity="0"/> <fsummary>Return a list of interfaces and their addresses.</fsummary> <desc> - <p>Returns a list of 2-tuples containing interface names and the - interface addresses. <c><anno>Ifname</anno></c> is a Unicode string. - <c><anno>Hwaddr</anno></c> is hardware dependent, for example, on - Ethernet interfaces - it is the 6-byte Ethernet address (MAC address (EUI-48 address)).</p> - <p>The tuples <c>{addr,<anno>Addr</anno>}</c>, <c>{netmask,_}</c>, and - <c>{broadaddr,_}</c> are repeated in the result list if the interface - has multiple addresses. If you come across an interface with - multiple <c>{flag,_}</c> or <c>{hwaddr,_}</c> tuples, you have - a strange interface or possibly a bug in this function. The tuple - <c>{flag,_}</c> is mandatory, all others are optional.</p> - <p>Do not rely too much on the order of <c><anno>Flag</anno></c> atoms - or <c><anno>Ifopt</anno></c> tuples. There are however some rules:</p> - <list type="bulleted"> - <item><p>Immediately after - <c>{addr,_}</c> follows <c>{netmask,_}</c>.</p></item> - <item><p>Immediately thereafter follows <c>{broadaddr,_}</c> if flag - <c>broadcast</c> is <em>not</em> set and flag - <c>pointtopoint</c> <em>is</em> set.</p></item> - <item><p>Any <c>{netmask,_}</c>, <c>{broadaddr,_}</c>, or - <c>{dstaddr,_}</c> tuples that follow an <c>{addr,_}</c> - tuple concerns that address.</p></item> - </list> - <p>The tuple <c>{hwaddr,_}</c> is not returned on Solaris, as the - hardware address historically belongs to the link layer and only - the superuser can read such addresses.</p> - <warning> - <p>On Windows, the data is fetched from different OS API functions, - so the <c><anno>Netmask</anno></c> and <c><anno>Broadaddr</anno></c> - values can be calculated, just as some <c><anno>Flag</anno></c> - values. Report flagrant bugs.</p> - </warning> + <p> + Returns a list of 2-tuples containing interface names and + the interfaces' addresses. <c><anno>Ifname</anno></c> + is a Unicode string and + <c><anno>Ifopts</anno></c> is a list of + interface address description tuples. + </p> + <p> + The interface address description tuples + are documented under the type of the + <seealso marker="#type-getifaddrs_ifopts"> + <c><anno>Ifopts</anno></c> + </seealso> + value. + </p> + </desc> + </func> + + <func> + <name>getifaddrs(Opts) -> + {ok, [{Ifname, Ifopts}]} | {error, Posix} + </name> + <fsummary>Return a list of interfaces and their addresses.</fsummary> + <type> + <v> + Opts = [{netns, Namespace}] + </v> + <v> + Namespace = + <seealso marker="file#type-filename_all"> + file:filename_all() + </seealso> + </v> + <v>Ifname = string()</v> + <v> + Ifopts = + <seealso marker="#type-getifaddrs_ifopts"> + getifaddrs_ifopts() + </seealso> + </v> + <v>Posix = <seealso marker="#type-posix">posix()</seealso></v> + </type> + <desc> + <p> + The same as + <seealso marker="#getifaddrs/0"><c>getifaddrs/0</c></seealso> + but the <c>Option</c> + <c>{netns, Namespace}</c> sets a network namespace + for the OS call, on platforms that supports that feature. + </p> + <p> + See the socket option + <seealso marker="#option-netns"> + <c>{netns, Namespace}</c> + </seealso> + under + <seealso marker="#setopts/2"><c>setopts/2</c></seealso>. + </p> </desc> </func> @@ -950,20 +1049,29 @@ get_tcpi_sacked(Sock) -> </item> <tag><c>{mode, Mode :: binary | list}</c></tag> <item> - <p>Received <c>Packet</c> is delivered as defined by <c>Mode</c>. + <p> + Received <c>Packet</c> is delivered as defined by <c>Mode</c>. </p> </item> - <tag><c>{netns, Namespace :: file:filename_all()}</c></tag> + <tag> + <marker id="option-netns"/> + <c>{netns, Namespace :: file:filename_all()}</c> + </tag> <item> - <p>Sets a network namespace for the socket. Parameter + <p> + Sets a network namespace for the socket. Parameter <c>Namespace</c> is a filename defining the namespace, for example, <c>"/var/run/netns/example"</c>, typically created by command <c>ip netns add example</c>. This option must be used in a function call that creates a socket, that is, <seealso marker="gen_tcp#connect/3"><c>gen_tcp:connect/3,4</c></seealso>, <seealso marker="gen_tcp#listen/2"><c>gen_tcp:listen/2</c></seealso>, - <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso>, or - <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>.</p> + <seealso marker="gen_udp#open/1"><c>gen_udp:open/1,2</c></seealso> + or + <seealso marker="gen_sctp#open/0"><c>gen_sctp:open/0,1,2</c></seealso>, + and also + <seealso marker="#getifaddrs/1"><c>getifaddrs/1</c></seealso>. + </p> <p>This option uses the Linux-specific syscall <c>setns()</c>, such as in Linux kernel 3.0 or later, and therefore only exists when the runtime system diff --git a/lib/kernel/src/inet.erl b/lib/kernel/src/inet.erl index 5dd68dc285..9f22eb6aaa 100644 --- a/lib/kernel/src/inet.erl +++ b/lib/kernel/src/inet.erl @@ -154,6 +154,15 @@ 'running' | 'multicast' | 'loopback']} | {'hwaddr', ether_address()}. +-type getifaddrs_ifopts() :: + [Ifopt :: {flags, Flags :: [up | broadcast | loopback | + pointtopoint | running | multicast]} | + {addr, Addr :: ip_address()} | + {netmask, Netmask :: ip_address()} | + {broadaddr, Broadaddr :: ip_address()} | + {dstaddr, Dstaddr :: ip_address()} | + {hwaddr, Hwaddr :: [byte()]}]. + -type address_family() :: 'inet' | 'inet6' | 'local'. -type socket_protocol() :: 'tcp' | 'udp' | 'sctp'. -type socket_type() :: 'stream' | 'dgram' | 'seqpacket'. @@ -321,32 +330,32 @@ getopts(Socket, Opts) -> Other end. --spec getifaddrs(Socket :: socket()) -> - {'ok', [string()]} | {'error', posix()}. - +-spec getifaddrs( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> + {'ok', [{Ifname :: string(), + Ifopts :: getifaddrs_ifopts()}]} + | {'error', posix()}. +getifaddrs(Opts) when is_list(Opts) -> + withsocket(fun(S) -> prim_inet:getifaddrs(S) end, Opts); getifaddrs(Socket) -> prim_inet:getifaddrs(Socket). --spec getifaddrs() -> {ok, Iflist} | {error, posix()} when - Iflist :: [{Ifname,[Ifopt]}], - Ifname :: string(), - Ifopt :: {flags,[Flag]} | {addr,Addr} | {netmask,Netmask} - | {broadaddr,Broadaddr} | {dstaddr,Dstaddr} - | {hwaddr,Hwaddr}, - Flag :: up | broadcast | loopback | pointtopoint - | running | multicast, - Addr :: ip_address(), - Netmask :: ip_address(), - Broadaddr :: ip_address(), - Dstaddr :: ip_address(), - Hwaddr :: [byte()]. - +-spec getifaddrs() -> + {'ok', [{Ifname :: string(), + Ifopts :: getifaddrs_ifopts()}]} + | {'error', posix()}. getifaddrs() -> withsocket(fun(S) -> prim_inet:getifaddrs(S) end). --spec getiflist(Socket :: socket()) -> - {'ok', [string()]} | {'error', posix()}. +-spec getiflist( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> + {'ok', [string()]} | {'error', posix()}. + +getiflist(Opts) when is_list(Opts) -> + withsocket(fun(S) -> prim_inet:getiflist(S) end, Opts); getiflist(Socket) -> prim_inet:getiflist(Socket). @@ -363,11 +372,19 @@ getiflist() -> ifget(Socket, Name, Opts) -> prim_inet:ifget(Socket, Name, Opts). --spec ifget(Name :: string() | atom(), Opts :: [if_getopt()]) -> +-spec ifget( + Name :: string() | atom(), + Opts :: [if_getopt() | + {netns, Namespace :: file:filename_all()}]) -> {'ok', [if_getopt_result()]} | {'error', posix()}. ifget(Name, Opts) -> - withsocket(fun(S) -> prim_inet:ifget(S, Name, Opts) end). + {NSOpts,IFOpts} = + lists:partition( + fun ({netns,_}) -> true; + (_) -> false + end, Opts), + withsocket(fun(S) -> prim_inet:ifget(S, Name, IFOpts) end, NSOpts). -spec ifset(Socket :: socket(), Name :: string() | atom(), @@ -377,11 +394,19 @@ ifget(Name, Opts) -> ifset(Socket, Name, Opts) -> prim_inet:ifset(Socket, Name, Opts). --spec ifset(Name :: string() | atom(), Opts :: [if_setopt()]) -> +-spec ifset( + Name :: string() | atom(), + Opts :: [if_setopt() | + {netns, Namespace :: file:filename_all()}]) -> 'ok' | {'error', posix()}. ifset(Name, Opts) -> - withsocket(fun(S) -> prim_inet:ifset(S, Name, Opts) end). + {NSOpts,IFOpts} = + lists:partition( + fun ({netns,_}) -> true; + (_) -> false + end, Opts), + withsocket(fun(S) -> prim_inet:ifset(S, Name, IFOpts) end, NSOpts). -spec getif() -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | @@ -391,10 +416,14 @@ getif() -> withsocket(fun(S) -> getif(S) end). %% backwards compatible getif --spec getif(Socket :: socket()) -> +-spec getif( + [Option :: {netns, Namespace :: file:filename_all()}] + | socket()) -> {'ok', [{ip_address(), ip_address() | 'undefined', ip_address()}]} | {'error', posix()}. +getif(Opts) when is_list(Opts) -> + withsocket(fun(S) -> getif(S) end, Opts); getif(Socket) -> case prim_inet:getiflist(Socket) of {ok, IfList} -> @@ -415,7 +444,10 @@ getif(Socket) -> end. withsocket(Fun) -> - case inet_udp:open(0,[]) of + withsocket(Fun, []). +%% +withsocket(Fun, Opts) -> + case inet_udp:open(0, Opts) of {ok,Socket} -> Res = Fun(Socket), inet_udp:close(Socket), diff --git a/lib/kernel/src/kernel.appup.src b/lib/kernel/src/kernel.appup.src index 305a1c788c..0c0435e051 100644 --- a/lib/kernel/src/kernel.appup.src +++ b/lib/kernel/src/kernel.appup.src @@ -20,9 +20,11 @@ %% Up from - max one major revision back [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21 + {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 + {<<"6\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21.1 %% Down to - max one major revision back [{<<"5\\.3(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 {<<"5\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21 + {<<"6\\.0(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 + {<<"6\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.1 }. diff --git a/lib/kernel/test/inet_SUITE.erl b/lib/kernel/test/inet_SUITE.erl index 2e5f8c7d2c..f436eafad3 100644 --- a/lib/kernel/test/inet_SUITE.erl +++ b/lib/kernel/test/inet_SUITE.erl @@ -1060,28 +1060,26 @@ getservbyname_overflow(Config) when is_list(Config) -> getifaddrs(Config) when is_list (Config) -> {ok,IfAddrs} = inet:getifaddrs(), io:format("IfAddrs = ~p.~n", [IfAddrs]), - case - {os:type(), - [If || - {If,Opts} <- IfAddrs, - lists:keymember(hwaddr, 1, Opts)]} of - {{unix,sunos},[]} -> ok; - {OT,[]} -> - ct:fail({should_have_hwaddr,OT}); - _ -> ok + case [If || {If,Opts} <- IfAddrs, lists:keymember(hwaddr, 1, Opts)] of + [] -> + case os:type() of + {unix,sunos} -> ok; + OT -> + ct:fail({should_have_hwaddr,OT}) + end; + [_|_] -> ok end, - Addrs = - [element(1, A) || A <- ifaddrs(IfAddrs)], + Addrs = ifaddrs(IfAddrs), io:format("Addrs = ~p.~n", [Addrs]), [check_addr(Addr) || Addr <- Addrs], ok. -check_addr({addr,Addr}) +check_addr(Addr) when tuple_size(Addr) =:= 8, element(1, Addr) band 16#FFC0 =:= 16#FE80 -> io:format("Addr: ~p link local; SKIPPED!~n", [Addr]), ok; -check_addr({addr,Addr}) -> +check_addr(Addr) -> io:format("Addr: ~p.~n", [Addr]), Ping = "ping", Pong = "pong", @@ -1097,78 +1095,86 @@ check_addr({addr,Addr}) -> ok = gen_tcp:close(S2), ok = gen_tcp:close(L). --record(ifopts, {name,flags,addrs=[],hwaddr}). - -ifaddrs([]) -> []; -ifaddrs([{If,Opts}|IOs]) -> - #ifopts{flags=F} = Ifopts = check_ifopts(Opts, #ifopts{name=If}), - case F of - {flags,Flags} -> - case lists:member(running, Flags) of - true -> Ifopts#ifopts.addrs; - false -> [] - end ++ ifaddrs(IOs); - undefined -> - ifaddrs(IOs) +ifaddrs(IfOpts) -> + IfMap = collect_ifopts(IfOpts), + ChkFun = + fun Self({{_,Flags} = Key, Opts}, ok) -> + Broadcast = lists:member(broadcast, Flags), + P2P = lists:member(pointtopoint, Flags), + case Opts of + [{addr,_},{netmask,_},{broadaddr,_}|Os] + when Broadcast -> + Self({Key, Os}, ok); + [{addr,_},{netmask,_},{dstaddr,_}|Os] + when P2P -> + Self({Key, Os}, ok); + [{addr,_},{netmask,_}|Os] -> + Self({Key, Os}, ok); + [{hwaddr,_}|Os] -> + Self({Key, Os}, ok); + [] -> + ok + end + end, + fold_ifopts(ChkFun, ok, IfMap), + AddrsFun = + fun ({{_,Flags}, Opts}, Acc) -> + case + lists:member(running, Flags) + andalso (not lists:member(pointtopoint, Flags)) + of + true -> + lists:reverse( + [Addr || {addr,Addr} <- Opts], + Acc); + false -> + Acc + end + end, + fold_ifopts(AddrsFun, [], IfMap). + +collect_ifopts(IfOpts) -> + collect_ifopts(IfOpts, #{}). +%% +collect_ifopts(IfOpts, IfMap) -> + case IfOpts of + [{If,[{flags,Flags}|Opts]}|IfOs] -> + Key = {If,Flags}, + case maps:is_key(Key, IfMap) of + true -> + ct:fail({unexpected_ifopts,IfOpts,IfMap}); + false -> + collect_ifopts(IfOs, IfMap, Opts, Key, []) + end; + [] -> + IfMap; + _ -> + ct:fail({unexpected_ifopts,IfOpts,IfMap}) + end. +%% +collect_ifopts(IfOpts, IfMap, Opts, Key, R) -> + case Opts of + [{flags,_}|_] -> + {If,_} = Key, + collect_ifopts( + [{If,Opts}|IfOpts], maps:put(Key, lists:reverse(R), IfMap)); + [OptVal|Os] -> + collect_ifopts(IfOpts, IfMap, Os, Key, [OptVal|R]); + [] -> + collect_ifopts(IfOpts, maps:put(Key, lists:reverse(R), IfMap)) end. -check_ifopts([], #ifopts{flags=F,addrs=Raddrs}=Ifopts) -> - Addrs = lists:reverse(Raddrs), - R = Ifopts#ifopts{addrs=Addrs}, - io:format("~p.~n", [R]), - %% See how we did... - {flags,Flags} = F, - case lists:member(broadcast, Flags) of - true -> - [case A of - {{addr,_},{netmask,_},{broadaddr,_}} -> - A; - {{addr,T},{netmask,_}} when tuple_size(T) =:= 8 -> - A - end || A <- Addrs]; - false -> - case lists:member(pointtopoint, Flags) of - true -> - [case A of - {{addr,_},{netmask,_},{dstaddr,_}} -> - A - end || A <- Addrs]; - false -> - [case A of - {{addr,_},{netmask,_}} -> - A - end || A <- Addrs] - end - end, - R; -check_ifopts([{flags,_}=F|Opts], #ifopts{flags=undefined}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{flags=F}); -check_ifopts([{flags,_}=F|Opts], #ifopts{flags=Flags}=Ifopts) -> - case F of - Flags -> - check_ifopts(Opts, Ifopts); - _ -> - ct:fail({multiple_flags,F,Ifopts}) - end; -check_ifopts( - [{addr,_}=A,{netmask,_}=N,{dstaddr,_}=D|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N,D}|Addrs]}); -check_ifopts( - [{addr,_}=A,{netmask,_}=N,{broadaddr,_}=B|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N,B}|Addrs]}); -check_ifopts( - [{addr,_}=A,{netmask,_}=N|Opts], - #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A,N}|Addrs]}); -check_ifopts([{addr,_}=A|Opts], #ifopts{addrs=Addrs}=Ifopts) -> - check_ifopts(Opts, Ifopts#ifopts{addrs=[{A}|Addrs]}); -check_ifopts([{hwaddr,Hwaddr}=H|Opts], #ifopts{hwaddr=undefined}=Ifopts) - when is_list(Hwaddr) -> - check_ifopts(Opts, Ifopts#ifopts{hwaddr=H}); -check_ifopts([{hwaddr,_}=H|_], #ifopts{}=Ifopts) -> - ct:fail({multiple_hwaddrs,H,Ifopts}). +fold_ifopts(Fun, Acc, IfMap) -> + fold_ifopts(Fun, Acc, IfMap, maps:keys(IfMap)). +%% +fold_ifopts(Fun, Acc, IfMap, Keys) -> + case Keys of + [Key|Ks] -> + Opts = maps:get(Key, IfMap), + fold_ifopts(Fun, Fun({Key,Opts}, Acc), IfMap, Ks); + [] -> + Acc + end. %% Works just like lists:member/2, except that any {127,_,_,_} tuple %% matches any other {127,_,_,_}. We do this to handle Linux systems diff --git a/lib/sasl/src/sasl.appup.src b/lib/sasl/src/sasl.appup.src index 83ee328af2..d37c5b3d95 100644 --- a/lib/sasl/src/sasl.appup.src +++ b/lib/sasl/src/sasl.appup.src @@ -19,10 +19,10 @@ {"%VSN%", %% Up from - max one major revision back [{<<"3\\.0\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.0 - {<<"3\\.1(\\.[0-2]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"3\\.1(\\.[3-9]+)*">>,[restart_new_emulator]}], % OTP-21 + {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ + {<<"3\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}], % OTP-21.* %% Down to - max one major revision back [{<<"3\\.0\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.* - {<<"3\\.1(\\.[0-2]+)*">>,[restart_new_emulator]}, % OTP-20.1+ - {<<"3\\.1(\\.[3-9]+)*">>,[restart_new_emulator]}] % OTP-21 + {<<"3\\.1(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.1+ + {<<"3\\.2(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.* }. diff --git a/lib/ssl/src/tls_sender.erl b/lib/ssl/src/tls_sender.erl index f5379f60e3..8d1938cee7 100644 --- a/lib/ssl/src/tls_sender.erl +++ b/lib/ssl/src/tls_sender.erl @@ -29,7 +29,7 @@ %% API -export([start/0, start/1, initialize/2, send_data/2, send_alert/2, - send_and_ack_alert/2, renegotiate/1, setopts/2, + send_and_ack_alert/2, setopts/2, renegotiate/1, update_connection_state/3, dist_tls_socket/1, dist_handshake_complete/3]). %% gen_statem callbacks @@ -97,7 +97,7 @@ send_alert(Pid, Alert) -> gen_statem:cast(Pid, Alert). %%-------------------------------------------------------------------- --spec send_and_ack_alert(pid(), #alert{}) -> ok. +-spec send_and_ack_alert(pid(), #alert{}) -> _. %% Description: TLS connection process wants to send an Alert %% in the connection state and recive an ack. %%-------------------------------------------------------------------- diff --git a/lib/stdlib/doc/src/epp.xml b/lib/stdlib/doc/src/epp.xml index 1dc0161398..d803d259aa 100644 --- a/lib/stdlib/doc/src/epp.xml +++ b/lib/stdlib/doc/src/epp.xml @@ -124,6 +124,10 @@ <fsummary>Open a file for preprocessing.</fsummary> <desc> <p>Opens a file for preprocessing.</p> + <p>If you want to change the file name of the implicit -file() + attributes inserted during preprocessing, you can do with + <c>{source_name, <anno>SourceName</anno>}</c>. If unset it will + default to the name of the opened file.</p> <p>If <c>extra</c> is specified in <c><anno>Options</anno></c>, the return value is <c>{ok, <anno>Epp</anno>, <anno>Extra</anno>}</c> instead @@ -169,6 +173,10 @@ <p>Preprocesses and parses an Erlang source file. Notice that tuple <c>{eof, <anno>Line</anno>}</c> returned at the end of the file is included as a "form".</p> + <p>If you want to change the file name of the implicit -file() + attributes inserted during preprocessing, you can do with + <c>{source_name, <anno>SourceName</anno>}</c>. If unset it will + default to the name of the opened file.</p> <p>If <c>extra</c> is specified in <c><anno>Options</anno></c>, the return value is <c>{ok, [<anno>Form</anno>], <anno>Extra</anno>}</c> instead diff --git a/lib/stdlib/src/epp.erl b/lib/stdlib/src/epp.erl index cc34d4bdd3..181a524db6 100644 --- a/lib/stdlib/src/epp.erl +++ b/lib/stdlib/src/epp.erl @@ -117,6 +117,7 @@ open(Name, File, StartLocation, Path, Pdm) -> {'ok', Epp} | {'ok', Epp, Extra} | {'error', ErrorDescriptor} when Options :: [{'default_encoding', DefEncoding :: source_encoding()} | {'includes', IncludePath :: [DirectoryName :: file:name()]} | + {'source_name', SourceName :: file:name()} | {'macros', PredefMacros :: macros()} | {'name',FileName :: file:name()} | 'extra'], @@ -248,6 +249,7 @@ parse_file(Ifile, Path, Predefs) -> {'ok', [Form]} | {'ok', [Form], Extra} | {error, OpenError} when FileName :: file:name(), Options :: [{'includes', IncludePath :: [DirectoryName :: file:name()]} | + {'source_name', SourceName :: file:name()} | {'macros', PredefMacros :: macros()} | {'default_encoding', DefEncoding :: source_encoding()} | 'extra'], @@ -540,9 +542,10 @@ server(Pid, Name, Options, #epp{pre_opened=PreOpened}=St) -> init_server(Pid, Name, Options, St) end. -init_server(Pid, Name, Options, St0) -> +init_server(Pid, FileName, Options, St0) -> + SourceName = proplists:get_value(source_name, Options, FileName), Pdm = proplists:get_value(macros, Options, []), - Ms0 = predef_macros(Name), + Ms0 = predef_macros(FileName), case user_predef(Pdm, Ms0) of {ok,Ms1} -> #epp{file = File, location = AtLocation} = St0, @@ -552,14 +555,14 @@ init_server(Pid, Name, Options, St0) -> epp_reply(Pid, {ok,self(),Encoding}), %% ensure directory of current source file is %% first in path - Path = [filename:dirname(Name) | + Path = [filename:dirname(FileName) | proplists:get_value(includes, Options, [])], - St = St0#epp{delta=0, name=Name, name2=Name, + St = St0#epp{delta=0, name=SourceName, name2=SourceName, path=Path, macs=Ms1, default_encoding=DefEncoding}, From = wait_request(St), Anno = erl_anno:new(AtLocation), - enter_file_reply(From, file_name(Name), Anno, + enter_file_reply(From, file_name(SourceName), Anno, AtLocation, code), wait_req_scan(St); {error,E} -> diff --git a/lib/stdlib/src/ms_transform.erl b/lib/stdlib/src/ms_transform.erl index 3845e35e9b..6d243e1bec 100644 --- a/lib/stdlib/src/ms_transform.erl +++ b/lib/stdlib/src/ms_transform.erl @@ -946,6 +946,7 @@ real_guard_function(node,0) -> true; real_guard_function(node,1) -> true; real_guard_function(round,1) -> true; real_guard_function(size,1) -> true; +real_guard_function(bit_size,1) -> true; real_guard_function(map_size,1) -> true; real_guard_function(map_get,2) -> true; real_guard_function(tl,1) -> true; diff --git a/lib/stdlib/src/stdlib.appup.src b/lib/stdlib/src/stdlib.appup.src index 8d1cc09a8b..8c0b186288 100644 --- a/lib/stdlib/src/stdlib.appup.src +++ b/lib/stdlib/src/stdlib.appup.src @@ -19,8 +19,10 @@ {"%VSN%", %% Up from - max one major revision back [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.* - {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}],% OTP-21.* + {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 + {<<"3\\.6(\\.[0-9]+)*">>,[restart_new_emulator]}],% OTP-21.1 %% Down to - max one major revision back [{<<"3\\.4(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-20.* - {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-20.* + {<<"3\\.5(\\.[0-9]+)*">>,[restart_new_emulator]}, % OTP-21.0 + {<<"3\\.6(\\.[0-9]+)*">>,[restart_new_emulator]}] % OTP-21.1 }. diff --git a/lib/stdlib/test/epp_SUITE.erl b/lib/stdlib/test/epp_SUITE.erl index a3e294ffea..0ac99ad03a 100644 --- a/lib/stdlib/test/epp_SUITE.erl +++ b/lib/stdlib/test/epp_SUITE.erl @@ -29,7 +29,7 @@ otp_8562/1, otp_8665/1, otp_8911/1, otp_10302/1, otp_10820/1, otp_11728/1, encoding/1, extends/1, function_macro/1, test_error/1, test_warning/1, otp_14285/1, - test_if/1]). + test_if/1,source_name/1]). -export([epp_parse_erl_form/2]). @@ -70,7 +70,7 @@ all() -> overload_mac, otp_8388, otp_8470, otp_8562, otp_8665, otp_8911, otp_10302, otp_10820, otp_11728, encoding, extends, function_macro, test_error, test_warning, - otp_14285, test_if]. + otp_14285, test_if, source_name]. groups() -> [{upcase_mac, [], [upcase_mac_1, upcase_mac_2]}, @@ -1702,6 +1702,18 @@ function_macro(Config) -> ok. +source_name(Config) when is_list(Config) -> + DataDir = proplists:get_value(data_dir, Config), + File = filename:join(DataDir, "source_name.erl"), + + source_name_1(File, "/test/gurka.erl"), + source_name_1(File, "gaffel.erl"), + + ok. + +source_name_1(File, Expected) -> + Res = epp:parse_file(File, [{source_name, Expected}]), + {ok, [{attribute,_,file,{Expected,_}} | _Forms]} = Res. check(Config, Tests) -> eval_tests(Config, fun check_test/2, Tests). diff --git a/lib/stdlib/test/epp_SUITE_data/source_name.erl b/lib/stdlib/test/epp_SUITE_data/source_name.erl new file mode 100644 index 0000000000..71ad2dddb9 --- /dev/null +++ b/lib/stdlib/test/epp_SUITE_data/source_name.erl @@ -0,0 +1,27 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2018. All Rights Reserved. +%% +%% Licensed under the Apache License, Version 2.0 (the "License"); +%% you may not use this file except in compliance with the License. +%% You may obtain a copy of the License at +%% +%% http://www.apache.org/licenses/LICENSE-2.0 +%% +%% Unless required by applicable law or agreed to in writing, software +%% distributed under the License is distributed on an "AS IS" BASIS, +%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +%% See the License for the specific language governing permissions and +%% limitations under the License. +%% +%% %CopyrightEnd% +%% + +-module(source_name). +-export([ok/0]). + +%% Changing source name should not affect headers +-include("bar.hrl"). + +ok() -> ok. diff --git a/lib/stdlib/test/ets_SUITE.erl b/lib/stdlib/test/ets_SUITE.erl index d8912e548c..aa2e352c29 100644 --- a/lib/stdlib/test/ets_SUITE.erl +++ b/lib/stdlib/test/ets_SUITE.erl @@ -41,7 +41,7 @@ -export([t_delete_object/1, t_init_table/1, t_whitebox/1, select_bound_chunk/1, t_delete_all_objects/1, t_insert_list/1, t_test_ms/1, - t_select_delete/1,t_select_replace/1,t_ets_dets/1]). + t_select_delete/1,t_select_replace/1,t_select_replace_next_bug/1,t_ets_dets/1]). -export([ordered/1, ordered_match/1, interface_equality/1, fixtable_next/1, fixtable_insert/1, rename/1, rename_unnamed/1, evil_rename/1, @@ -125,6 +125,7 @@ all() -> select_bound_chunk, t_init_table, t_whitebox, t_delete_all_objects, t_insert_list, t_test_ms, t_select_delete, t_select_replace, + t_select_replace_next_bug, t_ets_dets, memory, t_select_reverse, t_bucket_disappears, t_named_select, select_fail, t_insert_new, t_repair_continuation, @@ -1466,6 +1467,25 @@ t_select_replace(Config) when is_list(Config) -> verify_etsmem(EtsMem). +%% OTP-15346: Bug caused select_replace of bound key to corrupt static stack +%% used by ets:next and ets:prev. +t_select_replace_next_bug(Config) when is_list(Config) -> + T = ets:new(k, [ordered_set]), + [ets:insert(T, {I, value}) || I <- lists:seq(1,10)], + 1 = ets:first(T), + + %% Make sure select_replace does not leave pointer + %% to deallocated {2,value} in static stack. + MS = [{{2,value}, [], [{{2,"new_value"}}]}], + 1 = ets:select_replace(T, MS), + + %% This would crash or give wrong result at least on DEBUG emulator + %% where deallocated memory is overwritten. + 2 = ets:next(T, 1), + + ets:delete(T). + + %% Test that partly bound keys gives faster matches. partly_bound(Config) when is_list(Config) -> case os:type() of diff --git a/lib/stdlib/test/rand_SUITE.erl b/lib/stdlib/test/rand_SUITE.erl index d753d929f5..b76c9f5341 100644 --- a/lib/stdlib/test/rand_SUITE.erl +++ b/lib/stdlib/test/rand_SUITE.erl @@ -475,10 +475,11 @@ stats_standard_normal_box_muller_2(Config) when is_list(Config) -> stats_standard_normal(Config) when is_list(Config) -> + Retries = 7, try math:erfc(1.0) of _ -> stats_standard_normal( - fun rand:normal_s/1, rand:seed_s(exrop), 3) + fun rand:normal_s/1, rand:seed_s(exrop), Retries) catch error:_ -> {skip, "math:erfc/1 not supported"} end. diff --git a/lib/tools/emacs/Makefile b/lib/tools/emacs/Makefile index ea4d6cb723..b7775d1c8c 100644 --- a/lib/tools/emacs/Makefile +++ b/lib/tools/emacs/Makefile @@ -46,6 +46,7 @@ EMACS_FILES= \ erlang-eunit \ erlang-edoc \ erlang-flymake \ + erlang-test \ erlang README_FILES= README diff --git a/lib/tools/emacs/erlang-edoc.el b/lib/tools/emacs/erlang-edoc.el index d0dcc81028..ea1e263faf 100644 --- a/lib/tools/emacs/erlang-edoc.el +++ b/lib/tools/emacs/erlang-edoc.el @@ -28,6 +28,7 @@ (defcustom erlang-edoc-indent-level 2 "Indentation level of xhtml in Erlang edoc." + :type '(integer) :safe 'integerp :group 'erlang) diff --git a/lib/tools/emacs/erlang-eunit.el b/lib/tools/emacs/erlang-eunit.el index 38c40927f4..53543d7b01 100644 --- a/lib/tools/emacs/erlang-eunit.el +++ b/lib/tools/emacs/erlang-eunit.el @@ -23,6 +23,7 @@ (eval-when-compile (require 'cl)) +(require 'erlang) (defvar erlang-eunit-src-candidate-dirs '("../src" ".") "*Name of directories which to search for source files matching @@ -331,8 +332,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." t) (apply test-fun test-args) (if under-cover - (save-excursion - (set-buffer (find-file-noselect src-filename)) + (with-current-buffer (find-file-noselect src-filename) (erlang-eunit-analyze-coverage))))))) (defun erlang-eunit-compile-and-run-module-tests-under-cover () @@ -348,8 +348,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." (defun erlang-eunit-compile-file (file-path &optional under-cover) (if (file-readable-p file-path) - (save-excursion - (set-buffer (find-file-noselect file-path)) + (with-current-buffer (find-file-noselect file-path) ;; In order to run a code coverage analysis on a ;; module, we have two options: ;; @@ -376,8 +375,7 @@ With prefix arg, compiles for debug and runs tests with the verbose flag set." (error msg)))) (defun erlang-eunit-last-compilation-successful-p () - (save-excursion - (set-buffer inferior-erlang-buffer) + (with-current-buffer inferior-erlang-buffer (goto-char compilation-parsing-end) (erlang-eunit-all-list-elems-fulfill-p (lambda (re) (let ((continue t) diff --git a/lib/tools/emacs/erlang-pkg.el b/lib/tools/emacs/erlang-pkg.el index 02d6bebbf4..7e95e4050e 100644 --- a/lib/tools/emacs/erlang-pkg.el +++ b/lib/tools/emacs/erlang-pkg.el @@ -1,3 +1,6 @@ (define-package "erlang" "2.7.0" "Erlang major mode" '((emacs "24.1"))) +;; Local Variables: +;; no-byte-compile: t +;; End: diff --git a/lib/tools/emacs/erlang-skels.el b/lib/tools/emacs/erlang-skels.el index 534f50ab33..3ebc6e8e1e 100644 --- a/lib/tools/emacs/erlang-skels.el +++ b/lib/tools/emacs/erlang-skels.el @@ -1985,7 +1985,7 @@ configured off." The first character of DD is space if the value is less than 10." (let ((date (current-time-string))) (format "%2d %s %s" - (string-to-int (substring date 8 10)) + (string-to-number (substring date 8 10)) (substring date 4 7) (substring date -4)))) diff --git a/lib/tools/emacs/erlang-test.el b/lib/tools/emacs/erlang-test.el index efe3d515e9..2ee584d11a 100644 --- a/lib/tools/emacs/erlang-test.el +++ b/lib/tools/emacs/erlang-test.el @@ -29,7 +29,7 @@ ;; This library require GNU Emacs 25 or later. ;; -;; There are two ways to run emacs unit tests. +;; There are three ways to run the erlang emacs unit tests. ;; ;; 1. Within a running emacs process. Load this file. Then to run ;; all defined test cases: @@ -49,11 +49,15 @@ ;; ;; The -L option adds a directory to the load-path. It should be the ;; directory containing erlang.el and erlang-test.el. +;; +;; 3. Call the script test-erlang-mode in this directory. This script +;; use the second method. ;;; Code: +(eval-when-compile + (require 'cl)) (require 'ert) -(require 'cl-lib) (require 'erlang) (defvar erlang-test-code @@ -63,7 +67,7 @@ ("SYMBOL" . "-define(SYMBOL, value).") ("MACRO" . "-define(MACRO(X), X + X).") ("struct" . "-record(struct, {until,maps,are,everywhere}).") - ("function". "function() -> #struct{}.")) + ("function" . "function() -> #struct{}.")) "Alist of erlang test code. Each entry have the format (TAGNAME . ERLANG_CODE). If TAGNAME is nil there is no definitions in the ERLANG_CODE. The @@ -116,8 +120,8 @@ concatenated to form an erlang file to test on.") (defun erlang-test-create-erlang-file (erlang-file) (with-temp-file erlang-file - (cl-loop for (_ . code) in erlang-test-code - do (insert code "\n")))) + (loop for (_ . code) in erlang-test-code + do (insert code "\n")))) (defun erlang-test-compile-tags (erlang-file tags-file) (should (zerop (call-process "etags" nil nil nil @@ -132,19 +136,20 @@ concatenated to form an erlang file to test on.") (sort (erlang-expected-completion-table) #'string-lessp)))) (defun erlang-expected-completion-table () - (append (cl-loop for (symbol . _) in erlang-test-code - when (stringp symbol) - append (list symbol (concat "erlang_test:" symbol))) + (append (loop for (symbol . _) in erlang-test-code + when (stringp symbol) + append (list symbol (concat "erlang_test:" symbol))) (list "erlang_test:" "erlang_test:module_info"))) (defun erlang-test-xref-find-definitions (erlang-file erlang-buffer) - (cl-loop for (tagname . code) in erlang-test-code - for line = 1 then (1+ line) - do (when tagname - (switch-to-buffer erlang-buffer) - (erlang-test-xref-jump tagname erlang-file line) - (erlang-test-xref-jump (concat "erlang_test:" tagname) - erlang-file line))) + (loop for (tagname . code) in erlang-test-code + for line = 1 then (1+ line) + do (when tagname + (switch-to-buffer erlang-buffer) + (erlang-test-xref-jump tagname erlang-file line) + (when (string-equal tagname "function") + (erlang-test-xref-jump (concat "erlang_test:" tagname) + erlang-file line)))) (erlang-test-xref-jump "erlang_test:" erlang-file 1)) (defun erlang-test-xref-jump (id expected-file expected-line) @@ -213,27 +218,27 @@ concatenated to form an erlang file to test on.") (ert-deftest erlang-test-parse-id () - (cl-loop for id-string in '("fun/10" - "qualified-function module:fun/10" - "record reko" - "macro _SYMBOL" - "macro MACRO/10" - "module modula" - "macro" - nil) - for id-list in '((nil nil "fun" 10) - (qualified-function "module" "fun" 10) - (record nil "reko" nil) - (macro nil "_SYMBOL" nil) - (macro nil "MACRO" 10) - (module nil "modula" nil) - (nil nil "macro" nil) - nil) - for id-list2 = (erlang-id-to-list id-string) - do (should (equal id-list id-list2)) - for id-string2 = (erlang-id-to-string id-list) - do (should (equal id-string id-string2)) - collect id-list2)) + (loop for id-string in '("fun/10" + "qualified-function module:fun/10" + "record reko" + "macro _SYMBOL" + "macro MACRO/10" + "module modula" + "macro" + nil) + for id-list in '((nil nil "fun" 10) + (qualified-function "module" "fun" 10) + (record nil "reko" nil) + (macro nil "_SYMBOL" nil) + (macro nil "MACRO" 10) + (module nil "modula" nil) + (nil nil "macro" nil) + nil) + for id-list2 = (erlang-id-to-list id-string) + do (should (equal id-list id-list2)) + for id-string2 = (erlang-id-to-string id-list) + do (should (equal id-string id-string2)) + collect id-list2)) (provide 'erlang-test) diff --git a/lib/tools/emacs/erlang.el b/lib/tools/emacs/erlang.el index 82e5c2222d..3cbe9daa60 100644 --- a/lib/tools/emacs/erlang.el +++ b/lib/tools/emacs/erlang.el @@ -78,6 +78,8 @@ (eval-when-compile (require 'cl)) (require 'align) +(require 'comint) +(require 'tempo) ;; Variables: @@ -334,6 +336,7 @@ when a new function header is generated. When nil, no blank line is inserted between the current line and the new header. When bound to a number it represents the number of blank lines which should be inserted." + :type '(restricted-sexp :match-alternatives (integerp 'nil)) :group 'erlang) (defvar erlang-electric-semicolon-criteria @@ -1711,10 +1714,10 @@ Personal extensions could be added to `erlang-menu-personal-items'. This function should be called if any variable describing the menu configuration is changed." - (erlang-menu-install "Erlang" erlang-menu-items erlang-mode-map t)) + (erlang-menu-install "Erlang" erlang-menu-items erlang-mode-map)) -(defun erlang-menu-install (name items keymap &optional popup) +(defun erlang-menu-install (name items keymap) "Install a menu in Emacs based on an abstract description. NAME is the name of the menu. @@ -3694,16 +3697,17 @@ retried without regard to module. 4. Arity - Integer in case of functions and macros if the number of arguments could be found, otherwise nil." (save-excursion - (save-match-data - (if (eq (char-syntax (following-char)) ? ) - (skip-chars-backward " \t")) - (skip-chars-backward "[:word:]_:'") - (cond ((looking-at erlang-module-function-regexp) - (erlang-get-qualified-function-id-at-point)) - ((looking-at (concat erlang-atom-regexp ":")) - (erlang-get-module-id-at-point)) - ((looking-at erlang-name-regexp) - (erlang-get-some-other-id-at-point)))))) + (let (case-fold-search) + (save-match-data + (if (eq (char-syntax (following-char)) ? ) + (skip-chars-backward " \t")) + (skip-chars-backward "[:word:]_:'") + (cond ((looking-at erlang-module-function-regexp) + (erlang-get-qualified-function-id-at-point)) + ((looking-at (concat erlang-atom-regexp ":")) + (erlang-get-module-id-at-point)) + ((looking-at erlang-name-regexp) + (erlang-get-some-other-id-at-point))))))) (defun erlang-get-qualified-function-id-at-point () (let ((kind 'qualified-function) @@ -4207,22 +4211,18 @@ Return t if criteria fulfilled, nil otherwise." nil))))) -(defun erlang-in-literal (&optional lim) +(defun erlang-in-literal () "Test if point is in string, quoted atom or comment. Return one of the three atoms `atom', `string', and `comment'. Should the point be inside none of the above mentioned types of context, nil is returned." (save-excursion - (let* ((lim (or lim (save-excursion - (erlang-beginning-of-clause) - (point)))) - (state (funcall (symbol-function 'syntax-ppss)))) - (cond - ((eq (nth 3 state) ?') 'atom) - ((nth 3 state) 'string) - ((nth 4 state) 'comment) - (t nil))))) + (let ((state (funcall (symbol-function 'syntax-ppss)))) + (cond ((eq (nth 3 state) ?') 'atom) + ((nth 3 state) 'string) + ((nth 4 state) 'comment) + (t nil))))) (defun erlang-at-end-of-function-p () @@ -5041,7 +5041,10 @@ considered first when it is time to jump to the definition.") (defun erlang-visit-tags-table-buffer (cont cbuf) (if (< emacs-major-version 26) (visit-tags-table-buffer cont) - (visit-tags-table-buffer cont cbuf))) + ;; Remove this with-no-warnings when Emacs 26 is the required + ;; version minimum. + (with-no-warnings + (visit-tags-table-buffer cont cbuf)))) (defun erlang-xref-find-definitions-module-tag (module tag @@ -5536,7 +5539,7 @@ Return the position after the newly inserted command." (+ insert-point insert-length))) -(defun inferior-erlang-strip-delete (&optional s) +(defun inferior-erlang-strip-delete (&optional _s) "Remove `^H' (delete) and the characters it was supposed to remove." (interactive) (if (and (boundp 'comint-last-input-end) @@ -5554,7 +5557,7 @@ Return the position after the newly inserted command." ;; Basically `comint-strip-ctrl-m', with a few extra checks. -(defun inferior-erlang-strip-ctrl-m (&optional string) +(defun inferior-erlang-strip-ctrl-m (&optional _string) "Strip trailing `^M' characters from the current output group." (interactive) (if (and (boundp 'comint-last-input-end) @@ -5591,8 +5594,8 @@ There exists two workarounds for this bug: (let* ((dir (inferior-erlang-compile-outdir)) (noext (substring (erlang-local-buffer-file-name) 0 -4)) (opts (append (list (cons 'outdir dir)) - (if current-prefix-arg - (list 'debug_info 'export_all)) + (when arg + (list 'debug_info 'export_all)) erlang-compile-extra-opts)) end) (with-current-buffer inferior-erlang-buffer @@ -5641,7 +5644,6 @@ unless the optional NO-DISPLAY is non-nil." (defun inferior-erlang-compute-compile-command (module-name opts) (let ((ccfn erlang-compile-command-function-alist) - (res (inferior-erlang-compute-erl-compile-command module-name opts)) ccfn-entry done result) diff --git a/lib/tools/emacs/erlang_appwiz.el b/lib/tools/emacs/erlang_appwiz.el index ecbce66f47..b71c180739 100644 --- a/lib/tools/emacs/erlang_appwiz.el +++ b/lib/tools/emacs/erlang_appwiz.el @@ -103,6 +103,10 @@ ;; ;; +(defvar appwiz-erlang-modulename "foo") +(defvar appwiz-erlang-ext "_work") + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; ;; Erlang application wizard @@ -245,13 +249,6 @@ creating the root directory and for naming application files." (insert "Application specification file for " name ".") (save-buffer))) -;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; -;; These are setq:ed -;; - -(defvar appwiz-erlang-modulename "foo") -(defvar appwiz-erlang-ext "_work") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; @@ -468,7 +465,7 @@ Call the function `erlang-menu-init' after modifying this variable.") The first character of DD is *not* space if the value is less than 10." (let ((date (current-time-string))) (format "%d %s %s" - (string-to-int (substring date 8 10)) + (string-to-number (substring date 8 10)) (substring date 4 7) (substring date -4)))) diff --git a/lib/tools/test/emacs_SUITE.erl b/lib/tools/test/emacs_SUITE.erl index 5839f9ce5b..a6d43d1816 100644 --- a/lib/tools/test/emacs_SUITE.erl +++ b/lib/tools/test/emacs_SUITE.erl @@ -23,18 +23,28 @@ -export([all/0, init_per_testcase/2, end_per_testcase/2]). --export([bif_highlight/1, indent/1]). +-export([bif_highlight/1, + load_interpreted/1, compile_and_load/1, + indent/1, + tests_interpreted/1, tests_compiled/1 + ]). all() -> - [bif_highlight, indent]. + [bif_highlight, load_interpreted, compile_and_load, + indent, + tests_interpreted, tests_compiled + ]. -init_per_testcase(_Case, Config) -> +init_per_testcase(Case, Config) -> ErlangEl = filename:join([code:lib_dir(tools),"emacs","erlang.el"]), case file:read_file_info(ErlangEl) of - {ok, _} -> - [{el, ErlangEl}|Config]; - _ -> - {skip, "Could not find erlang.el"} + {ok, _} -> + case Case =:= bif_highlight orelse emacs_version_ok(24.1) of + false -> {skip, "Old or no emacs found"}; + _ -> [{el, ErlangEl}|Config] + end; + _ -> + {skip, "Could not find erlang.el"} end. end_per_testcase(_Case, _Config) -> @@ -46,26 +56,26 @@ bif_highlight(Config) -> %% All auto-imported bifs IntBifs = lists:usort( - [F || {F,A} <- erlang:module_info(exports), - erl_internal:bif(F,A)]), + [F || {F,A} <- erlang:module_info(exports), + erl_internal:bif(F,A)]), %% all bif which need erlang: prefix and are not operands ExtBifs = lists:usort( - [F || {F,A} <- erlang:module_info(exports), - not erl_internal:bif(F,A) andalso - not is_atom(catch erl_internal:op_type(F,A))]), + [F || {F,A} <- erlang:module_info(exports), + not erl_internal:bif(F,A) andalso + not is_atom(catch erl_internal:op_type(F,A))]), check_bif_highlight(Bin, <<"erlang-int-bifs">>, IntBifs), check_bif_highlight(Bin, <<"erlang-ext-bifs">>, ExtBifs). - + check_bif_highlight(Bin, Tag, Compare) -> - [_H,IntMatch,_T] = - re:split(Bin,<<"defvar ",Tag/binary, - "[^(]*\\(([^)]*)">>,[]), - EmacsIntBifs = [list_to_atom(S) || - S <- string:tokens(binary_to_list(IntMatch)," '\"\n")], - + [_H,IntMatch,_T] = + re:split(Bin,<<"defvar ",Tag/binary, + "[^(]*\\(([^)]*)">>,[]), + EmacsIntBifs = [list_to_atom(S) || + S <- string:tokens(binary_to_list(IntMatch)," '\"\n")], + ct:log("Emacs ~p",[EmacsIntBifs]), ct:log("Int ~p",[Compare]), @@ -73,27 +83,92 @@ check_bif_highlight(Bin, Tag, Compare) -> ct:log("Diff2 ~p",[EmacsIntBifs -- Compare]), [] = Compare -- EmacsIntBifs, [] = EmacsIntBifs -- Compare. - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -indent(Config) -> - case emacs_version_ok() of +load_interpreted(_Config) -> + _ = emacs(["-l erlang.el -f erlang-mode"]), + ok. + +compile_and_load(_Config) -> + Dir = emacs_dir(), + Files0 = filelib:wildcard("*.el", Dir), + Files = case emacs_version_ok(24.3) of + %% erldoc.el depends on cl-lib which was introduced in 24.3. + false -> Files0 -- ["erldoc.el"]; + _ -> Files0 + end, + Unforgiving = + case emacs_version_ok(24) of + Ver when Ver < 25 -> + ""; + Ver when Ver < 26 -> + %% Workaround byte-compile-error-on-warn which seem broken in + %% Emacs 25. + "\"(advice-add #'display-warning :after " + "(lambda (_ f _ _) (error \"%s\" f)))\""; + _ -> + "\"(setq byte-compile-error-on-warn t)\"" + end, + %% Add files here whenever they are cleaned of warnings. + NoWarn = ["erlang.el", "erlang-test.el", "erlang-edoc.el", "erlang-start.el", "erldoc.el"], + Compile = fun(File) -> + Pedantic = case lists:member(File, NoWarn) andalso Unforgiving /= "" of + true -> ["--eval ", Unforgiving, " "]; + false -> " " + end, + emacs([Pedantic, + " -f batch-byte-compile ",filename:join(Dir, File)]), + true + end, + lists:foreach(Compile, Files), + emacs(["-l erlang.elc -f erlang-mode"]), + ok. + +tests_interpreted(_Config) -> + case emacs_version_ok(25) of false -> {skip, "Old or no emacs found"}; - true -> - Def = filename:dirname(code:which(?MODULE)) ++ "/" ++ ?MODULE_STRING ++ "_data", - Dir = proplists:get_value(data_dir, Config, Def), - OrigFs = filelib:wildcard(Dir ++ "/*"), - io:format("Dir: ~s~nFs: ~p~n", [Dir, OrigFs]), - Fs = [{File, unindent(File)} || File <- OrigFs, - filename:extension(File) =:= ""], - Indent = fun emacs/1, - [Indent(File) || {_, File} <- Fs], - Res = [diff(Orig, File) || {Orig, File} <- Fs], - [file:delete(File) || {ok, File} <- Res], %% Cleanup - [] = [Fail || {fail, Fail} <- Res], + _ -> + emacs(["-l erlang.el ", + "-l erlang-test.el -f ert-run-tests-batch-and-exit"]), ok end. +tests_compiled(_Config) -> + case emacs_version_ok(25) of + false -> {skip, "Old or no emacs found"}; + _ -> + emacs(["-l erlang.elc ", + "-l erlang-test.elc -f ert-run-tests-batch-and-exit"]), + ok + end. + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +indent(Config) -> + Def = filename:dirname(code:which(?MODULE)) + ++ "/" + ++ ?MODULE_STRING + ++ "_data", + Dir = proplists:get_value(data_dir, Config, Def), + OrigFs = filelib:wildcard(Dir ++ "/*"), + io:format("Dir: ~s~nFs: ~p~n", [Dir, OrigFs]), + Fs = [{File, unindent(File)} || File <- OrigFs, + filename:extension(File) =:= ""], + Indent = fun(File) -> + emacs([ + File, " ", + "--eval '(indent-region (point-min) (point-max) nil)' ", + "--eval '(save-buffer 0)'" + ]), + ok + end, + [Indent(File) || {_, File} <- Fs], + Res = [diff(Orig, File) || {Orig, File} <- Fs], + [file:delete(File) || {ok, File} <- Res], %% Cleanup + [] = [Fail || {fail, Fail} <- Res], + ok. + unindent(Input) -> Output = Input ++ ".erl", {ok, Bin} = file:read_file(Input), @@ -112,14 +187,13 @@ diff(Orig, File) -> {fail, File} end. -emacs_version_ok() -> +emacs_version_ok(AcceptVer) -> case os:cmd("emacs --version | head -1") of "GNU Emacs " ++ Ver -> case string:to_float(Ver) of - {Vsn, _} when Vsn >= 24.1 -> - true; + {Vsn, _} when Vsn >= AcceptVer -> + Vsn; _ -> - io:format("Emacs version fail~n~s~n~n",[Ver]), false end; Res -> @@ -127,16 +201,19 @@ emacs_version_ok() -> false end. -emacs(File) -> - EmacsErlDir = filename:join([code:lib_dir(tools), "emacs"]), +emacs(EmacsCmds) when is_list(EmacsCmds) -> Cmd = ["emacs ", "--batch --quick ", - "--directory ", EmacsErlDir, " ", - "--eval \"(require 'erlang-start)\" ", - File, " ", - "--eval '(indent-region (point-min) (point-max) nil)' ", - "--eval '(save-buffer 0)'" - ], - _Res = os:cmd(Cmd), - % io:format("cmd ~s:~n=> ~s~n", [Cmd, _Res]), - ok. + "--directory ", emacs_dir(), " ", + "--eval \"(require 'erlang-start)\" " + | EmacsCmds], + Res0 = os:cmd(Cmd ++ " ; echo $?"), + Rows = string:lexemes(Res0, ["\r\n", $\n]), + Res = lists:last(Rows), + Output = string:join(lists:droplast(Rows), "\n"), + io:format("Cmd ~s:~n => ~s ~ts~n", [Cmd, Res, Output]), + "0" = Res, + Output. + +emacs_dir() -> + filename:join([code:lib_dir(tools), "emacs"]). |