diff options
author | Anders Svensson <anders@erlang.org> | 2011-12-09 11:35:37 +0100 |
---|---|---|
committer | Anders Svensson <anders@erlang.org> | 2011-12-09 11:35:37 +0100 |
commit | 23c1d8c8dafc9094e462f7b220f9faec5eec3ad8 (patch) | |
tree | 1c6f180487ae1a2c8a4e2e28ece5e226eb5f1f88 /lib | |
parent | 2ead2d78f94bda90309077080c6b6d7a5bb72d9c (diff) | |
parent | 57d5564fca1f6d5e57199e1dcdca1d64284ecae8 (diff) | |
download | otp-23c1d8c8dafc9094e462f7b220f9faec5eec3ad8.tar.gz otp-23c1d8c8dafc9094e462f7b220f9faec5eec3ad8.tar.bz2 otp-23c1d8c8dafc9094e462f7b220f9faec5eec3ad8.zip |
Merge branch 'anders/diameter/suites_and_dialyzer/OTP-9781'
* anders/diameter/suites_and_dialyzer/OTP-9781:
Tell dialyzer not to warn about unused functions
Add compiler testcases for generation and compilation to beam
Fix semantic checks on AVP qualifiers
Minor codegen/debug fix
Deal with crypto:start() failure in TLS suite
Don't assume empty sndrcvinfo in diameter_sctp
Diffstat (limited to 'lib')
-rw-r--r-- | lib/diameter/src/compiler/diameter_codegen.erl | 30 | ||||
-rw-r--r-- | lib/diameter/src/compiler/diameter_dict_util.erl | 83 | ||||
-rw-r--r-- | lib/diameter/src/compiler/diameter_nowarn.erl | 41 | ||||
-rw-r--r-- | lib/diameter/src/modules.mk | 1 | ||||
-rw-r--r-- | lib/diameter/src/transport/diameter_sctp.erl | 18 | ||||
-rw-r--r-- | lib/diameter/test/diameter_app_SUITE.erl | 1 | ||||
-rw-r--r-- | lib/diameter/test/diameter_compiler_SUITE.erl | 197 | ||||
-rw-r--r-- | lib/diameter/test/diameter_tls_SUITE.erl | 2 |
8 files changed, 313 insertions, 60 deletions
diff --git a/lib/diameter/src/compiler/diameter_codegen.erl b/lib/diameter/src/compiler/diameter_codegen.erl index 6763e06140..1e31c40afe 100644 --- a/lib/diameter/src/compiler/diameter_codegen.erl +++ b/lib/diameter/src/compiler/diameter_codegen.erl @@ -98,15 +98,15 @@ file(F, Outdir, Mode) -> get_value(Key, Plist) -> proplists:get_value(Key, Plist, []). -write(Path, [C|_] = Spec) - when is_integer(C) -> - w(Path, Spec, "~s"); -write(Path, Spec) -> - w(Path, Spec, "~p."). +write(Path, Str) -> + w(Path, Str, "~s"). -w(Path, Spec, Fmt) -> +write_term(Path, T) -> + w(Path, T, "~p."). + +w(Path, T, Fmt) -> {ok, Fd} = file:open(Path, [write]), - io:fwrite(Fd, Fmt ++ "~n", [Spec]), + io:fwrite(Fd, Fmt ++ "~n", [T]), file:close(Fd). codegen(File, Spec, Outdir, Mode) -> @@ -121,7 +121,7 @@ mod(_, {ok, Mod}) -> Mod. gen(spec, Spec, _Mod, Path) -> - write(Path ++ ".spec", [?VERSION | Spec]); + write_term(Path ++ ".spec", [?VERSION | Spec]); gen(hrl, Spec, Mod, Path) -> gen_hrl(Path ++ ".hrl", Mod, Spec); @@ -129,7 +129,7 @@ gen(hrl, Spec, Mod, Path) -> gen(erl, Spec, Mod, Path) -> Forms = [{?attribute, module, Mod}, {?attribute, compile, [{parse_transform, diameter_exprecs}]}, - {?attribute, compile, [nowarn_unused_function]}, + {?attribute, compile, [{parse_transform, diameter_nowarn}]}, {?attribute, export, [{name, 0}, {id, 0}, {vendor_id, 0}, @@ -173,7 +173,7 @@ gen(erl, Spec, Mod, Path) -> gen_erl(Path, insert_hrl_forms(Spec, Forms)). gen_erl(Path, Forms) -> - getr(debug) andalso write(Path ++ ".forms", Forms), + getr(debug) andalso write_term(Path ++ ".forms", Forms), write(Path ++ ".erl", header() ++ erl_prettypr:format(erl_syntax:form_list(Forms))). @@ -835,15 +835,15 @@ avp_info(Entry) -> %% {Name, Arity} [A] -> {A, {0,1}}; {Q,T} -> {A,_} = avp_info(T), - {A, arity(Q)} + {A, arity(T,Q)} end. %% Normalize arity to 1 or {N,X} where N is an integer. A record field %% for an AVP is list-valued iff the normalized arity is not 1. -arity('*' = Inf) -> {0, Inf}; -arity({'*', N}) -> {0, N}; -arity({1,1}) -> 1; -arity(T) -> T. +arity({{_}}, '*' = Inf) -> {0, Inf}; +arity([_], '*' = Inf) -> {0, Inf}; +arity({_}, '*' = Inf) -> {1, Inf}; +arity(_, {_,_} = Q) -> Q. prefix(Spec) -> case orddict:find(prefix, Spec) of diff --git a/lib/diameter/src/compiler/diameter_dict_util.erl b/lib/diameter/src/compiler/diameter_dict_util.erl index 2207925e49..e4cd29ab7f 100644 --- a/lib/diameter/src/compiler/diameter_dict_util.erl +++ b/lib/diameter/src/compiler/diameter_dict_util.erl @@ -174,7 +174,13 @@ fmt(message_application_id_mismatch) -> fmt(invalid_avp_order) -> "AVP reference ~c~s~c at line ~p breaks fixed/required/optional order"; -fmt(invalid_qualifier) -> +fmt(required_avp_has_zero_max_arity) -> + "Required AVP has maximum arity 0 at line ~p"; +fmt(required_avp_has_zero_min_arity) -> + "Required AVP has minimum arity 0 at line ~p"; +fmt(optional_avp_has_nonzero_min_arity) -> + "Optional AVP has non-zero minimum arity at line ~p"; +fmt(qualifier_has_min_greater_than_max) -> "Qualifier ~p*~p at line ~p has Min > Max"; fmt(avp_already_referenced) -> "AVP ~s at line ~p already referenced at line ~p"; @@ -300,7 +306,7 @@ body(avp_types, {Name, Code, Type, Flags}) -> body(messages, {"answer-message", _, _, [], Avps}) -> [?NL, ?NL, ?INDENT, "answer-message ::= < Diameter Header: code, ERR [PXY] >", - f_avps(Avps)]; + f_avps(Avps)]; body(messages, {Name, Code, Flags, ApplId, Avps}) -> [?NL, ?NL, ?INDENT, word(Name), " ::= ", header(Code, Flags, ApplId), f_avps(Avps)]; @@ -326,7 +332,8 @@ f_avps(L) -> [[?NL, ?INDENT, ?INDENT, f_avp(A)] || A <- L]. f_avp({Q, A}) -> - f_avp(f_qual(Q), f_delim(A)); + [D | _] = Avp = f_delim(A), + f_avp(f_qual(D, Q), Avp); f_avp(A) -> f_avp("", f_delim(A)). @@ -337,17 +344,19 @@ f_delim({A}) -> f_delim([A]) -> [$[, word(A), $]]. -f_avp(Q, Avp) -> +f_avp(Q, [L, Avp, R]) -> Len = length(lists:flatten([Q])), - [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), Avp]. + [io_lib:format("~*s", [-1*max(Len+1, 6) , Q]), L, " ", Avp, " ", R]. -f_qual('*') -> +f_qual(${, '*') -> + "1*"; %% Equivalent to "*" but the more common/obvious rendition +f_qual(_, '*') -> "*"; -f_qual({'*', N}) -> +f_qual(_, {'*', N}) -> [$*, ?I(N)]; -f_qual({N, '*'}) -> +f_qual(_, {N, '*'}) -> [?I(N), $*]; -f_qual({M,N}) -> +f_qual(_, {M,N}) -> [?I(M), $*, ?I(N)]. section(Key) -> @@ -500,7 +509,17 @@ make_body(Avps) -> avp([false, D, Avp]) -> avp(D, Avp); avp([Q, D, Avp]) -> - {qual(Q), avp(D, Avp)}. + case {qual(D, Q), avp(D, Avp)} of + {{0,1}, A} when D == $[ -> + A; + {{1,1}, A} -> + A; + T -> + T + end. +%% Could just store the qualifier as a pair in all cases but the more +%% compact form is easier to parse visually so live with a bit of +%% mapping. Ditto the use of '*'. avp(D, {'AVP', _}) -> delim(D, "AVP"); @@ -514,14 +533,40 @@ delim(${, N) -> delim($[, N) -> [N]. -qual({true, {_,_,N}}) -> - {'*', N}; -qual({{_,_,N}, true}) -> +%% There's a difference between max = 0 and not specifying an AVP: +%% reception of an AVP with max = 0 will always be an error, otherwise +%% it depends on the existence of 'AVP' and the M flag. + +qual(${, {{_,L,0}, _}) -> + ?RETURN(required_avp_has_zero_min_arity, [L]); +qual(${, {_, {_,L,0}}) -> + ?RETURN(required_avp_has_zero_max_arity, [L]); + +qual($[, {{_,L,N}, _}) + when 0 < N -> + ?RETURN(optional_avp_has_nonzero_min_arity, [L]); + +qual(_, {{_,L,Min}, {_,_,Max}}) + when Min > Max -> + ?RETURN(qualifier_has_min_greater_than_max, [Min, Max, L]); + +qual(_, true) -> + '*'; + +qual(${, {true, {_,_,N}}) -> + {1, N}; +qual(_, {true, {_,_,N}}) -> + {0, N}; + +qual(D, {{_,_,N}, true}) + when D == ${, N == 1; + D /= ${, N == 0 -> + '*'; +qual(_, {{_,_,N}, true}) -> {N, '*'}; -qual({{_,_,N},{_,_,M}}) -> - {N, M}; -qual(true) -> - '*'. + +qual(_, {{_,_,Min}, {_,_,Max}}) -> + {Min, Max}. %% Optional reports when running verbosely. report(What, [F | A]) @@ -851,10 +896,6 @@ xa(Ds, [[Qual, D, {'AVP', Line}] | Avps], Dict, Key, Name) -> xa([], [[_Qual, D, {_, Line, Name}] | _], _, _, _) -> ?RETURN(invalid_avp_order, [D, Name, close(D), Line]); -xa([D|_], [[{{_, Line, Min}, {_, _, Max}}, D, _] | _], _, _, _) - when Min > Max -> - ?RETURN(invalid_qualifier, [Min, Max, Line]); - xa([D|_] = Ds, [[Qual, D, {_, Line, AvpName}] | Avps], Dict, Key, Name) -> xa(Ds, Avps, diff --git a/lib/diameter/src/compiler/diameter_nowarn.erl b/lib/diameter/src/compiler/diameter_nowarn.erl new file mode 100644 index 0000000000..6c17af6563 --- /dev/null +++ b/lib/diameter/src/compiler/diameter_nowarn.erl @@ -0,0 +1,41 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2010-2011. 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% +%% + +%% +%% A parse transform to work around dialyzer currently not +%% understanding nowarn_unused_function except on individual +%% functions. The include of diameter_gen.hrl by generated dictionary +%% modules contains code that may not be called depending on the +%% dictionary. (The relay dictionary for example.) +%% +%% Even called functions may contain cases that aren't used for a +%% particular dictionary. This also causes dialyzer to complain but +%% there's no way to silence it in this case. +%% + +-module(diameter_nowarn). + +-export([parse_transform/2]). + +parse_transform(Forms, _Options) -> + [{attribute, ?LINE, compile, {nowarn_unused_function, {F,A}}} + || {function, _, F, A, _} <- Forms] + ++ Forms. +%% Note that dialyzer also doesn't understand {nowarn_unused_function, FAs} +%% with FAs a list of tuples. diff --git a/lib/diameter/src/modules.mk b/lib/diameter/src/modules.mk index c5d448b2ff..11d354e57e 100644 --- a/lib/diameter/src/modules.mk +++ b/lib/diameter/src/modules.mk @@ -66,6 +66,7 @@ CT_MODULES = \ base/diameter_info \ compiler/diameter_codegen \ compiler/diameter_exprecs \ + compiler/diameter_nowarn \ compiler/diameter_dict_scanner \ compiler/diameter_dict_util \ compiler/diameter_make diff --git a/lib/diameter/src/transport/diameter_sctp.erl b/lib/diameter/src/transport/diameter_sctp.erl index 209f8c01c1..68b0342cd5 100644 --- a/lib/diameter/src/transport/diameter_sctp.erl +++ b/lib/diameter/src/transport/diameter_sctp.erl @@ -546,10 +546,10 @@ send(Sock, AssocId, Stream, Bin) -> %% recv/2 %% Association established ... -recv({[], #sctp_assoc_change{state = comm_up, - outbound_streams = OS, - inbound_streams = IS, - assoc_id = Id}}, +recv({_, #sctp_assoc_change{state = comm_up, + outbound_streams = OS, + inbound_streams = IS, + assoc_id = Id}}, #transport{assoc_id = undefined, mode = {T, _}, socket = Sock} @@ -562,7 +562,7 @@ recv({[], #sctp_assoc_change{state = comm_up, streams = {IS, OS}}); %% ... or not: try the next address. -recv({[], #sctp_assoc_change{} = E}, +recv({_, #sctp_assoc_change{} = E}, #transport{assoc_id = undefined, socket = Sock, mode = {connect = C, {[RA|RAs], RP, Es}}} @@ -570,7 +570,7 @@ recv({[], #sctp_assoc_change{} = E}, S#transport{mode = {C, connect(Sock, RAs, RP, [{RA,E} | Es])}}; %% Lost association after establishment. -recv({[], #sctp_assoc_change{}}, _) -> +recv({_, #sctp_assoc_change{}}, _) -> stop; %% Inbound Diameter message. @@ -580,7 +580,7 @@ recv({[#sctp_sndrcvinfo{stream = Id}], Bin}, #transport{parent = Pid}) bin = Bin}), ok; -recv({[], #sctp_shutdown_event{assoc_id = Id}}, +recv({_, #sctp_shutdown_event{assoc_id = Id}}, #transport{assoc_id = Id}) -> stop; @@ -593,10 +593,10 @@ recv({[], #sctp_shutdown_event{assoc_id = Id}}, %% disabled by default so don't handle it. We could simply disable %% events we don't react to but don't. -recv({[], #sctp_paddr_change{}}, _) -> +recv({_, #sctp_paddr_change{}}, _) -> ok; -recv({[], #sctp_pdapi_event{}}, _) -> +recv({_, #sctp_pdapi_event{}}, _) -> ok. %% up/1 diff --git a/lib/diameter/test/diameter_app_SUITE.erl b/lib/diameter/test/diameter_app_SUITE.erl index 808f2cd30d..53332af626 100644 --- a/lib/diameter/test/diameter_app_SUITE.erl +++ b/lib/diameter/test/diameter_app_SUITE.erl @@ -48,6 +48,7 @@ diameter_dict_parser, diameter_dict_util, diameter_exprecs, + diameter_nowarn, diameter_make]). -define(HELP_MODULES, [diameter_dbg, diff --git a/lib/diameter/test/diameter_compiler_SUITE.erl b/lib/diameter/test/diameter_compiler_SUITE.erl index cc4b1ddac5..66d788f6ec 100644 --- a/lib/diameter/test/diameter_compiler_SUITE.erl +++ b/lib/diameter/test/diameter_compiler_SUITE.erl @@ -22,7 +22,6 @@ %% -module(diameter_compiler_SUITE). --compile({no_auto_import, [error/2]}). -export([suite/0, all/0, @@ -30,14 +29,16 @@ end_per_suite/1]). %% testcases --export([format/1, - replace/1, replace/2]). +-export([format/1, format/2, + replace/1, replace/2, + generate/1, generate/4, generate/0]). -export([dict/0]). %% fake dictionary module -define(base, "base_rfc3588.dia"). -define(util, diameter_util). -define(S, atom_to_list). +-define(L, integer_to_list). %% =========================================================================== @@ -46,7 +47,10 @@ %% returned in the first element of the error tuple returned by %% diameter_dict_util:parse/2. -define(REPLACE, - [{scan, + [{ok, + "", + ""}, + {scan, "@id 0", "@id \\&"}, {scan, @@ -154,9 +158,126 @@ {invalid_avp_order, "CEA ::=", "{Result-Code} &"}, - {invalid_qualifier, - "CEA ::=.*", - "& 3*2"}, + {ok, + "{ Product-Name", + "* &"}, + {required_avp_has_zero_max_arity, + "{ Product-Name", + "*0 &"}, + {required_avp_has_zero_min_arity, + "{ Product-Name", + "0* &"}, + {required_avp_has_zero_min_arity, + "{ Product-Name", + "0*0 &"}, + {ok, + "{ Product-Name", + "*1 &"}, + {ok, + "{ Product-Name", + "1* &"}, + {ok, + "{ Product-Name", + "1*1 &"}, + {ok, + "{ Product-Name", + "2* &"}, + {ok, + "{ Product-Name", + "*2 &"}, + {ok, + "{ Product-Name", + "2*2 &"}, + {ok, + "{ Product-Name", + "2*3 &"}, + {qualifier_has_min_greater_than_max, + "{ Product-Name", + "3*2 &"}, + {ok, + "\\[ Origin-State-Id", + "* &"}, + {ok, + "\\[ Origin-State-Id", + "0* &"}, + {ok, + "\\[ Origin-State-Id", + "*0 &"}, + {ok, + "\\[ Origin-State-Id", + "0*0 &"}, + {ok, + "\\[ Origin-State-Id", + "0*1 &"}, + {ok, + "\\[ Origin-State-Id", + "0*2 &"}, + {ok, + "\\[ Origin-State-Id", + "*1 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "1* &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "1*1 &"}, + {ok, + "\\[ Origin-State-Id", + "*2 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "2* &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "2*2 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "2*3 &"}, + {optional_avp_has_nonzero_min_arity, + "\\[ Origin-State-Id", + "3*2 &"}, + {ok, + "^ *< Session-Id", + "* &"}, + {ok, + "^ *< Session-Id", + "*0 &"}, + {ok, + "^ *< Session-Id", + "0* &"}, + {ok, + "^ *< Session-Id", + "0*0 &"}, + {ok, + "^ *< Session-Id", + "0*1 &"}, + {ok, + "^ *< Session-Id", + "0*2 &"}, + {ok, + "^ *< Session-Id", + "*1 &"}, + {ok, + "^ *< Session-Id", + "1* &"}, + {ok, + "^ *< Session-Id", + "1*1 &"}, + {ok, + "^ *< Session-Id", + "*2 &"}, + {ok, + "^ *< Session-Id", + "2* &"}, + {ok, + "^ *< Session-Id", + "2*2 &"}, + {ok, + "^ *< Session-Id", + "2*3 &"}, + {qualifier_has_min_greater_than_max, + "^ *< Session-Id", + "3*2 &"}, {avp_already_referenced, "CER ::=.*", "& {Origin-Host}"}, @@ -213,7 +334,8 @@ suite() -> all() -> [format, - replace]. + replace, + generate]. %% Error handling testcases will make an erroneous dictionary out of %% the base dictionary and check that the expected error results. @@ -229,24 +351,35 @@ end_per_suite(_Config) -> %% =========================================================================== %% testcases +%% format/1 +%% %% Ensure that parse o format is the identity map. + format(Config) -> Bin = proplists:get_value(base, Config), - {ok, Dict} = diameter_dict_util:parse(Bin, []), + [] = ?util:run([{?MODULE, [format, M, Bin]} + || E <- ?REPLACE, + {ok, M} <- [norm(E)]]). + +format(Mods, Bin) -> + B = modify(Bin, Mods), + {ok, Dict} = diameter_dict_util:parse(B, []), {ok, D} = diameter_dict_util:parse(diameter_dict_util:format(Dict), []), {Dict, Dict} = {Dict, D}. %% replace/1 +%% +%% Ensure the expected success/error when parsing a morphed common +%% dictionary. replace(Config) -> Bin = proplists:get_value(base, Config), - [] = ?util:run([{?MODULE, [replace, E, Bin]} || E <- ?REPLACE]). - -replace({E, RE, Repl}, Bin) -> - replace({E, [{RE, Repl}]}, Bin); + [] = ?util:run([{?MODULE, [replace, N, Bin]} + || E <- ?REPLACE, + N <- [norm(E)]]). replace({E, Mods}, Bin) -> - B = iolist_to_binary(lists:foldl(fun re/2, Bin, Mods)), + B = modify(Bin, Mods), case {E, diameter_dict_util:parse(B, [{include, here()}]), Mods} of {ok, {ok, Dict}, _} -> Dict; @@ -260,8 +393,44 @@ replace({E, Mods}, Bin) -> re({RE, Repl}, Bin) -> re:replace(Bin, RE, Repl, [multiline]). +%% generate/1 +%% +%% Ensure success when generating code and compiling. + +generate() -> + [{timetrap, {seconds, length(?REPLACE)}}]. + +generate(Config) -> + Bin = proplists:get_value(base, Config), + Rs = lists:zip(?REPLACE, lists:seq(1, length(?REPLACE))), + [] = ?util:run([{?MODULE, [generate, M, Bin, N, T]} + || {E,N} <- Rs, + {ok, M} <- [norm(E)], + T <- [erl, hrl, spec]]). + +generate(Mods, Bin, N, Mode) -> + B = modify(Bin, Mods ++ [{"@name .*", "@name dict" ++ ?L(N)}]), + {ok, Dict} = diameter_dict_util:parse(B, []), + File = "dict" ++ integer_to_list(N), + {_, ok} = {Dict, diameter_codegen:from_dict("dict", + Dict, + [{name, File}, + {prefix, "base"}, + debug], + Mode)}, + Mode == erl + andalso ({ok, _} = compile:file(File ++ ".erl", [return_errors])). + %% =========================================================================== +modify(Bin, Mods) -> + lists:foldl(fun re/2, Bin, Mods). + +norm({E, RE, Repl}) -> + {E, [{RE, Repl}]}; +norm({_,_} = T) -> + T. + nochar(Char, Str, Err) -> Err == parse orelse not lists:member(Char, Str) orelse Str. diff --git a/lib/diameter/test/diameter_tls_SUITE.erl b/lib/diameter/test/diameter_tls_SUITE.erl index a325ca33eb..38282282b8 100644 --- a/lib/diameter/test/diameter_tls_SUITE.erl +++ b/lib/diameter/test/diameter_tls_SUITE.erl @@ -157,7 +157,7 @@ init_per_suite(Config) -> try false /= os:find_executable("openssl") orelse throw({?MODULE, no_openssl}), - ok == crypto:start() + ok == (catch crypto:start()) orelse throw({?MODULE, no_crypto}), Config catch |