Dialyzer giving warnings that make no sense

Background

I have recently started trying out dialyxir. After playing around with a few pet projects I decided to up the game and try it into something more real.

Problem

However, dialyxir is giving me warnings that make no sense whatsoever. Thanks to the success algorithm dialyzer uses, I would expect it to not find every possible error which is fine, but giving me so many false positives ( which should be impossible ) is definitely something I was not expecting - it completely ruins the experience with noisy warnings that are useless.

Have a look on the following behaviour:

defmodule MyApp.Web.MetricsInstrumenter do
  @moduledoc """
  Adds HTTP related metrics for every request.
  See used Prometheus.PlugPipelineInstrumenter module for details.
  """
  use Prometheus.PlugPipelineInstrumenter
end

This created the following dialyxir warning:

lib/myapp/web/metrics_instrumenter.ex:6:guard_fail
Guard test:
tuple_size(_ :: Exception.t())

can never succeed.

This is mind blowing. This makes no sense whatsoever. There is literally only 1 line of code and that line doesn’t even have a when clause.

Why am I getting this error?

Questions

I keep thinking this is a configuration problem. Somehow, I am not configuring the app correctly in the mix.exs file while using Prometheus.

defmodule MyApp.MixProject do
  @moduledoc false
  use Mix.Project

  def project do
    [
      app: :myaap,
      version: "1.1.0",
      elixir: "~> 1.7",
      elixirc_paths: elixirc_paths(Mix.env()),
      start_permanent: Mix.env() == :prod,
      deps: deps()
    ]
  end

  def application do
    [
      extra_applications: [:logger],
      mod: {MyApp.Application, []}
    ]
  end

  defp deps do
    [
      { :plug,              "~> 1.0"  },
      { :prometheus_plugs,  "~> 1.1.5"  },
      { :prometheus_ex,     "~> 3.0"  },
      { :dialyxir,          "~> 1.0.0-rc.4",  only: [:dev],         runtime: false  }
    ]
  end

  defp elixirc_paths(:test), do: ["lib", "test/support"]
  defp elixirc_paths(:dev), do: ["lib", "test/support"]
  defp elixirc_paths(_), do: ["lib"]
end

But nothing I do works.
Why am I getting these non nonsensical errors?

Thats hard to tell from this perspective, but probably something in prometheus is faulty?

In general it makes totally sense that tuple_size/1 called with an exception as argument would not suceed. Problem is though, I can not find any reference to tuple_size/1 in either prometheus_plugs, nor prometheus_ex.

So this problem might be burried even deeper.


edit

Can’t even find tuple_size it in the underlying erlang library. I’m trying to dig into this locally. Dialyzer is building its PLT right now…

3 Likes

@NobbZ Just to make it clear, I truly appreciate all the help you are giving. Without you this forum wouldn’t be what it is today.

Thank you.


On a separate note, if this is an issue with Prometheus, what are the options?

  1. make a PR to fix it?
  2. ask dialyzer to ignore prometheus_ex ? ( not sure if even possible )
2 Likes

If it is, you can configure dialyzer to ignore it. In dialyxirs README there should be a section about ignoring.

I’d report it upstream anyway.

But before reporting anything, we have to wait a bit. Dialyzer hasn’t found any issues in prometheus_ex itself, so now I have tried to create an example project from what you gave us so far and I am running dialyxir on it. This means building PLTs again.

Sorry, but with the information you have provided so far I am unable to reproduce the issue.

Can you create a minimal project that shows the problem and publish it on github or somewhere else?

Done:

https://github.com/Fl4m3Ph03n1x/dialyxir-problem

I got the same class of errors as described. They’re everywhere.

On my current machine (elixir 1.6/OTP20) there are no issues. I have currently not the resources available to update to 1.7/21 here, but I will try again when I’m back at home, where my setup is more up to date.

Spoiler warning… I sshd into my system at home which runs 1.7/21. It triggers the same errors as you report. Ill have to play a bit with versions to learn more…

2 Likes

I just asked for a friend to test in his machine using 1.7.3-otp-21 and he gets the same issues. On a clean machine with a fresh 1.7.3-otp-21 install.

I will wait for you to get home. If you verify there is in fact something odd, then I believe we should create an issue and report it. Where to report it? Not sure. Not sure if this is an issue with dialyxir or prometheus, though I am leaning towards the 1st one because it seems to work fine with older versions.

This seems to happen in the expansion of Prometheus.Error.with_prometheus_error/1, as the same messages appear when I run dialyzer in the prometheus_ex code base at places where that macro is used:

lib/prometheus/format/protobuf.ex:13: Guard test tuple_size(__@5::#{'__exception__':='true', '__struct__' :=_, _=>_}) can never succeed
lib/prometheus/format/protobuf.ex:21: Guard test tuple_size(__@5::#{'__exception__':='true', '__struct__' :=_, _=>_}) can never succeed
lib/prometheus/format/text.ex:26: Guard test tuple_size(__@5::#{'__exception__':='true', '__struct__':=_, _=>_}) can never succeed
lib/prometheus/format/text.ex:34: Guard test tuple_size(__@5::#{'__exception__':='true', '__struct__':=_, _=>_}) can never succeed  

The output is from dialyxir ~> 0.5.0, as that is the specified dependency of prometheus_ex.

As in that macro some testing about the availability of __STACKTRACE__ is done, I have to assume this is due changes in OTP 21 (or has it been in 20?) and exception handling. This is definitifely something that needs to be reported at prometheus.ex.

1 Like

Its even worse and I will prepare a bug report to elixir, as I can reproduce the issue with the following minimal module:

defmodule Foobar do
  def hello do
    try do
      IO.puts("foo")
    rescue
      e in ErlangError ->
        :error
    end
  end
end
1 Like

Its already reported and closed:

https://github.com/elixir-lang/elixir/issues/8157

I’m not sure though, if the fix is in the 1.7 branch or only for 1.8.

4 Likes

I also had encountered this problem when working with Prometheus library and the problem indeed was in Elixir itself, however I believe it was released as a fix in one of 1.7.x versions.

I however fixed the problem twofold:

  1. Firstly I fixed prometheus_ex library that was needlessly using a lot of macros for no reason.
  2. Ditched prometheus_ex at all and replaced it with plain prometheus library (Erlang one) as this one has IMHO much cleaner interface.
1 Like

@NobbZ Thanks for all the help really!
Didn’t think this path would take us to an elixir bug, I am fairly surprised!

It will be interesting to see when they fix this. So for now I guess I can’t use elixir 1.7.3-otp-21. Which version would work?

Is there a place where I can check all elixir 1.7.X-otp-Y builds available? I assume I now have to try with several and see which one works with the project.


@hauleth I am always amazed when I see someone make PR with code to such a big project and get it accepted. I usually only do documentation because my time is not enough to understand a library deeply enough in order to make those kinds of PRs.

Still, I am surprised to see you completely ditched prometeus_ex. Unfortunately this is not something I can do in the project I have. First, because it is a company project and I can’t do whatever I want. Second, because I actually have hopes that the bug will be fixed in the future and I will be able to use diallyzer.

Not Ideal, I know, but for now finding an elixir/otp compatible build is my best option, the way I see it.