Line number where exception was originally raised

Please excuse me if this is really basic, but is there a way to catch an exception and get the line where the exception was originally raised?

Yep:

iex> try do :atom / :atom rescue a -> {a, System.stacktrace()} end         
{
  %ArithmeticError{message: "bad argument in arithmetic expression"},
  [
    {:erlang, :/, [:atom, :atom], []},
    {:erl_eval, :do_apply, 6, [file: 'erl_eval.erl', line: 670]},
    {:erl_eval, :try_clauses, 8, [file: 'erl_eval.erl', line: 904]},
    {:elixir, :eval_forms, 4, [file: 'src/elixir.erl', line: 233]},
    {IEx.Evaluator, :handle_eval, 5, [file: 'lib/iex/evaluator.ex', line: 232]},
    {IEx.Evaluator, :do_eval, 3, [file: 'lib/iex/evaluator.ex', line: 213]},
    {IEx.Evaluator, :eval, 3, [file: 'lib/iex/evaluator.ex', line: 191]},
    {IEx.Evaluator, :loop, 1, [file: 'lib/iex/evaluator.ex', line: 89]}
  ]
}

System.stacktrace() gets the stacktrace of the current exception context. :slight_smile:

3 Likes

Thanks, I assume the stacktrace is local to the current process and as such is not subject yo race conditions, right?

As far as my understanding goes it gets it from the C side of the local process data, so correct there are no race conditions. :slight_smile:

EDIT: Do note, it may not exist if the BEAM files were not compiled with debug information (elixir does by default) and in some other cases. It is intended for debugging, not actual usage, so you could get missing stackframes and/or even an empty list.

It’s not critical if it comes with missing debug information. Everything I want will work, even if not as well. My goal here is the following:

In the very long term, I’d like to work on a property testing library similar to stream_data. When shrinking the data, I don’t want one kind of error to turn into another one. I want to keep track of which exception was raised by which values. One way of tracking this is to compare the exception module. But if I’m using assert in two places, I don’t want to mix the assertion errors from those two places.

So I have 3 options:

  1. Store the exc.expr of the exception (only works for assertion errors)
  2. Store exc.__struct__ plus the line number of the exception
  3. Store both 1 and 2

Until now, I didn’t know if 2 and 3 would be possible.

This way I can make sure that I find the simplest example that triggers that particular exception as well as the simplest example that triggere any exception.

Also under some circumstances a function might not be present in the stacktrace at all because of tail-call-optimisation!

iex(1)> defmodule M do
...(1)>   def foo(), do: bar()
...(1)>   def bar(), do: 1 / 0
...(1)> end
warning: this expression will fail with ArithmeticError
  iex:3

{:module, M,
 <<...>>, {:bar, 0}}
iex(2)> M.bar
** (ArithmeticError) bad argument in arithmetic expression
    iex:3: M.bar/0
iex(2)> M.foo
** (ArithmeticError) bad argument in arithmetic expression
    iex:3: M.bar/0

Not an issue in this case as the raise/throwing point will always be at the top of the stacktrace if it has debug information, regardless of TCO.