summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Wilson <benwilson512@gmail.com>2017-05-23 07:00:44 -0400
committerJosé Valim <jose.valim@gmail.com>2017-05-23 13:00:44 +0200
commitd0af4d7909f2ff6c9fb0ac9aedac61d1cea632ec (patch)
tree2a7d3e33a8307867762bcb1b919002f4f98ebf27
parentad167e2e955af8c7abfd42c2388013b9eb682e77 (diff)
downloadelixir-d0af4d7909f2ff6c9fb0ac9aedac61d1cea632ec.tar.gz
Provide inspect opts printable_limit (#5822)
It avoids performance issues found when inspecting large printable strings
-rw-r--r--lib/elixir/lib/inspect.ex5
-rw-r--r--lib/elixir/lib/inspect/algebra.ex5
-rw-r--r--lib/elixir/lib/string.ex44
-rw-r--r--lib/elixir/test/elixir/inspect_test.exs9
-rw-r--r--lib/iex/test/iex/autocomplete_test.exs4
5 files changed, 46 insertions, 21 deletions
diff --git a/lib/elixir/lib/inspect.ex b/lib/elixir/lib/inspect.ex
index e661874a8..46a9b6ffe 100644
--- a/lib/elixir/lib/inspect.ex
+++ b/lib/elixir/lib/inspect.ex
@@ -99,8 +99,9 @@ defimpl Inspect, for: Atom do
end
defimpl Inspect, for: BitString do
- def inspect(term, %Inspect.Opts{binaries: bins, base: base} = opts) when is_binary(term) do
- if base == :decimal and (bins == :as_strings or (bins == :infer and String.printable?(term))) do
+ def inspect(term, %Inspect.Opts{binaries: bins, base: base, printable_limit: printable_limit} = opts) when is_binary(term) do
+ if base == :decimal and (bins == :as_strings or (bins == :infer and String.printable?(term, printable_limit))) do
+ term = String.slice(term, 0..printable_limit)
inspected = IO.iodata_to_binary([?", escape(term, ?"), ?"])
color(inspected, :string, opts)
else
diff --git a/lib/elixir/lib/inspect/algebra.ex b/lib/elixir/lib/inspect/algebra.ex
index 46aa89412..d57e56245 100644
--- a/lib/elixir/lib/inspect/algebra.ex
+++ b/lib/elixir/lib/inspect/algebra.ex
@@ -27,6 +27,9 @@ defmodule Inspect.Opts do
bitstrings, maps, lists and any other collection of items. It does not
apply to strings nor charlists and defaults to 50.
+ * `:printable_limit` - limits the number of bytes that are printed for strings
+ and char lists. Defaults to 1024.
+
* `:pretty` - if set to `true` enables pretty printing, defaults to `false`.
* `:width` - defaults to 80 characters, used when pretty is `true` or when
@@ -55,6 +58,7 @@ defmodule Inspect.Opts do
charlists: :infer,
char_lists: :infer,
limit: 50,
+ printable_limit: 1024,
width: 80,
base: :decimal,
pretty: false,
@@ -70,6 +74,7 @@ defmodule Inspect.Opts do
charlists: :infer | :as_lists | :as_charlists,
char_lists: :infer | :as_lists | :as_char_lists,
limit: pos_integer | :infinity,
+ printable_limit: pos_integer | :infinity,
width: pos_integer | :infinity,
base: :decimal | :binary | :hex | :octal,
pretty: boolean,
diff --git a/lib/elixir/lib/string.ex b/lib/elixir/lib/string.ex
index c7ffa6b00..2d96ed999 100644
--- a/lib/elixir/lib/string.ex
+++ b/lib/elixir/lib/string.ex
@@ -208,39 +208,49 @@ defmodule String do
@doc """
Checks if a string contains only printable characters.
+ Takes an optional `limit` as a second argument. `printable?/2` only
+ checks the printability of the string up to the `limit`.
+
## Examples
iex> String.printable?("abc")
true
+ iex> String.printable?("abc" <> <<0>>)
+ false
+
+ iex> String.printable?("abc" <> <<0>>, 2)
+ true
+
"""
@spec printable?(t) :: boolean
- def printable?(string)
+ @spec printable?(t, non_neg_integer | :infinity) :: boolean
+ def printable?(string, counter \\ :infinity)
+
+ def printable?(<<>>, _), do: true
+ def printable?(_, 0), do: true
for char <- 0x20..0x7E do
- def printable?(<<unquote(char), rest::binary>>) do
- printable?(rest)
+ def printable?(<<unquote(char), rest::binary>>, counter) do
+ printable?(rest, decrement(counter))
+ end
+ end
+ for char <- '\n\r\t\v\b\f\e\d\a' do
+ def printable?(<<unquote(char), rest::binary>>, counter) do
+ printable?(rest, decrement(counter))
end
end
- def printable?(<<?\n, rest::binary>>), do: printable?(rest)
- def printable?(<<?\r, rest::binary>>), do: printable?(rest)
- def printable?(<<?\t, rest::binary>>), do: printable?(rest)
- def printable?(<<?\v, rest::binary>>), do: printable?(rest)
- def printable?(<<?\b, rest::binary>>), do: printable?(rest)
- def printable?(<<?\f, rest::binary>>), do: printable?(rest)
- def printable?(<<?\e, rest::binary>>), do: printable?(rest)
- def printable?(<<?\d, rest::binary>>), do: printable?(rest)
- def printable?(<<?\a, rest::binary>>), do: printable?(rest)
-
- def printable?(<<char::utf8, rest::binary>>)
+ def printable?(<<char::utf8, rest::binary>>, counter)
when char in 0xA0..0xD7FF
when char in 0xE000..0xFFFD
when char in 0x10000..0x10FFFF do
- printable?(rest)
+ printable?(rest, decrement(counter))
end
- def printable?(<<>>), do: true
- def printable?(binary) when is_binary(binary), do: false
+ def printable?(binary, _) when is_binary(binary), do: false
+
+ defp decrement(:infinity), do: :infinity
+ defp decrement(counter), do: counter - 1
@doc ~S"""
Divides a string into substrings at each Unicode whitespace
diff --git a/lib/elixir/test/elixir/inspect_test.exs b/lib/elixir/test/elixir/inspect_test.exs
index 968bf0349..1dadd44f1 100644
--- a/lib/elixir/test/elixir/inspect_test.exs
+++ b/lib/elixir/test/elixir/inspect_test.exs
@@ -141,6 +141,15 @@ defmodule Inspect.BitStringTest do
test "unprintable with opts" do
assert inspect(<<193, 193, 193, 193>>, limit: 3) == "<<193, 193, 193, ...>>"
end
+
+ test "string with limits" do
+ assert inspect("hello world", printable_limit: 4) == ~s("hello")
+ # non printable characters after the limit don't matter
+ assert inspect("hello world" <> <<0>>, printable_limit: 4) == ~s("hello")
+ # non printable strings aren't affected by printable limit
+ assert inspect(<<0,1,2,3,4>>, printable_limit: 3) == ~s(<<0, 1, 2, 3, 4>>)
+
+ end
end
defmodule Inspect.NumberTest do
diff --git a/lib/iex/test/iex/autocomplete_test.exs b/lib/iex/test/iex/autocomplete_test.exs
index 28f53a314..e2a4bf674 100644
--- a/lib/iex/test/iex/autocomplete_test.exs
+++ b/lib/iex/test/iex/autocomplete_test.exs
@@ -160,8 +160,8 @@ defmodule IEx.AutocompleteTest do
end
test "function completion with arity" do
- assert expand('String.printable?') == {:yes, '', ['printable?/1']}
- assert expand('String.printable?/') == {:yes, '', ['printable?/1']}
+ assert expand('String.printable?') == {:yes, '', ['printable?/1', 'printable?/2']}
+ assert expand('String.printable?/') == {:yes, '', ['printable?/1', 'printable?/2']}
end
test "function completion using a variable bound to a module" do