From 888155ee081590660f0a40f1a87f32ca077683ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Wed, 24 Jul 2013 14:52:50 +0200 Subject: [PATCH] Do not re-use stacktraces on IEx, closes #1486 This commit also cleans up error normalization throughout Elixir. --- lib/elixir/lib/exception.ex | 9 +++++++ lib/elixir/lib/kernel/cli.ex | 30 +++++++++-------------- lib/elixir/src/elixir_errors.erl | 8 +++++- lib/elixir/test/elixir/exception_test.exs | 9 +++++++ lib/ex_unit/lib/ex_unit/runner.ex | 20 +++------------ lib/iex/lib/iex/server.ex | 30 +++++++++++++---------- 6 files changed, 58 insertions(+), 48 deletions(-) diff --git a/lib/elixir/lib/exception.ex b/lib/elixir/lib/exception.ex index 76042758d0..57e2f1c9f0 100644 --- a/lib/elixir/lib/exception.ex +++ b/lib/elixir/lib/exception.ex @@ -100,6 +100,15 @@ defmodule Exception do exceptions and stacktraces. """ + @doc """ + Normalizes an exception converting Erlang exceptions + to Elixir exceptions. It takes the kind spilled by + catch as argument as a convenience for converting only + :errors, ignorning the others. + """ + def normalize(:error, exception), do: normalize(exception) + def normalize(_kind, other), do: other + @doc """ Normalizes an exception converting Erlang exceptions to Elixir exceptions. Useful when interfacing Erlang diff --git a/lib/elixir/lib/kernel/cli.ex b/lib/elixir/lib/kernel/cli.ex index f2ecb2c9de..bab26b71a8 100644 --- a/lib/elixir/lib/kernel/cli.ex +++ b/lib/elixir/lib/kernel/cli.ex @@ -40,13 +40,6 @@ defmodule Kernel.CLI do at_exit(0) System.halt(0) end - rescue - exception -> - at_exit(1) - trace = System.stacktrace - IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}" - IO.puts :stderr, format_stacktrace(trace) - System.halt(1) catch :exit, reason when is_integer(reason) -> at_exit(reason) @@ -56,9 +49,7 @@ defmodule Kernel.CLI do System.halt(0) kind, reason -> at_exit(1) - trace = System.stacktrace - IO.puts :stderr, "** (#{kind}) #{inspect(reason)}" - IO.puts :stderr, format_stacktrace(trace) + print_error(kind, Exception.normalize(kind, reason), System.stacktrace) System.halt(1) end end @@ -71,16 +62,9 @@ defmodule Kernel.CLI do lc hook inlist hooks do try do hook.(status) - rescue - exception -> - trace = System.stacktrace - IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}" - IO.puts :stderr, format_stacktrace(trace) catch kind, reason -> - trace = System.stacktrace - IO.puts :stderr, "** #{kind} #{inspect(reason)}" - IO.puts :stderr, format_stacktrace(trace) + print_error(kind, Exception.normalize(kind, reason), System.stacktrace) end end @@ -99,6 +83,16 @@ defmodule Kernel.CLI do end end + defp print_error(:error, exception, trace) do + IO.puts :stderr, "** (#{inspect exception.__record__(:name)}) #{exception.message}" + IO.puts :stderr, format_stacktrace(trace) + end + + defp print_error(kind, reason, trace) do + IO.puts :stderr, "** #{kind} #{inspect(reason)}" + IO.puts :stderr, format_stacktrace(trace) + end + defp format_stacktrace(stack) do Exception.format_stacktrace(prune_stacktrace(stack)) end diff --git a/lib/elixir/src/elixir_errors.erl b/lib/elixir/src/elixir_errors.erl index 8be2dd9a15..af35b1fc7a 100644 --- a/lib/elixir/src/elixir_errors.erl +++ b/lib/elixir/src/elixir_errors.erl @@ -193,9 +193,15 @@ raise(none, File, Kind, Message) -> raise(0, File, Kind, Message); raise(Line, File, Kind, Message) when is_integer(Line) -> + %% Populate the stacktrace so we can raise it + try + throw(ok) + catch + ok -> ok + end, Stacktrace = erlang:get_stacktrace(), Exception = Kind:new([{description, Message}, {file, iolist_to_binary(File)}, {line, Line}]), - erlang:raise(error, Exception, Stacktrace). + erlang:raise(error, Exception, tl(Stacktrace)). file_format(0, File, Message) -> io_lib:format("~ts: ~ts~n", [File, Message]); diff --git a/lib/elixir/test/elixir/exception_test.exs b/lib/elixir/test/elixir/exception_test.exs index 0d8a07b9aa..08577069ae 100644 --- a/lib/elixir/test/elixir/exception_test.exs +++ b/lib/elixir/test/elixir/exception_test.exs @@ -10,6 +10,15 @@ defmodule Kernel.ExceptionTest do refute is_exception(a_list) end + test :normalize do + assert is_record Exception.normalize(:badarg), ArgumentError + assert is_record Exception.normalize(ArgumentError[]), ArgumentError + + assert Exception.normalize(:throw, :badarg) == :badarg + assert is_record Exception.normalize(:error, :badarg), ArgumentError + assert is_record Exception.normalize(:error, ArgumentError[]), ArgumentError + end + test :format_stacktrace_entry_with_no_file_or_line do assert Exception.format_stacktrace_entry({Foo, :bar, [1, 2, 3], []}) == "Foo.bar(1, 2, 3)" assert Exception.format_stacktrace_entry({Foo, :bar, [], []}) == "Foo.bar()" diff --git a/lib/ex_unit/lib/ex_unit/runner.ex b/lib/ex_unit/lib/ex_unit/runner.ex index 26e1b2636c..38475fe3f9 100644 --- a/lib/ex_unit/lib/ex_unit/runner.ex +++ b/lib/ex_unit/lib/ex_unit/runner.ex @@ -86,12 +86,9 @@ defmodule ExUnit.Runner do { test_case, context } = try do { :ok, context } = case_name.__ex_unit__(:setup_all, [case: test_case]) { test_case, context } - rescue - error -> - { test_case.failure({ :error, error, filtered_stacktrace }), nil } catch kind, error -> - { test_case.failure({ kind, error, filtered_stacktrace }), nil } + { test_case.failure({ kind, Exception.normalize(kind, error), filtered_stacktrace }), nil } end tests = tests_for(case_name) @@ -105,12 +102,9 @@ defmodule ExUnit.Runner do test_case = try do case_name.__ex_unit__(:teardown_all, context) test_case - rescue - error -> - test_case.failure { :error, error, filtered_stacktrace } catch kind, error -> - test_case.failure { kind, error, filtered_stacktrace } + test_case.failure { kind, Exception.normalize(kind, error), filtered_stacktrace } end self_pid <- { self, :case_finished, test_case, [] } @@ -143,22 +137,16 @@ defmodule ExUnit.Runner do test = try do apply case_name, test.name, [context] test - rescue - error1 -> - test.failure { :error, error1, filtered_stacktrace } catch kind1, error1 -> - test.failure { kind1, error1, filtered_stacktrace } + test.failure { kind1, Exception.normalize(kind1, error1), filtered_stacktrace } end case_name.__ex_unit__(:teardown, Keyword.put(context, :test, test)) test - rescue - error2 -> - test.failure { :error, error2, filtered_stacktrace } catch kind2, error2 -> - test.failure { kind2, error2, filtered_stacktrace } + test.failure { kind2, Exception.normalize(kind2, error2), filtered_stacktrace } end end) diff --git a/lib/iex/lib/iex/server.ex b/lib/iex/lib/iex/server.ex index ffe5849ff6..5ddd33449f 100644 --- a/lib/iex/lib/iex/server.ex +++ b/lib/iex/lib/iex/server.ex @@ -27,6 +27,7 @@ defmodule IEx.Server do old_flag = Process.flag(:trap_exit, true) self_pid = self pid = spawn_link(fn -> input_loop(self_pid) end) + try do do_loop(config.input_pid(pid)) after @@ -50,13 +51,9 @@ defmodule IEx.Server do counter = config.counter code = config.cache eval(code, line, counter, config) - rescue - exception -> - print_exception(exception, System.stacktrace) - config.cache('') catch kind, error -> - print_error(kind, error, System.stacktrace) + print_error(kind, Exception.normalize(kind, error), System.stacktrace) config.cache('') end @@ -96,6 +93,7 @@ defmodule IEx.Server do # https://github.com/elixir-lang/elixir/issues/1089 for discussion. # @break_trigger '#iex:break\n' + defp eval(_, @break_trigger, _, config=IEx.Config[cache: '']) do config end @@ -106,6 +104,7 @@ defmodule IEx.Server do defp eval(code_so_far, latest_input, line_no, config) do code = code_so_far ++ latest_input + case :elixir_translator.forms(code, line_no, "iex", []) do { :ok, forms } -> { result, new_binding, scope } = @@ -137,12 +136,14 @@ defmodule IEx.Server do else Enum.map [".iex", "~/.iex"], Path.expand(&1) end + path = Enum.find candidates, fn path -> File.regular?(path) end + if nil?(path) do config else try do - code = File.read!(path) + code = File.read!(path) scope = :elixir.scope_for_eval(config.scope, file: path) # Evaluate the contents in the same environment do_loop will run in @@ -154,13 +155,9 @@ defmodule IEx.Server do scope = :elixir.scope_for_eval(scope, file: "iex") config.binding(binding).scope(scope) - rescue - exception -> - print_exception(exception, System.stacktrace) - System.halt(1) catch kind, error -> - print_error(kind, error, System.stacktrace) + print_error(kind, Exception.normalize(kind, error), System.stacktrace) System.halt(1) end end @@ -215,7 +212,7 @@ defmodule IEx.Server do if node == node(:erlang.group_leader), do: "iex", else: "rem" end - defp print_exception(exception, stacktrace) do + defp print_error(:error, exception, stacktrace) do print_stacktrace stacktrace, fn -> "** (#{inspect exception.__record__(:name)}) #{exception.message}" end @@ -234,10 +231,17 @@ defmodule IEx.Server do defp print_stacktrace(trace, callback) do try do io_error callback.() - io_error Exception.format_stacktrace(trace) + case prune_stacktrace(trace) do + [] -> :ok + other -> io_error Exception.format_stacktrace(other) + end catch _, _ -> io_error "** (IEx.Error) error when printing exception message and stacktrace" end end + + defp prune_stacktrace([{ IEx.Server, _, _, _ }|t]), do: prune_stacktrace(t) + defp prune_stacktrace([h|t]), do: [h|prune_stacktrace(t)] + defp prune_stacktrace([]), do: [] end