Closed liamwhite closed 3 years ago
Dialyxir does not produce warnings; that warning is coming from Dialyzer.
@jeremyjh Any ideas why Dialyzer (not Dialyxir) is producing that warning? I ran into it as well and it doesn't make sense given the types:
...
-type secret() :: binary().
-type token() :: binary().
-type token_option() :: {token_length, pos_integer()}.
-type time_interval_option() :: {addwindow, integer()} |
{interval_length, pos_integer()} |
{timestamp, erlang:timestamp()}.
-type hotp_option() :: token_option() |
{digest_method, atom()}.
-type totp_option() :: hotp_option() | time_interval_option().
...
-type valid_totp_option() :: totp_option() |
{window, non_neg_integer()}.
...
-type valid_totp_options() :: [valid_totp_option()].
...
From my local copy of the relevant file that's the same as the current version in the upstream GitHub repo:
It seems like Dialyzer expects the options to always or only contain {:token_length, pos_integer()}
.
Adding the :token_length
option to the :pot.valid_totp/3
call resolves the warning:
:pot.valid_totp(token, secret, token_length: 6, window: 1)
Weird!
@jeremyjh Is it sensible to report this upstream to Erlang/OTP?
@kenny-evitt only if you can reproduce the problem in pure Erlang and can determine it isn't a problem in pot. If the types are correct the problem may be the implementation in pot, is the success typing of valid_totp
actually the same as its spec ? (e.g. does dialyzer pass on pot
itself )? If all the answers are yes then you'd want to develop the simplest example that can reproduce the problem and report it on Erlang bugs. Usually the bug is in the library, not in Erlang but we've seen it happen.
@jeremyjh Dialyzer passes on pot
itself.
You didn't mention it, and I imagine it's rare too, but what would point to a problem with Elixir with respect to Dialyzer?
@jeremyjh I was able to reproduce the warning with a minimal Elixir (Mix) project:
The relevant (and only) code:
defmodule PotDialyzerWarningRepro do
@moduledoc """
Documentation for `PotDialyzerWarningRepro`.
"""
def totp_valid?(token, secret) do
:pot.valid_totp(token, secret, [{:window, 1}])
end
end
I wasn't able to reproduce the same warning (or any warnings) with what seems to me like the functionally equivalent Erlang code in a separate minimal Erlang (Rebar3) project:
The relevant (and only) code:
-module(pot_dialyzer_warning_repro_erl).
-export([is_totp_valid/2]).
is_totp_valid(Token, Secret) ->
pot:valid_totp(Token, Secret, [{window, 1}]).
Any ideas on what to look at or do next? Open an issue for pot
? Ask on the Elixir forum or mailing list?
@kenny-evitt The forums are the best place to get help on this, yes. A better minimal example would be one without pot
. That would clarify exactly where in the definition or implementation the issue lies. When you say you can't reproduce the same error with the Erlang code, are you running dialyzer the same way in both cases? Can you reproduce it against the compiled Elixir beams by just running the dialyzer CLI?
@jeremyjh Thanks!
I created a post on Elixir Forum for this (and some other warnings):
A better minimal example would be one without
pot
. That would clarify exactly where in the definition or implementation the issue lies.
I'm not sure I understand what you're suggesting. Copy the pot
code to the minimal repro repo? I'm a little confused because I'm not sure whether the error is because the pot
code is a dependency, nor do I know, or even suspect, what in particular is causing the problem.
Having written the preceding though, I do think I might understand your reasons (and wisdom) for offering that suggestion. I'll try to replace the pot
dependency in the repro code and test whether I can still repro the Dialyzer warnings.
When you say you can't reproduce the same error with the Erlang code, are you running dialyzer the same way in both cases? Can you reproduce it against the compiled Elixir beams by just running the dialyzer CLI?
No and I don't know yet how to do either of those things.
In the repro-Erlang code, I'm running rebar3 dialyzer
in the root directory of the repo. For the Elixir code, I'm using Dialyxir via mix dialyzer
.
How can I run Dialyzer the same way for both repro-repos?
Also, how can I run the Dialyzer CLI against the compiled Elixir BEAM files?
If those questions are 'too big' to easily answer, can you suggest search terms to hopefully point me in the right direction? I'll try to search anyways myself when I have some time.
How can I run Dialyzer the same way for both repro-repos?
@kenny-evitt You can run dialyzer from the command line like so, just substitute the proper plt file name and target path.
$ dialyzer --plt _build/dev/dialyxir_erlang-23.1.1_elixir-1.10.1_deps-dev.plt _build/dev/lib/diatarget/ebin/*.beam
@jeremyjh Thanks!
I suspect I'm doing something wrong. Here's what I tried for both of the minimal-repro-repos.
I ran dialyzer --plt _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt _build/dev/lib/dialyxir/ebin/*.beam
for the Elixir repo:
✔ ~/@code/pot_dialyzer_warning_repro [master L|✔]
(ins)12:01 $ dialyzer --plt _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt _build/dev/lib/dialyxir/ebin/*.beam
Checking whether the PLT _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt is up-to-date... yes
Compiling some key modules to native code... done in 0m36.92s
Proceeding with analysis...=ERROR REPORT==== 20-Nov-2020::12:04:02.882576 ===
Error in process <0.3632.0> with exit value:
{undef,
[{elixir_erl,debug_info,
[core_v1,'Elixir.Dialyxir.Dialyzer',
...
``` ✔ ~/@code/pot_dialyzer_warning_repro [master L|✔] (ins)12:01 $ dialyzer --plt _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt _build/dev/lib/dialyxir/ebin/*.beam Checking whether the PLT _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt is up-to-date... yes Compiling some key modules to native code... done in 0m36.92s Proceeding with analysis...=ERROR REPORT==== 20-Nov-2020::12:04:02.882576 === Error in process <0.3632.0> with exit value: {undef, [{elixir_erl,debug_info, [core_v1,'Elixir.Dialyxir.Dialyzer', {elixir_v1, #{attributes => [],compile_opts => [], definitions => [{{warnings_msg,0}, defp, [{line,101}], [{[{line,101}], [],[], {{'.',[{line,101}],['Elixir.Dialyxir.Output',color]}, [{line,101}], [<<"done (warnings were emitted)">>,yellow]}}]}, {{success_msg,0}, defp, [{line,99}], [{[{line,99}], [],[], {{'.',[{line,99}],['Elixir.Dialyxir.Output',color]}, [{line,99}], [<<"done (passed successfully)">>,green]}}]}, {{dialyze,3}, def, [{defaults,2},{line,74}], [{[{line,74}], [{args,[{version,0},{line,74}],nil}, {runner,[{version,1},{line,74}],nil}, {filterer,[{version,2},{line,74}],nil}], [], {'case', [{line,75}], [{{'.',[{line,75}],[{runner,[{version,1},{line,75}],nil},run]}, [{line,75}], [{args,[{version,0},{line,75}],nil}, {filterer,[{version,2},{line,75}],nil}]}, [{do, [{'->', [{line,76}], [[{ok, {'{}', [{line,76}], [{time,[{version,3},{line,76}],nil}, [], {formatted_unnecessary_skips, [{version,4},{line,76}], nil}]}}], {'{}', [{line,77}], [ok,0, [{time,[{version,3},{line,77}],nil}, {formatted_unnecessary_skips, [{version,4},{line,77}], nil}, {success_msg,[{line,77}],[]}]]}]}, {'->', [{line,79}], [[{ok, {'{}', [{line,79}], [{time,[{version,5},{line,79}],nil}, {result,[{version,6},{line,79}],nil}, {formatted_unnecessary_skips, [{version,7},{line,79}], nil}]}}], {'__block__',[], [{'=', [{line,80}], [{warnings,[{version,9},{line,80}],nil}, {{'.',[{line,80}],['Elixir.Enum',map]}, [{line,80}], [{result,[{version,6},{line,80}],nil}, {fn, [{line,80}], [{'->', [{line,80}], [[{x1,[{version,8}],elixir_fn}], {{'.', [{line,80}], ['Elixir.Dialyxir.Output',color]}, [{line,80}], [{x1,[{version,8}],elixir_fn},red]}]}]}]}]}, {'{}', [{line,82}], [warn,2, {{'.',[{line,83}],[erlang,'++']}, [{line,83}], [[{time,[{version,5},{line,83}],nil}], {{'.',[{line,83}],[erlang,'++']}, [{line,83}], [{warnings,[{version,9},{line,83}],nil}, [{formatted_unnecessary_skips, [{version,7},{line,83}], nil}, {warnings_msg,[{line,83}],[]}]]}]}]}]}]}, {'->', [{line,85}], [[{warn, {'{}', [{line,85}], [{time,[{version,10},{line,85}],nil}, {result,[{version,11},{line,85}],nil}, {formatted_unnecessary_skips, [{version,12},{line,85}], nil}]}}], {'__block__',[], [{'=', [{line,86}], [{warnings,[{version,14},{line,86}],nil}, {{'.',[{line,86}],['Elixir.Enum',map]}, [{line,86}], [{result,[{version,11},{line,86}],nil}, {fn, [{line,86}], [{'->', [{line,86}], [[{x1,[{version,13}],elixir_fn}], {{'.', [{line,86}], ['Elixir.Dialyxir.Output',color]}, [{line,86}], [{x1,[{version,13}],elixir_fn},red]}]}]}]}]}, {'{}', [{line,88}], [warn,2, {{'.',[{line,89}],[erlang,'++']}, [{line,89}], [[{time,[{version,10},{line,89}],nil}], {{'.',[{line,89}],[erlang,'++']}, [{line,89}], [{warnings,[{version,14},{line,89}],nil}, [{formatted_unnecessary_skips, [{version,12},{line,89}], nil}, {warnings_msg,[{line,89}],[]}]]}]}]}]}]}, {'->', [{line,91}], [[{error, {{msg,[{version,15},{line,91}],nil}, {formatted_unnecessary_skips, [{version,16},{line,91}], nil}}}], {'{}', [{line,92}], [error,1, [{{'.',[{line,92}],['Elixir.Dialyxir.Output',color]}, [{line,92}], [{formatted_unnecessary_skips, [{version,16},{line,92}], nil}, red]}, {{'.',[{line,92}],['Elixir.Dialyxir.Output',color]}, [{line,92}], [{msg,[{version,15},{line,92}],nil},red]}]]}]}, {'->', [{line,94}], [[{error,{msg,[{version,17},{line,94}],nil}}], {'{}', [{line,95}], [error,1, [{{'.',[{line,95}],['Elixir.Dialyxir.Output',color]}, [{line,95}], [{msg,[{version,17},{line,95}],nil},red]}]]}]}]}]]}}]}, {{dialyze,2}, def, [{line,74}], [{[{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}, {x1,[{generated,true},{version,1}],elixir_def}], [], {super, [{super,{def,dialyze}},{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}, {x1,[{generated,true},{version,1}],elixir_def}, 'Elixir.Dialyxir.Project']}}]}, {{dialyze,1}, def, [{line,74}], [{[{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}], [], {super, [{super,{def,dialyze}},{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}, 'Elixir.Dialyxir.Dialyzer.Runner', 'Elixir.Dialyxir.Project']}}]}], deprecated => [], file => <<"/Users/kenny/@code/pot_dialyzer_warning_repro/deps/dialyxir/lib/dialyxir/dialyzer.ex">>, is_behaviour => false,line => 1,module => 'Elixir.Dialyxir.Dialyzer', relative_file => <<"lib/dialyxir/dialyzer.ex">>,unreachable => []}, []}, [no_copt,to_core,binary,return_errors,no_inline,strict_record_tests, strict_record_updates,dialyzer]], []}, {dialyzer_utils,get_core_from_beam,2, [{file,"dialyzer_utils.erl"},{line,116}]}]} dialyzer: Analysis failed with error: {undef, [{elixir_erl,debug_info, [core_v1,'Elixir.Dialyxir.Dialyzer', {elixir_v1, #{attributes => [],compile_opts => [], definitions => [{{warnings_msg,0}, defp, [{line,101}], [{[{line,101}], [],[], {{'.',[{line,101}],['Elixir.Dialyxir.Output',color]}, [{line,101}], [<<"done (warnings were emitted)">>,yellow]}}]}, {{success_msg,0}, defp, [{line,99}], [{[{line,99}], [],[], {{'.',[{line,99}],['Elixir.Dialyxir.Output',color]}, [{line,99}], [<<"done (passed successfully)">>,green]}}]}, {{dialyze,3}, def, [{defaults,2},{line,74}], [{[{line,74}], [{args,[{version,0},{line,74}],nil}, {runner,[{version,1},{line,74}],nil}, {filterer,[{version,2},{line,74}],nil}], [], {'case', [{line,75}], [{{'.',[{line,75}],[{runner,[{version,1},{line,75}],nil},run]}, [{line,75}], [{args,[{version,0},{line,75}],nil}, {filterer,[{version,2},{line,75}],nil}]}, [{do, [{'->', [{line,76}], [[{ok, {'{}', [{line,76}], [{time,[{version,3},{line,76}],nil}, [], {formatted_unnecessary_skips, [{version,4},{line,76}], nil}]}}], {'{}', [{line,77}], [ok,0, [{time,[{version,3},{line,77}],nil}, {formatted_unnecessary_skips, [{version,4},{line,77}], nil}, {success_msg,[{line,77}],[]}]]}]}, {'->', [{line,79}], [[{ok, {'{}', [{line,79}], [{time,[{version,5},{line,79}],nil}, {result,[{version,6},{line,79}],nil}, {formatted_unnecessary_skips, [{version,7},{line,79}], nil}]}}], {'__block__',[], [{'=', [{line,80}], [{warnings,[{version,9},{line,80}],nil}, {{'.',[{line,80}],['Elixir.Enum',map]}, [{line,80}], [{result,[{version,6},{line,80}],nil}, {fn, [{line,80}], [{'->', [{line,80}], [[{x1,[{version,8}],elixir_fn}], {{'.', [{line,80}], ['Elixir.Dialyxir.Output',color]}, [{line,80}], [{x1,[{version,8}],elixir_fn},red]}]}]}]}]}, {'{}', [{line,82}], [warn,2, {{'.',[{line,83}],[erlang,'++']}, [{line,83}], [[{time,[{version,5},{line,83}],nil}], {{'.',[{line,83}],[erlang,'++']}, [{line,83}], [{warnings,[{version,9},{line,83}],nil}, [{formatted_unnecessary_skips, [{version,7},{line,83}], nil}, {warnings_msg,[{line,83}],[]}]]}]}]}]}]}, {'->', [{line,85}], [[{warn, {'{}', [{line,85}], [{time,[{version,10},{line,85}],nil}, {result,[{version,11},{line,85}],nil}, {formatted_unnecessary_skips, [{version,12},{line,85}], nil}]}}], {'__block__',[], [{'=', [{line,86}], [{warnings,[{version,14},{line,86}],nil}, {{'.',[{line,86}],['Elixir.Enum',map]}, [{line,86}], [{result,[{version,11},{line,86}],nil}, {fn, [{line,86}], [{'->', [{line,86}], [[{x1,[{version,13}],elixir_fn}], {{'.', [{line,86}], ['Elixir.Dialyxir.Output',color]}, [{line,86}], [{x1,[{version,13}],elixir_fn},red]}]}]}]}]}, {'{}', [{line,88}], [warn,2, {{'.',[{line,89}],[erlang,'++']}, [{line,89}], [[{time,[{version,10},{line,89}],nil}], {{'.',[{line,89}],[erlang,'++']}, [{line,89}], [{warnings,[{version,14},{line,89}],nil}, [{formatted_unnecessary_skips, [{version,12},{line,89}], nil}, {warnings_msg,[{line,89}],[]}]]}]}]}]}]}, {'->', [{line,91}], [[{error, {{msg,[{version,15},{line,91}],nil}, {formatted_unnecessary_skips, [{version,16},{line,91}], nil}}}], {'{}', [{line,92}], [error,1, [{{'.',[{line,92}],['Elixir.Dialyxir.Output',color]}, [{line,92}], [{formatted_unnecessary_skips, [{version,16},{line,92}], nil}, red]}, {{'.',[{line,92}],['Elixir.Dialyxir.Output',color]}, [{line,92}], [{msg,[{version,15},{line,92}],nil},red]}]]}]}, {'->', [{line,94}], [[{error,{msg,[{version,17},{line,94}],nil}}], {'{}', [{line,95}], [error,1, [{{'.',[{line,95}],['Elixir.Dialyxir.Output',color]}, [{line,95}], [{msg,[{version,17},{line,95}],nil},red]}]]}]}]}]]}}]}, {{dialyze,2}, def, [{line,74}], [{[{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}, {x1,[{generated,true},{version,1}],elixir_def}], [], {super, [{super,{def,dialyze}},{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}, {x1,[{generated,true},{version,1}],elixir_def}, 'Elixir.Dialyxir.Project']}}]}, {{dialyze,1}, def, [{line,74}], [{[{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}], [], {super, [{super,{def,dialyze}},{line,74}], [{x0,[{generated,true},{version,0}],elixir_def}, 'Elixir.Dialyxir.Dialyzer.Runner', 'Elixir.Dialyxir.Project']}}]}], deprecated => [], file => <<"/Users/kenny/@code/pot_dialyzer_warning_repro/deps/dialyxir/lib/dialyxir/dialyzer.ex">>, is_behaviour => false,line => 1,module => 'Elixir.Dialyxir.Dialyzer', relative_file => <<"lib/dialyxir/dialyzer.ex">>,unreachable => []}, []}, [no_copt,to_core,binary,return_errors,no_inline,strict_record_tests, strict_record_updates,dialyzer]], []}, {dialyzer_utils,get_core_from_beam,2, [{file,"dialyzer_utils.erl"},{line,116}]}]} Last messages in the log cache: Reading files and computing callgraph... ```
For the Erlang repo, I ran dialyzer --plt _build/default/rebar3_21.2.3_plt _build/default/lib/pot_dialzer_warning_repor_erl/ebin/*.beam
:
✔ ~/@code/pot_dialyzer_warning_repro_erl [master L|✔]
(ins)12:08 $ dialyzer --plt _build/default/rebar3_21.2.3_plt _build/default/lib/pot_dialzer_warning_repor_erl/ebin/*.beam
Checking whether the PLT _build/default/rebar3_21.2.3_plt is up-to-date... yes
Proceeding with analysis...
Unknown functions:
pot:valid_totp/3
done in 0m0.15s
done (passed successfully)
I'm not sure what that output means for the Elixir project but you are running the analysis on Dialyxir's beams instead of your project's beams, if your project is named pot_dialyzer_warning_repro
then use that in the path:
dialyzer --plt _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt _build/dev/lib/pot_dialyzer_warning_repro/ebin/*.beam
The output for the Erlang example suggests pot
has not been added to that PLT, hence unknown function error.
@jeremyjh I finally tried your suggestion:
✔ ~/@code/pot_dialyzer_warning_repro [master L|✔]
(ins)15:14 $ dialyzer --plt _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt _build/dev/lib/pot_dialyzer_warning_repro/ebin/*.beam
Checking whether the PLT _build/dev/dialyxir_erlang-21.2.3_elixir-1.10.4_deps-dev.plt is up-to-date... yes
Proceeding with analysis...
dialyzer: Analysis failed with error:
{undef,
[{elixir_erl,debug_info,
[core_v1,'Elixir.PotDialyzerWarningRepro',
{elixir_v1,
#{attributes => [],compile_opts => [],
definitions =>
[{{'totp_valid?',2},
def,
[{line,6}],
[{[{line,6}],
[{token,[{version,0},{line,6}],nil},
{secret,[{version,1},{line,6}],nil}],
[],
{{'.',[{line,7}],[pot,valid_totp]},
[{line,7}],
[{token,[{version,0},{line,7}],nil},
{secret,[{version,1},{line,7}],nil},
[{window,1}]]}}]}],
deprecated => [],
file =>
<<"/Users/kenny/@code/pot_dialyzer_warning_repro/lib/pot_dialyzer_warning_repro.ex">>,
is_behaviour => false,line => 1,
module => 'Elixir.PotDialyzerWarningRepro',
relative_file => <<"lib/pot_dialyzer_warning_repro.ex">>,
unreachable => []},
[]},
[no_copt,to_core,binary,return_errors,no_inline,strict_record_tests,
strict_record_updates,dialyzer]],
[]},
{dialyzer_utils,get_core_from_beam,2,
[{file,"dialyzer_utils.erl"},{line,116}]},
{dialyzer_analysis_callgraph,compile_byte,5,
[{file,"dialyzer_analysis_callgraph.erl"},{line,416}]},
{dialyzer_worker,loop,2,[{file,"dialyzer_worker.erl"},{line,90}]}]}
...
I tried a new command for the Erlang version too and reproduced the same original error:
✔ ~/@code/pot_dialyzer_warning_repro_erl [master L|✔]
(ins)15:58 $ dialyzer --plt /Users/kenny/.cache/rebar3/rebar3_21.2.3_plt -c _build/default/lib/pot/ebin/*.beam _build/default/lib/pot_dialyzer_warning_repro_erl/ebin/*.beam
Checking whether the PLT /Users/kenny/.cache/rebar3/rebar3_21.2.3_plt is up-to-date... yes
Proceeding with analysis...
pot_dialyzer_warning_repro_erl.erl:5: Function is_totp_valid/2 has no local return
pot_dialyzer_warning_repro_erl.erl:6: The call pot:valid_totp(Token::any(),Secret::any(),[{'window',1},...]) will never return since it differs in the 3rd argument from the success typing arguments: (binary(),any(),[{'token_length',pos_integer()}])
done in 0m0.23s
done (warnings were emitted)
Environment
Current behavior
Given the following code which uses the pot OTP library:
Dialyxir generates a nonsense warning [1] [2] [3]:
This seems to be an issue with dialyxir, because running dialyzer on the tests in the pot repository, such as this code, does not produce any warnings except specdiffs.
Expected behavior
No warnings should be generated.