diff options
Diffstat (limited to 'lib/dialyzer/src/dialyzer_utils.erl')
-rw-r--r-- | lib/dialyzer/src/dialyzer_utils.erl | 458 |
1 files changed, 458 insertions, 0 deletions
diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl new file mode 100644 index 0000000000..fa9ad2eae2 --- /dev/null +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -0,0 +1,458 @@ +%% -*- erlang-indent-level: 2 -*- +%%----------------------------------------------------------------------- +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2006-2009. 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% +%% + +%%%------------------------------------------------------------------- +%%% File : dialyzer_utils.erl +%%% Author : Tobias Lindahl <[email protected]> +%%% Description : +%%% +%%% Created : 5 Dec 2006 by Tobias Lindahl <[email protected]> +%%%------------------------------------------------------------------- +-module(dialyzer_utils). + +-export([ + format_sig/1, + format_sig/2, + get_abstract_code_from_beam/1, + get_abstract_code_from_src/1, + get_abstract_code_from_src/2, + get_core_from_abstract_code/1, + get_core_from_abstract_code/2, + get_core_from_src/1, + get_core_from_src/2, + get_record_and_type_info/1, + get_spec_info/3, + merge_records/2, + pp_hook/0, + process_record_remote_types/1, + src_compiler_opts/0 + ]). + +-include("dialyzer.hrl"). + +%% +%% Types that need to be imported from somewhere else +%% + +-type abstract_code() :: [tuple()]. %% XXX: refine +-type comp_options() :: [atom()]. %% XXX: only a resticted set of options used + +%% ============================================================================ +%% +%% Compilation utils +%% +%% ============================================================================ + +-spec get_abstract_code_from_src(atom() | file:filename()) -> + {'ok', abstract_code()} | {'error', [string()]}. + +get_abstract_code_from_src(File) -> + get_abstract_code_from_src(File, src_compiler_opts()). + +-spec get_abstract_code_from_src(atom() | file:filename(), comp_options()) -> + {'ok', abstract_code()} | {'error', [string()]}. + +get_abstract_code_from_src(File, Opts) -> + case compile:file(File, [to_pp, binary|Opts]) of + error -> {error, []}; + {error, Errors, _} -> {error, format_errors(Errors)}; + {ok, _, AbstrCode} -> {ok, AbstrCode} + end. + +-type get_core_from_src_ret() :: {'ok', cerl:c_module()} | {'error', string()}. + +-spec get_core_from_src(file:filename()) -> get_core_from_src_ret(). + +get_core_from_src(File) -> + get_core_from_src(File, []). + +-spec get_core_from_src(file:filename(), comp_options()) -> get_core_from_src_ret(). + +get_core_from_src(File, Opts) -> + case get_abstract_code_from_src(File, Opts) of + {error, _} = Error -> Error; + {ok, AbstrCode} -> + case get_core_from_abstract_code(AbstrCode, Opts) of + error -> {error, " Could not get Core Erlang code from abstract code"}; + {ok, _Core} = C -> C + end + end. + +-spec get_abstract_code_from_beam(file:filename()) -> 'error' | {'ok', abstract_code()}. + +get_abstract_code_from_beam(File) -> + case beam_lib:chunks(File, [abstract_code]) of + {ok, {_, List}} -> + case lists:keyfind(abstract_code, 1, List) of + {abstract_code, {raw_abstract_v1, Abstr}} -> {ok, Abstr}; + _ -> error + end; + _ -> + %% No or unsuitable abstract code. + error + end. + +-type get_core_from_abs_ret() :: {'ok', cerl:c_module()} | 'error'. + +-spec get_core_from_abstract_code(abstract_code()) -> get_core_from_abs_ret(). + +get_core_from_abstract_code(AbstrCode) -> + get_core_from_abstract_code(AbstrCode, []). + +-spec get_core_from_abstract_code(abstract_code(), comp_options()) -> get_core_from_abs_ret(). + +get_core_from_abstract_code(AbstrCode, Opts) -> + %% We do not want the parse_transforms around since we already + %% performed them. In some cases we end up in trouble when + %% performing them again. + AbstrCode1 = cleanup_parse_transforms(AbstrCode), + try compile:forms(AbstrCode1, Opts ++ src_compiler_opts()) of + {ok, _, Core} -> {ok, Core}; + _What -> error + catch + error:_ -> error + end. + +%% ============================================================================ +%% +%% Typed Records +%% +%% ============================================================================ + +-spec get_record_and_type_info(abstract_code()) -> + {'ok', dict()} | {'error', string()}. + +get_record_and_type_info(AbstractCode) -> + Module = get_module(AbstractCode), + get_record_and_type_info(AbstractCode, Module, dict:new()). + +-spec get_record_and_type_info(abstract_code(), atom(), dict()) -> + {'ok', dict()} | {'error', string()}. + +get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left], + Module, RecDict) -> + case get_record_fields(Fields0, RecDict) of + {ok, Fields} -> + Arity = length(Fields), + Fun = fun(OldOrdDict) -> orddict:store(Arity, Fields, OldOrdDict) end, + NewRecDict = dict:update({record, Name}, Fun, [{Arity, Fields}], RecDict), + get_record_and_type_info(Left, Module, NewRecDict); + {error, Error} -> + {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", + [Name, Error]))} + end; +get_record_and_type_info([{attribute, _, type, {{record, Name}, Fields0, []}} + |Left], Module, RecDict) -> + %% This overrides the original record declaration. + case get_record_fields(Fields0, RecDict) of + {ok, Fields} -> + Arity = length(Fields), + Fun = fun(OldOrdDict) -> orddict:store(Arity, Fields, OldOrdDict) end, + NewRecDict = dict:update({record, Name}, Fun, [{Arity, Fields}], RecDict), + get_record_and_type_info(Left, Module, NewRecDict); + {error, Error} -> + {error, lists:flatten(io_lib:format(" Error while parsing #~w{}: ~s\n", + [Name, Error]))} + end; +get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left], + Module, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> + try + NewRecDict = add_new_type(Attr, Name, TypeForm, [], Module, RecDict), + get_record_and_type_info(Left, Module, NewRecDict) + catch + throw:{error, _} = Error -> Error + end; +get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left], + Module, RecDict) when Attr =:= 'type'; Attr =:= 'opaque' -> + try + NewRecDict = add_new_type(Attr, Name, TypeForm, Args, Module, RecDict), + get_record_and_type_info(Left, Module, NewRecDict) + catch + throw:{error, _} = Error -> Error + end; +get_record_and_type_info([_Other|Left], Module, RecDict) -> + get_record_and_type_info(Left, Module, RecDict); +get_record_and_type_info([], _Module, RecDict) -> + {ok, RecDict}. + +add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> + case erl_types:type_is_defined(TypeOrOpaque, Name, RecDict) of + true -> + throw({error, io_lib:format("Type already defined: ~w\n", [Name])}); + false -> + ArgTypes = [erl_types:t_from_form(X) || X <- ArgForms], + _Type = erl_types:t_from_form(TypeForm, RecDict), + case lists:all(fun erl_types:t_is_var/1, ArgTypes) of + true -> + ArgNames = [erl_types:t_var_name(X) || X <- ArgTypes], + dict:store({TypeOrOpaque, Name}, {Module, TypeForm, ArgNames}, RecDict); + false -> + throw({error, io_lib:format("Type declaration for ~w does not " + "have variables as parameters", [Name])}) + end + end. + +get_record_fields(Fields, RecDict) -> + get_record_fields(Fields, RecDict, []). + +get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left], + RecDict, Acc) -> + Name = + case OrdRecField of + {record_field, _Line, Name0} -> erl_parse:normalise(Name0); + {record_field, _Line, Name0, _Init} -> erl_parse:normalise(Name0) + end, + try + Type = erl_types:t_from_form(TypeForm, RecDict), + get_record_fields(Left, RecDict, [{Name, Type}|Acc]) + catch + throw:{error, _} = Error -> Error + end; +get_record_fields([{record_field, _Line, Name}|Left], RecDict, Acc) -> + NewAcc = [{erl_parse:normalise(Name), erl_types:t_any()}|Acc], + get_record_fields(Left, RecDict, NewAcc); +get_record_fields([{record_field, _Line, Name, _Init}|Left], RecDict, Acc) -> + NewAcc = [{erl_parse:normalise(Name), erl_types:t_any()}|Acc], + get_record_fields(Left, RecDict, NewAcc); +get_record_fields([], _RecDict, Acc) -> + {ok, lists:reverse(Acc)}. + +-spec process_record_remote_types(dialyzer_codeserver:codeserver()) -> dialyzer_codeserver:codeserver(). + +process_record_remote_types(CServer) -> + TempRecords = dialyzer_codeserver:get_temp_records(CServer), + RecordFun = + fun(Key, Value) -> + case Key of + {record, _Name} -> + FieldFun = + fun(_Arity, Fields) -> + [{Name, erl_types:t_solve_remote(Field, TempRecords)} || {Name, Field} <- Fields] + end, + orddict:map(FieldFun, Value); + _Other -> Value + end + end, + ModuleFun = + fun(_Module, Record) -> + dict:map(RecordFun, Record) + end, + NewRecords = dict:map(ModuleFun, TempRecords), + dialyzer_codeserver:finalize_records(NewRecords, CServer). + +-spec merge_records(dict(), dict()) -> dict(). + +merge_records(NewRecords, OldRecords) -> + dict:merge(fun(_Key, NewVal, _OldVal) -> NewVal end, NewRecords, OldRecords). + +%% ============================================================================ +%% +%% Spec info +%% +%% ============================================================================ + +-spec get_spec_info(module(), abstract_code(), dict()) -> + {'ok', dict()} | {'error', string()}. + +get_spec_info(ModName, AbstractCode, RecordsDict) -> + get_spec_info(AbstractCode, dict:new(), RecordsDict, ModName, "nofile"). + +%% TypeSpec is a list of conditional contracts for a function. +%% Each contract is of the form {[Argument], Range, [Constraint]} where +%% - Argument and Range are in erl_types:erl_type() format and +%% - Constraint is of the form {subtype, T1, T2} where T1 and T2 +%% are erl_types:erl_type() + +get_spec_info([{attribute, Ln, spec, {Id, TypeSpec}}|Left], + SpecDict, RecordsDict, ModName, File) when is_list(TypeSpec) -> + MFA = case Id of + {_, _, _} = T -> T; + {F, A} -> {ModName, F, A} + end, + try dict:find(MFA, SpecDict) of + error -> + NewSpecDict = + dialyzer_contracts:store_tmp_contract(MFA, {File, Ln}, TypeSpec, + SpecDict, RecordsDict), + get_spec_info(Left, NewSpecDict, RecordsDict, ModName, File); + {ok, {{OtherFile, L},_C}} -> + {Mod, Fun, Arity} = MFA, + Msg = io_lib:format(" Contract for function ~w:~w/~w " + "already defined in ~s:~w\n", + [Mod, Fun, Arity, OtherFile, L]), + throw({error, Msg}) + catch + throw:{error, Error} -> + {error, lists:flatten(io_lib:format(" Error while parsing contract " + "in line ~w: ~s\n", [Ln, Error]))} + end; +get_spec_info([{attribute, _, file, {IncludeFile, _}}|Left], + SpecDict, RecordsDict, ModName, _File) -> + get_spec_info(Left, SpecDict, RecordsDict, ModName, IncludeFile); +get_spec_info([_Other|Left], SpecDict, RecordsDict, ModName, File) -> + get_spec_info(Left, SpecDict, RecordsDict, ModName, File); +get_spec_info([], SpecDict, _RecordsDict, _ModName, _File) -> + {ok, SpecDict}. + +%% ============================================================================ +%% +%% Util utils +%% +%% ============================================================================ + +-spec src_compiler_opts() -> comp_options(). + +src_compiler_opts() -> + [no_copt, to_core, binary, return_errors, + no_inline, strict_record_tests, strict_record_updates]. + +-spec get_module(abstract_code()) -> module(). + +get_module([{attribute, _, module, {M, _As}} | _]) -> M; +get_module([{attribute, _, module, M} | _]) -> M; +get_module([_ | Rest]) -> get_module(Rest). + +-spec cleanup_parse_transforms(abstract_code()) -> abstract_code(). + +cleanup_parse_transforms([{attribute, _, compile, {parse_transform, _}}|Left]) -> + cleanup_parse_transforms(Left); +cleanup_parse_transforms([Other|Left]) -> + [Other|cleanup_parse_transforms(Left)]; +cleanup_parse_transforms([]) -> + []. + +-spec format_errors([{module(), string()}]) -> [string()]. + +format_errors([{Mod, Errors}|Left]) -> + FormatedError = + [io_lib:format("~s:~w: ~s\n", [Mod, Line, M:format_error(Desc)]) + || {Line, M, Desc} <- Errors], + [lists:flatten(FormatedError) | format_errors(Left)]; +format_errors([]) -> + []. + +-spec format_sig(erl_types:erl_type()) -> string(). + +format_sig(Type) -> + format_sig(Type, dict:new()). + +-spec format_sig(erl_types:erl_type(), dict()) -> string(). + +format_sig(Type, RecDict) -> + "fun(" ++ Sig = lists:flatten(erl_types:t_to_string(Type, RecDict)), + ")" ++ RevSig = lists:reverse(Sig), + lists:reverse(RevSig). + +%%------------------------------------------------------------------- +%% Author : Per Gustafsson <[email protected]> +%% Description : Provides better printing of binaries. +%% Created : 5 March 2007 +%%------------------------------------------------------------------- + +pp_hook() -> + fun pp_hook/3. + +-spec pp_hook() -> fun((cerl:cerl(), _, _) -> term()). + +pp_hook(Node, Ctxt, Cont) -> + case cerl:type(Node) of + binary -> + pp_binary(Node, Ctxt, Cont); + bitstr -> + pp_segment(Node, Ctxt, Cont); + _ -> + Cont(Node, Ctxt) + end. + +pp_binary(Node, Ctxt, Cont) -> + prettypr:beside(prettypr:text("<<"), + prettypr:beside(pp_segments(cerl:binary_segments(Node), + Ctxt, Cont), + prettypr:text(">>"))). + +pp_segments([Seg], Ctxt, Cont) -> + pp_segment(Seg, Ctxt, Cont); +pp_segments([], _Ctxt, _Cont) -> + prettypr:text(""); +pp_segments([Seg|Rest], Ctxt, Cont) -> + prettypr:beside(pp_segment(Seg, Ctxt, Cont), + prettypr:beside(prettypr:text(","), + pp_segments(Rest, Ctxt, Cont))). + +pp_segment(Node, Ctxt, Cont) -> + Val = cerl:bitstr_val(Node), + Size = cerl:bitstr_size(Node), + Unit = cerl:bitstr_unit(Node), + Type = cerl:bitstr_type(Node), + Flags = cerl:bitstr_flags(Node), + prettypr:beside(Cont(Val, Ctxt), + prettypr:beside(pp_size(Size, Ctxt, Cont), + prettypr:beside(pp_opts(Type, Flags), + pp_unit(Unit, Ctxt, Cont)))). + +pp_size(Size, Ctxt, Cont) -> + case cerl:is_c_atom(Size) of + true -> + prettypr:text(""); + false -> + prettypr:beside(prettypr:text(":"), Cont(Size, Ctxt)) + end. + +pp_opts(Type, Flags) -> + FinalFlags = + case cerl:atom_val(Type) of + binary -> []; + float -> keep_endian(cerl:concrete(Flags)); + integer -> keep_all(cerl:concrete(Flags)); + utf8 -> []; + utf16 -> []; + utf32 -> [] + end, + prettypr:beside(prettypr:text("/"), + prettypr:beside(pp_atom(Type), + pp_flags(FinalFlags))). + +pp_flags([]) -> + prettypr:text(""); +pp_flags([Flag|Flags]) -> + prettypr:beside(prettypr:text("-"), + prettypr:beside(pp_atom(Flag), + pp_flags(Flags))). + +keep_endian(Flags) -> + [cerl:c_atom(X) || X <- Flags, (X =:= little) or (X =:= native)]. + +keep_all(Flags) -> + [cerl:c_atom(X) || X <- Flags, + (X =:= little) or (X =:= native) or (X =:= signed)]. + +pp_unit(Unit, Ctxt, Cont) -> + case cerl:concrete(Unit) of + N when is_integer(N) -> + prettypr:beside(prettypr:text("-"), + prettypr:beside(prettypr:text("unit:"), + Cont(Unit, Ctxt))); + _ -> % Other value: e.g. 'undefined' when UTF + prettypr:text("") + end. + +pp_atom(Atom) -> + String = atom_to_list(cerl:atom_val(Atom)), + prettypr:text(String). |