diff options
Diffstat (limited to 'lib/dialyzer/src')
-rw-r--r-- | lib/dialyzer/src/dialyzer.app.src | 4 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer.erl | 39 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl.erl | 64 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_cl_parse.erl | 10 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_contracts.erl | 21 | ||||
-rw-r--r-- | lib/dialyzer/src/dialyzer_utils.erl | 148 |
6 files changed, 189 insertions, 97 deletions
diff --git a/lib/dialyzer/src/dialyzer.app.src b/lib/dialyzer/src/dialyzer.app.src index 7b2e1d4a9d..b6b9173a84 100644 --- a/lib/dialyzer/src/dialyzer.app.src +++ b/lib/dialyzer/src/dialyzer.app.src @@ -2,7 +2,7 @@ %% %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2014. All Rights Reserved. +%% Copyright Ericsson AB 2006-2015. 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 @@ -45,6 +45,6 @@ {registered, []}, {applications, [compiler, gs, hipe, kernel, stdlib, wx]}, {env, []}, - {runtime_dependencies, ["wx-1.2","syntax_tools-1.6.14","stdlib-2.0", + {runtime_dependencies, ["wx-1.2","syntax_tools-1.6.14","stdlib-2.5", "kernel-3.0","hipe-3.10.3","erts-7.0", "compiler-5.0"]}]}. diff --git a/lib/dialyzer/src/dialyzer.erl b/lib/dialyzer/src/dialyzer.erl index c9e7da9ef0..c8537e3bd8 100644 --- a/lib/dialyzer/src/dialyzer.erl +++ b/lib/dialyzer/src/dialyzer.erl @@ -2,7 +2,7 @@ %%----------------------------------------------------------------------- %% %CopyrightBegin% %% -%% Copyright Ericsson AB 2006-2014. All Rights Reserved. +%% Copyright Ericsson AB 2006-2015. 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 @@ -162,14 +162,7 @@ run(Opts) -> {error, Msg} -> throw({dialyzer_error, Msg}); OptsRecord -> - case OptsRecord#options.check_plt of - true -> - case cl_check_init(OptsRecord) of - {ok, ?RET_NOTHING_SUSPICIOUS} -> ok; - {error, ErrorMsg1} -> throw({dialyzer_error, ErrorMsg1}) - end; - false -> ok - end, + ok = check_init(OptsRecord), case dialyzer_cl:start(OptsRecord) of {?RET_DISCREPANCIES, Warnings} -> Warnings; {?RET_NOTHING_SUSPICIOUS, _} -> [] @@ -179,6 +172,16 @@ run(Opts) -> erlang:error({dialyzer_error, lists:flatten(ErrorMsg)}) end. +check_init(#options{analysis_type = plt_check}) -> + ok; +check_init(#options{check_plt = true} = OptsRecord) -> + case cl_check_init(OptsRecord) of + {ok, _} -> ok; + {error, Msg} -> throw({dialyzer_error, Msg}) + end; +check_init(#options{check_plt = false}) -> + ok. + internal_gui(OptsRecord) -> F = fun() -> dialyzer_gui_wx:start(OptsRecord), @@ -199,17 +202,13 @@ gui(Opts) -> throw({dialyzer_error, Msg}); OptsRecord -> ok = check_gui_options(OptsRecord), - case cl_check_init(OptsRecord) of - {ok, ?RET_NOTHING_SUSPICIOUS} -> - F = fun() -> - dialyzer_gui_wx:start(OptsRecord) - end, - case doit(F) of - {ok, _} -> ok; - {error, Msg} -> throw({dialyzer_error, Msg}) - end; - {error, ErrorMsg1} -> - throw({dialyzer_error, ErrorMsg1}) + ok = check_init(OptsRecord), + F = fun() -> + dialyzer_gui_wx:start(OptsRecord) + end, + case doit(F) of + {ok, _} -> ok; + {error, Msg} -> throw({dialyzer_error, Msg}) end catch throw:{dialyzer_error, ErrorMsg} -> diff --git a/lib/dialyzer/src/dialyzer_cl.erl b/lib/dialyzer/src/dialyzer_cl.erl index 4386a8d52a..55fcd15641 100644 --- a/lib/dialyzer/src/dialyzer_cl.erl +++ b/lib/dialyzer/src/dialyzer_cl.erl @@ -512,32 +512,82 @@ hipe_compile(Files, #options{erlang_mode = ErlangMode} = Options) -> dialyzer_worker], report_native_comp(Options), {T1, _} = statistics(wall_clock), - native_compile(Mods), + Cache = (get(dialyzer_options_native_cache) =/= false), + native_compile(Mods, Cache), {T2, _} = statistics(wall_clock), report_elapsed_time(T1, T2, Options) end end. -native_compile(Mods) -> +native_compile(Mods, Cache) -> case dialyzer_utils:parallelism() > ?MIN_PARALLELISM of true -> Parent = self(), - Pids = [spawn(fun () -> Parent ! {self(), hc(M)} end) || M <- Mods], + Pids = [spawn(fun () -> Parent ! {self(), hc(M, Cache)} end) || M <- Mods], lists:foreach(fun (Pid) -> receive {Pid, Res} -> Res end end, Pids); false -> - lists:foreach(fun (Mod) -> hc(Mod) end, Mods) + lists:foreach(fun (Mod) -> hc(Mod, Cache) end, Mods) end. -hc(Mod) -> +hc(Mod, Cache) -> {module, Mod} = code:ensure_loaded(Mod), case code:is_module_native(Mod) of true -> ok; false -> %% io:format(" ~w", [Mod]), - {ok, Mod} = hipe:c(Mod), - ok + case Cache of + false -> + {ok, Mod} = hipe:c(Mod), + ok; + true -> + hc_cache(Mod) + end end. +hc_cache(Mod) -> + CacheBase = cache_base_dir(), + %% Use HiPE architecture and version in directory name, to avoid + %% clashes between incompatible binaries. + HipeArchVersion = + lists:concat( + [erlang:system_info(hipe_architecture), "-", + hipe:version(), "-", + hipe_bifs:system_crc()]), + CacheDir = filename:join(CacheBase, HipeArchVersion), + OrigBeamFile = code:which(Mod), + {ok, {Mod, <<Checksum:128>>}} = beam_lib:md5(OrigBeamFile), + CachedBeamFile = filename:join(CacheDir, lists:concat([Mod, "-", Checksum, ".beam"])), + ok = filelib:ensure_dir(CachedBeamFile), + ModBin = + case filelib:is_file(CachedBeamFile) of + true -> + {ok, BinFromFile} = file:read_file(CachedBeamFile), + BinFromFile; + false -> + {ok, Mod, CompiledBin} = compile:file(OrigBeamFile, [from_beam, native, binary]), + ok = file:write_file(CachedBeamFile, CompiledBin), + CompiledBin + end, + code:unstick_dir(filename:dirname(OrigBeamFile)), + {module, Mod} = code:load_binary(Mod, CachedBeamFile, ModBin), + true = code:is_module_native(Mod), + ok. + +cache_base_dir() -> + %% http://standards.freedesktop.org/basedir-spec/basedir-spec-0.7.html + %% If XDG_CACHE_HOME is set to an absolute path, use it as base. + XdgCacheHome = os:getenv("XDG_CACHE_HOME"), + CacheHome = + case is_list(XdgCacheHome) andalso filename:pathtype(XdgCacheHome) =:= absolute of + true -> + XdgCacheHome; + false -> + %% Otherwise, the default is $HOME/.cache. + {ok, [[Home]]} = init:get_argument(home), + filename:join(Home, ".cache") + end, + filename:join([CacheHome, "dialyzer_hipe_cache"]). + new_state() -> #cl_state{}. diff --git a/lib/dialyzer/src/dialyzer_cl_parse.erl b/lib/dialyzer/src/dialyzer_cl_parse.erl index 21fc424a1b..fae88ed6e8 100644 --- a/lib/dialyzer/src/dialyzer_cl_parse.erl +++ b/lib/dialyzer/src/dialyzer_cl_parse.erl @@ -75,6 +75,9 @@ cl(["-nn"|T]) -> cl(["--no_native"|T]) -> put(dialyzer_options_native, false), cl(T); +cl(["--no_native_cache"|T]) -> + put(dialyzer_options_native_cache, false), + cl(T); cl(["--plt_info"|T]) -> put(dialyzer_options_analysis_type, plt_info), cl(T); @@ -363,7 +366,7 @@ help_message() -> [--build_plt] [--add_to_plt] [--remove_from_plt] [--check_plt] [--no_check_plt] [--plt_info] [--get_warnings] [--dump_callgraph file] [--no_native] [--fullpath] - [--statistics] + [--statistics] [--no_native_cache] Options: files_or_dirs (for backwards compatibility also as: -c files_or_dirs) Use Dialyzer from the command line to detect defects in the @@ -468,6 +471,11 @@ Options: Bypass the native code compilation of some key files that Dialyzer heuristically performs when dialyzing many files; this avoids the compilation time but it may result in (much) longer analysis time. + --no_native_cache + By default, Dialyzer caches the results of native compilation in the + $XDG_CACHE_HOME/erlang/dialyzer_hipe_cache directory. + XDG_CACHE_HOME defaults to $HOME/.cache. Use this option to disable + caching. --fullpath Display the full path names of files for which warnings are emitted. --gui diff --git a/lib/dialyzer/src/dialyzer_contracts.erl b/lib/dialyzer/src/dialyzer_contracts.erl index 4a1ba9c539..914a4c6d8f 100644 --- a/lib/dialyzer/src/dialyzer_contracts.erl +++ b/lib/dialyzer/src/dialyzer_contracts.erl @@ -409,7 +409,7 @@ contract_from_form([{type, _, 'fun', [_, _]} = Form | Left], Module, RecDict, fun(ExpTypes, AllRecords) -> NewType = try - erl_types:t_from_form(Form, ExpTypes, Module, AllRecords) + from_form_with_check(Form, ExpTypes, Module, AllRecords) catch throw:{error, Msg} -> {File, Line} = FileLine, @@ -430,8 +430,8 @@ contract_from_form([{type, _L1, bounded_fun, fun(ExpTypes, AllRecords) -> {Constr1, VarDict} = process_constraints(Constr, Module, RecDict, ExpTypes, AllRecords), - NewType = erl_types:t_from_form(Form, ExpTypes, Module, AllRecords, - VarDict), + NewType = from_form_with_check(Form, ExpTypes, Module, AllRecords, + VarDict), NewTypeNoVars = erl_types:subst_all_vars_to_any(NewType), {NewTypeNoVars, Constr1} end, @@ -454,7 +454,7 @@ initialize_constraints([], _Module, _RecDict, _ExpTypes, _AllRecords, Acc) -> initialize_constraints([Constr|Rest], Module, RecDict, ExpTypes, AllRecords, Acc) -> case Constr of {type, _, constraint, [{atom, _, is_subtype}, [Type1, Type2]]} -> - T1 = final_form(Type1, Module, ExpTypes, AllRecords, dict:new()), + T1 = final_form(Type1, ExpTypes, Module, AllRecords, dict:new()), Entry = {T1, Type2}, initialize_constraints(Rest, Module, RecDict, ExpTypes, AllRecords, [Entry|Acc]); {type, _, constraint, [{atom,_,Name}, List]} -> @@ -483,7 +483,16 @@ constraints_fixpoint(OldVarDict, Module, Constrs, RecDict, ExpTypes, AllRecords) constraints_fixpoint(NewVarDict, Module, Constrs, RecDict, ExpTypes, AllRecords) end. -final_form(Form, Module, ExpTypes, AllRecords, VarDict) -> +final_form(Form, ExpTypes, Module, AllRecords, VarDict) -> + from_form_with_check(Form, ExpTypes, Module, AllRecords, VarDict). + +from_form_with_check(Form, ExpTypes, Module, AllRecords) -> + erl_types:t_check_record_fields(Form, ExpTypes, Module, AllRecords), + erl_types:t_from_form(Form, ExpTypes, Module, AllRecords). + +from_form_with_check(Form, ExpTypes, Module, AllRecords, VarDict) -> + erl_types:t_check_record_fields(Form, ExpTypes, Module, AllRecords, + VarDict), erl_types:t_from_form(Form, ExpTypes, Module, AllRecords, VarDict). constraints_to_dict(Constrs, Module, RecDict, ExpTypes, AllRecords, VarDict) -> @@ -495,7 +504,7 @@ constraints_to_subs([], _Module, _RecDict, _ExpTypes, _AllRecords, _VarDict, Acc Acc; constraints_to_subs([C|Rest], Module, RecDict, ExpTypes, AllRecords, VarDict, Acc) -> {T1, Form2} = C, - T2 = final_form(Form2, Module, ExpTypes, AllRecords, VarDict), + T2 = final_form(Form2, ExpTypes, Module, AllRecords, VarDict), NewAcc = [{subtype, T1, T2}|Acc], constraints_to_subs(Rest, Module, RecDict, ExpTypes, AllRecords, VarDict, NewAcc). diff --git a/lib/dialyzer/src/dialyzer_utils.erl b/lib/dialyzer/src/dialyzer_utils.erl index e29fc3ba8b..dbfd20014c 100644 --- a/lib/dialyzer/src/dialyzer_utils.erl +++ b/lib/dialyzer/src/dialyzer_utils.erl @@ -64,15 +64,15 @@ print_types(RecDict) -> print_types1([], _) -> ok; print_types1([{type, _Name, _NArgs} = Key|T], RecDict) -> - {ok, {{_Mod, _Form, _Args}, Type}} = dict:find(Key, RecDict), + {ok, {{_Mod, _FileLine, _Form, _Args}, Type}} = dict:find(Key, RecDict), io:format("\n~w: ~w\n", [Key, Type]), print_types1(T, RecDict); print_types1([{opaque, _Name, _NArgs} = Key|T], RecDict) -> - {ok, {{_Mod, _Form, _Args}, Type}} = dict:find(Key, RecDict), + {ok, {{_Mod, _FileLine, _Form, _Args}, Type}} = dict:find(Key, RecDict), io:format("\n~w: ~w\n", [Key, Type]), print_types1(T, RecDict); print_types1([{record, _Name} = Key|T], RecDict) -> - {ok, [{_Arity, _Fields} = AF]} = dict:find(Key, RecDict), + {ok, {_FileLine, [{_Arity, _Fields} = AF]}} = dict:find(Key, RecDict), io:format("~w: ~w\n\n", [Key, AF]), print_types1(T, RecDict). -define(debug(D_), print_types(D_)). @@ -203,52 +203,53 @@ get_record_and_type_info(AbstractCode) -> {'ok', dict:dict()} | {'error', string()}. get_record_and_type_info(AbstractCode, Module, RecDict) -> - get_record_and_type_info(AbstractCode, Module, [], RecDict). + get_record_and_type_info(AbstractCode, Module, RecDict, "nofile"). -get_record_and_type_info([{attribute, _, record, {Name, Fields0}}|Left], - Module, Records, RecDict) -> +get_record_and_type_info([{attribute, A, record, {Name, Fields0}}|Left], + Module, RecDict, File) -> {ok, Fields} = get_record_fields(Fields0, RecDict), Arity = length(Fields), - NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict), - get_record_and_type_info(Left, Module, [{record, Name}|Records], NewRecDict); -get_record_and_type_info([{attribute, _, type, {{record, Name}, Fields0, []}} - |Left], Module, Records, RecDict) -> + FN = {File, erl_anno:line(A)}, + NewRecDict = dict:store({record, Name}, {FN, [{Arity,Fields}]}, RecDict), + get_record_and_type_info(Left, Module, NewRecDict, File); +get_record_and_type_info([{attribute, A, type, {{record, Name}, Fields0, []}} + |Left], Module, RecDict, File) -> %% This overrides the original record declaration. {ok, Fields} = get_record_fields(Fields0, RecDict), Arity = length(Fields), - NewRecDict = dict:store({record, Name}, [{Arity, Fields}], RecDict), - get_record_and_type_info(Left, Module, Records, NewRecDict); -get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm}}|Left], - Module, Records, RecDict) when Attr =:= 'type'; - Attr =:= 'opaque' -> - try add_new_type(Attr, Name, TypeForm, [], Module, RecDict) of + FN = {File, erl_anno:line(A)}, + NewRecDict = dict:store({record, Name}, {FN, [{Arity, Fields}]}, RecDict), + get_record_and_type_info(Left, Module, NewRecDict, File); +get_record_and_type_info([{attribute, A, Attr, {Name, TypeForm}}|Left], + Module, RecDict, File) + when Attr =:= 'type'; Attr =:= 'opaque' -> + FN = {File, erl_anno:line(A)}, + try add_new_type(Attr, Name, TypeForm, [], Module, FN, RecDict) of NewRecDict -> - get_record_and_type_info(Left, Module, Records, NewRecDict) + get_record_and_type_info(Left, Module, NewRecDict, File) catch throw:{error, _} = Error -> Error end; -get_record_and_type_info([{attribute, _, Attr, {Name, TypeForm, Args}}|Left], - Module, Records, RecDict) when Attr =:= 'type'; - Attr =:= 'opaque' -> - try add_new_type(Attr, Name, TypeForm, Args, Module, RecDict) of +get_record_and_type_info([{attribute, A, Attr, {Name, TypeForm, Args}}|Left], + Module, RecDict, File) + when Attr =:= 'type'; Attr =:= 'opaque' -> + FN = {File, erl_anno:line(A)}, + try add_new_type(Attr, Name, TypeForm, Args, Module, FN, RecDict) of NewRecDict -> - get_record_and_type_info(Left, Module, Records, NewRecDict) + get_record_and_type_info(Left, Module, NewRecDict, File) catch throw:{error, _} = Error -> Error end; -get_record_and_type_info([_Other|Left], Module, Records, RecDict) -> - get_record_and_type_info(Left, Module, Records, RecDict); -get_record_and_type_info([], _Module, Records, RecDict) -> - case - check_type_of_record_fields(lists:reverse(Records), RecDict) - of - ok -> - {ok, RecDict}; - {error, Name, Error} -> - {error, flat_format(" Error while parsing #~w{}: ~s\n", [Name, Error])} - end. - -add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> +get_record_and_type_info([{attribute, _, file, {IncludeFile, _}}|Left], + Module, RecDict, _File) -> + get_record_and_type_info(Left, Module, RecDict, IncludeFile); +get_record_and_type_info([_Other|Left], Module, RecDict, File) -> + get_record_and_type_info(Left, Module, RecDict, File); +get_record_and_type_info([], _Module, RecDict, _File) -> + {ok, RecDict}. + +add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, FN, + RecDict) -> Arity = length(ArgForms), case erl_types:type_is_defined(TypeOrOpaque, Name, Arity, RecDict) of true -> @@ -258,7 +259,7 @@ add_new_type(TypeOrOpaque, Name, TypeForm, ArgForms, Module, RecDict) -> try erl_types:t_var_names(ArgForms) of ArgNames -> dict:store({TypeOrOpaque, Name, Arity}, - {{Module, TypeForm, ArgNames}, + {{Module, FN, TypeForm, ArgNames}, erl_types:t_any()}, RecDict) catch _:_ -> @@ -280,32 +281,16 @@ get_record_fields([{typed_record_field, OrdRecField, TypeForm}|Left], end, get_record_fields(Left, RecDict, [{Name, TypeForm}|Acc]); get_record_fields([{record_field, _Line, Name}|Left], RecDict, Acc) -> - NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|Acc], + A = erl_anno:set_generated(true, erl_anno:new(1)), + NewAcc = [{erl_parse:normalise(Name), {var, A, '_'}}|Acc], get_record_fields(Left, RecDict, NewAcc); get_record_fields([{record_field, _Line, Name, _Init}|Left], RecDict, Acc) -> - NewAcc = [{erl_parse:normalise(Name), {var, -1, '_'}}|Acc], + A = erl_anno:set_generated(true, erl_anno:new(1)), + NewAcc = [{erl_parse:normalise(Name), {var, A, '_'}}|Acc], get_record_fields(Left, RecDict, NewAcc); get_record_fields([], _RecDict, Acc) -> lists:reverse(Acc). -%% Just check the local types. process_record_remote_types will add -%% the types later. -check_type_of_record_fields([], _RecDict) -> - ok; -check_type_of_record_fields([RecKey|Recs], RecDict) -> - {ok, [{_Arity, Fields}]} = dict:find(RecKey, RecDict), - try - [erl_types:t_from_form_without_remote(FieldTypeForm, RecDict) - || {_FieldName, FieldTypeForm, _} <- Fields] - of - L when is_list(L) -> - check_type_of_record_fields(Recs, RecDict) - catch - throw:{error, Error} -> - {record, Name} = RecKey, - {error, Name, Error} - end. - -spec process_record_remote_types(codeserver()) -> codeserver(). %% The field types are cached. Used during analysis when handling records. @@ -327,9 +312,10 @@ process_record_remote_types(CServer) -> TempRecords)} || {Name, Field, _} <- Fields] end, - orddict:map(FieldFun, Value); + {FileLine, Fields} = Value, + {FileLine, orddict:map(FieldFun, Fields)}; {opaque, _, _} -> - {{_Module, Form, _ArgNames}=F, _Type} = Value, + {{_Module, _FileLine, Form, _ArgNames}=F, _Type} = Value, Type = erl_types:t_from_form(Form, TempExpTypes, Module, TempRecords), {F, Type}; @@ -340,13 +326,53 @@ process_record_remote_types(CServer) -> end, try dict:map(ModuleFun, TempRecords) of NewRecords -> + ok = check_record_fields(NewRecords, TempExpTypes), CServer1 = dialyzer_codeserver:finalize_records(NewRecords, CServer), dialyzer_codeserver:finalize_exported_types(TempExpTypes, CServer1) catch - throw:{error, _RecName, _Error} = Error-> + throw:{error, _RecName, _Error} = Error -> Error end. +check_record_fields(Records, TempExpTypes) -> + CheckFun = + fun({Module, Element}) -> + CheckForm = fun(F) -> + erl_types:t_check_record_fields(F, TempExpTypes, + Module, Records) + end, + ElemFun = + fun({Key, Value}) -> + case Key of + {record, _Name} -> + FieldFun = + fun({_Arity, Fields}) -> + _ = [ok = CheckForm(Field) || {_, Field, _} <- Fields], + ok + end, + {FileLine, Fields} = Value, + Fun = fun() -> lists:foreach(FieldFun, Fields) end, + msg_with_position(Fun, FileLine); + {_OpaqueOrType, _Name, _} -> + {{_Module, FileLine, Form, _ArgNames}, _Type} = Value, + Fun = fun() -> ok = CheckForm(Form) end, + msg_with_position(Fun, FileLine) + end + end, + lists:foreach(ElemFun, dict:to_list(Element)) + end, + lists:foreach(CheckFun, dict:to_list(Records)). + +msg_with_position(Fun, FileLine) -> + try Fun() + catch + throw:{error, Msg} -> + {File, Line} = FileLine, + BaseName = filename:basename(File), + NewMsg = io_lib:format("~s:~p: ~s", [BaseName, Line, Msg]), + throw({error, NewMsg}) + end. + -spec merge_records(dict:dict(), dict:dict()) -> dict:dict(). merge_records(NewRecords, OldRecords) -> @@ -385,11 +411,11 @@ get_optional_callbacks(Abs) -> %% - Constraint is of the form {subtype, T1, T2} where T1 and T2 %% are erl_types:erl_type() -get_spec_info([{attribute, Attr, Contract, {Id, TypeSpec}}|Left], +get_spec_info([{attribute, Anno, Contract, {Id, TypeSpec}}|Left], SpecDict, CallbackDict, RecordsDict, ModName, OptCb, File) when ((Contract =:= 'spec') or (Contract =:= 'callback')), is_list(TypeSpec) -> - Ln = erl_anno:line(Attr), + Ln = erl_anno:line(Anno), MFA = case Id of {_, _, _} = T -> T; {F, A} -> {ModName, F, A} |