diff options
author | Stavros Aronis <[email protected]> | 2016-06-08 13:13:04 +0200 |
---|---|---|
committer | Stavros Aronis <[email protected]> | 2016-06-08 13:13:04 +0200 |
commit | b13412d2e4331fa426a6722a04310cbcbf1731d6 (patch) | |
tree | 17fba1d7099d652eeab812b4b1a72e44cc9277bd /lib/dialyzer/src | |
parent | d26c15e07229c90ba8353bd78d5406ada0f13271 (diff) | |
download | otp-b13412d2e4331fa426a6722a04310cbcbf1731d6.tar.gz otp-b13412d2e4331fa426a6722a04310cbcbf1731d6.tar.bz2 otp-b13412d2e4331fa426a6722a04310cbcbf1731d6.zip |
Fix a bug in Dialyzer related to call-site analysis
Dialyzer's "dataflow" module is using information from the callgraph to
determine which functions may be called at a particular call-site. Unfortunately
this information can include functions that are certainly not among the possible
choices. We don't want to emit warnings in such cases, so a "reasonable"
compromise is to stay silent if there are many possible funs and at least one of
them can succeed.
Bug reported by Dan Gudmundsson, test shrunk down by Magnus Lång.
Diffstat (limited to 'lib/dialyzer/src')
-rw-r--r-- | lib/dialyzer/src/dialyzer_dataflow.erl | 58 |
1 files changed, 50 insertions, 8 deletions
diff --git a/lib/dialyzer/src/dialyzer_dataflow.erl b/lib/dialyzer/src/dialyzer_dataflow.erl index 5ab0c39c04..3349b12932 100644 --- a/lib/dialyzer/src/dialyzer_dataflow.erl +++ b/lib/dialyzer/src/dialyzer_dataflow.erl @@ -430,17 +430,35 @@ handle_apply(Tree, Map, State) -> handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State) -> None = t_none(), + %% Call-site analysis may be inaccurate and consider more funs than those that + %% are actually possible. If all of them are incorrect, then warnings can be + %% emitted. If at least one fun is ok, however, then no warning is emitted, + %% just in case the bad ones are not really possible. The last argument is + %% used for this, with the following encoding: + %% Initial value: {none, []} + %% First fun checked: {one, <List of warns>} + %% More funs checked: {many, <List of warns>} + %% A '{one, []}' can only become '{many, []}'. + %% If at any point an fun does not add warnings, then the list is also + %% replaced with an empty list. handle_apply_or_call(FunInfoList, Args, ArgTypes, Map, Tree, State, - [None || _ <- ArgTypes], None, false). + [None || _ <- ArgTypes], None, false, {none, []}). handle_apply_or_call([{local, external}|Left], Args, ArgTypes, Map, Tree, State, - _AccArgTypes, _AccRet, _HadExternal) -> + _AccArgTypes, _AccRet, _HadExternal, Warns) -> + {HowMany, _} = Warns, + NewHowMany = + case HowMany of + none -> one; + _ -> many + end, + NewWarns = {NewHowMany, []}, handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, State, - ArgTypes, t_any(), true); + ArgTypes, t_any(), true, NewWarns); handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], Args, ArgTypes, Map, Tree, #state{opaques = Opaques} = State, - AccArgTypes, AccRet, HadExternal) -> + AccArgTypes, AccRet, HadExternal, Warns) -> Any = t_any(), AnyArgs = [Any || _ <- Args], GenSig = {AnyArgs, fun(_) -> t_any() end}, @@ -586,16 +604,32 @@ handle_apply_or_call([{TypeOfApply, {Fun, Sig, Contr, LocalRet}}|Left], end, NewAccRet = t_sup(AccRet, TotalRet), ?debug("NewAccRet: ~s\n", [t_to_string(NewAccRet)]), + {NewWarnings, State4} = state__remove_added_warnings(State, State3), + {HowMany, OldWarnings} = Warns, + NewWarns = + case HowMany of + none -> {one, NewWarnings}; + _ -> + case OldWarnings =:= [] of + true -> {many, []}; + false -> + case NewWarnings =:= [] of + true -> {many, []}; + false -> {many, NewWarnings ++ OldWarnings} + end + end + end, handle_apply_or_call(Left, Args, ArgTypes, Map, Tree, - State3, NewAccArgTypes, NewAccRet, HadExternal); + State4, NewAccArgTypes, NewAccRet, HadExternal, NewWarns); handle_apply_or_call([], Args, _ArgTypes, Map, _Tree, State, - AccArgTypes, AccRet, HadExternal) -> + AccArgTypes, AccRet, HadExternal, {_, Warnings}) -> + State1 = state__add_warnings(Warnings, State), case HadExternal of false -> NewMap = enter_type_lists(Args, AccArgTypes, Map), - {State, NewMap, AccRet}; + {State1, NewMap, AccRet}; true -> - {had_external, State} + {had_external, State1} end. apply_fail_reason(FailedSig, FailedBif, FailedContract) -> @@ -3038,6 +3072,14 @@ state__add_warning(#state{warnings = Warnings, warning_mode = true} = State, end end. +state__remove_added_warnings(OldState, NewState) -> + #state{warnings = OldWarnings} = OldState, + #state{warnings = NewWarnings} = NewState, + {NewWarnings -- OldWarnings, NewState#state{warnings = OldWarnings}}. + +state__add_warnings(Warns, #state{warnings = Warnings} = State) -> + State#state{warnings = Warns ++ Warnings}. + -spec state__set_curr_fun(curr_fun(), state()) -> state(). state__set_curr_fun(undefined, State) -> |