File elixir-1.16.2-git.patch of Package elixir
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7bcb4556f..0441740da 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -78,6 +78,27 @@ Another [ExDoc](https://github.com/elixir-lang/ex_doc) feature we have incorpora
Finally, we have started enriching our documentation with [Mermaid.js](https://mermaid.js.org/) diagrams. You can find examples in the [GenServer](https://hexdocs.pm/elixir/main/GenServer.html) and [Supervisor](https://hexdocs.pm/elixir/main/Supervisor.html) docs.
+## v1.16.3-dev
+
+### 1. Bug fixes
+
+#### Elixir
+
+ * [bin/elixir] Properly handle the `--dbg` flag in Elixir's CLI
+ * [Code.Formatter] Add brackets around keyword lists when formatting the left-hand side of `when`
+ * [Kernel] Only infer size in pinned variable in binary strings when needed
+ * [System] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows via `System.cmd/3`
+ * [Port] Add a note that arguments are unsafe when invoking .bat/.com scripts on Windows
+ * [URI] Ensure `:undefined` fields are properly converted to `nil` when invoking Erlang's API
+
+#### Logger
+
+ * [Logger] Ensure translators are persisted across logger restarts
+
+#### Mix
+
+ * [mix compile] Ensure compile paths are accessible during compilation
+
## v1.16.2 (2024-03-10)
### 1. Enhancements
diff --git a/bin/elixir b/bin/elixir
index d1b6058b7..166f6834a 100755
--- a/bin/elixir
+++ b/bin/elixir
@@ -112,10 +112,10 @@ while [ $I -le $LENGTH ]; do
C=1
MODE="iex"
;;
- -v|--no-halt|--dbg)
+ -v|--no-halt)
C=1
;;
- -e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex)
+ -e|-r|-pr|-pa|-pz|--eval|--remsh|--dot-iex|--dbg)
C=2
;;
--rpc-eval)
diff --git a/lib/elixir/lib/code/formatter.ex b/lib/elixir/lib/code/formatter.ex
index 6d50afb7e..3a159c3f7 100644
--- a/lib/elixir/lib/code/formatter.ex
+++ b/lib/elixir/lib/code/formatter.ex
@@ -1959,6 +1959,14 @@ defp clause_args_to_algebra(args, min_line, state) do
# fn a, b, c when d -> e end
defp clause_args_to_algebra([{:when, meta, args}], state) do
{args, right} = split_last(args)
+
+ # If there are any keywords, wrap them in lists
+ args =
+ Enum.map(args, fn
+ [_ | _] = keyword -> {:__block__, [], [keyword]}
+ other -> other
+ end)
+
left = {{:special, :clause_args}, meta, [args]}
binary_op_to_algebra(:when, "when", meta, left, right, :no_parens_arg, state)
end
diff --git a/lib/elixir/lib/port.ex b/lib/elixir/lib/port.ex
index fefcf2fa6..735529430 100644
--- a/lib/elixir/lib/port.ex
+++ b/lib/elixir/lib/port.ex
@@ -79,6 +79,27 @@ defmodule Port do
are for advanced usage within the VM. Also consider using `System.cmd/3`
if all you want is to execute a program and retrieve its return value.
+ > #### Windows argument splitting and untrusted arguments {: .warning}
+ >
+ > On Unix systems, arguments are passed to a new operating system
+ > process as an array of strings but on Windows it is up to the child
+ > process to parse them and some Windows programs may apply their own
+ > rules, which are inconsistent with the standard C runtime `argv` parsing
+ >
+ > This is particularly troublesome when invoking `.bat` or `.com` files
+ > as these run implicitly through `cmd.exe`, whose argument parsing is
+ > vulnerable to malicious input and can be used to run arbitrary shell
+ > commands.
+ >
+ > Therefore, if you are running on Windows and you execute batch
+ > files or `.com` applications, you must not pass untrusted input as
+ > arguments to the program. You may avoid accidentally executing them
+ > by explicitly passing the extension of the program you want to run,
+ > such as `.exe`, and double check the program is indeed not a batch
+ > file or `.com` application.
+ >
+ > This affects both `spawn` and `spawn_executable`.
+
### spawn
The `:spawn` tuple receives a binary that is going to be executed as a
diff --git a/lib/elixir/lib/system.ex b/lib/elixir/lib/system.ex
index 912b17b77..6d90cff49 100644
--- a/lib/elixir/lib/system.ex
+++ b/lib/elixir/lib/system.ex
@@ -1005,6 +1005,25 @@ def shell(command, opts \\ []) when is_binary(command) do
`Port` module describes this problem and possible solutions under
the "Zombie processes" section.
+ > #### Windows argument splitting and untrusted arguments {: .warning}
+ >
+ > On Unix systems, arguments are passed to a new operating system
+ > process as an array of strings but on Windows it is up to the child
+ > process to parse them and some Windows programs may apply their own
+ > rules, which are inconsistent with the standard C runtime `argv` parsing
+ >
+ > This is particularly troublesome when invoking `.bat` or `.com` files
+ > as these run implicitly through `cmd.exe`, whose argument parsing is
+ > vulnerable to malicious input and can be used to run arbitrary shell
+ > commands.
+ >
+ > Therefore, if you are running on Windows and you execute batch
+ > files or `.com` applications, you must not pass untrusted input as
+ > arguments to the program. You may avoid accidentally executing them
+ > by explicitly passing the extension of the program you want to run,
+ > such as `.exe`, and double check the program is indeed not a batch
+ > file or `.com` application.
+
## Examples
iex> System.cmd("echo", ["hello"])
diff --git a/lib/elixir/lib/uri.ex b/lib/elixir/lib/uri.ex
index fe612a609..c15523dc2 100644
--- a/lib/elixir/lib/uri.ex
+++ b/lib/elixir/lib/uri.ex
@@ -656,16 +656,16 @@ defp uri_from_map(map) do
scheme = String.downcase(scheme, :ascii)
case map do
- %{port: port} when port != :undefined ->
+ %{port: port} when is_integer(port) ->
%{uri | scheme: scheme}
%{} ->
- case default_port(scheme) do
- nil -> %{uri | scheme: scheme}
- port -> %{uri | scheme: scheme, port: port}
- end
+ %{uri | scheme: scheme, port: default_port(scheme)}
end
+ %{port: :undefined} ->
+ %{uri | port: nil}
+
%{} ->
uri
end
diff --git a/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd b/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd
index 4d5a63ea0..73255de6c 100644
--- a/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd
+++ b/lib/elixir/pages/cheatsheets/enum-cheat.cheatmd
@@ -707,11 +707,11 @@ fruits = ["apple", "banana", "grape", "orange", "pear"]
iex> Enum.slide(fruits, 2, 0)
["grape", "apple", "banana", "orange", "pear"]
iex> Enum.slide(fruits, 2, 4)
-["apple", "banana", "orange", "pear", "grape", ]
+["apple", "banana", "orange", "pear", "grape"]
iex> Enum.slide(fruits, 1..3, 0)
["banana", "grape", "orange", "apple", "pear"]
iex> Enum.slide(fruits, 1..3, 4)
-["banana", "pear", "grape", "orange", "apple"]
+["apple", "pear", "banana", "grape", "orange"]
```
## Reversing
diff --git a/lib/elixir/pages/getting-started/basic-types.md b/lib/elixir/pages/getting-started/basic-types.md
index 525d9bb12..3bbaa7c2b 100644
--- a/lib/elixir/pages/getting-started/basic-types.md
+++ b/lib/elixir/pages/getting-started/basic-types.md
@@ -227,7 +227,7 @@ Elixir also supports string interpolation:
```elixir
iex> string = "world"
iex> "hello #{string}!"
-"hello world"
+"hello world!"
```
String concatenation requires both sides to be strings but interpolation supports any data type that may be converted to a string:
diff --git a/lib/elixir/pages/meta-programming/macros.md b/lib/elixir/pages/meta-programming/macros.md
index 1f04a344c..7b70efb5c 100644
--- a/lib/elixir/pages/meta-programming/macros.md
+++ b/lib/elixir/pages/meta-programming/macros.md
@@ -223,7 +223,7 @@ It is important that a macro is defined before its usage. Failing to define a ma
```elixir
iex> defmodule Sample do
-...> def four, do: two + two
+...> def four, do: two() + two()
...> defmacrop two, do: 2
...> end
** (CompileError) iex:2: function two/0 undefined
diff --git a/lib/elixir/src/elixir_bitstring.erl b/lib/elixir/src/elixir_bitstring.erl
index 1d85535d0..963be7837 100644
--- a/lib/elixir/src/elixir_bitstring.erl
+++ b/lib/elixir/src/elixir_bitstring.erl
@@ -36,9 +36,9 @@ expand(BitstrMeta, Fun, [{'::', Meta, [Left, Right]} | T], Acc, S, E, Alignment,
MatchOrRequireSize = RequireSize or is_match_size(T, EL),
EType = expr_type(ELeft),
ExpectSize = case ELeft of
+ _ when not MatchOrRequireSize -> optional;
{'^', _, [{_, _, _}]} -> {infer, ELeft};
- _ when MatchOrRequireSize -> required;
- _ -> optional
+ _ -> required
end,
{ERight, EAlignment, SS, ES} = expand_specs(EType, Meta, Right, SL, OriginalS, EL, ExpectSize),
diff --git a/lib/elixir/test/elixir/code_formatter/general_test.exs b/lib/elixir/test/elixir/code_formatter/general_test.exs
index 525f08ff3..bc3b58ab8 100644
--- a/lib/elixir/test/elixir/code_formatter/general_test.exs
+++ b/lib/elixir/test/elixir/code_formatter/general_test.exs
@@ -296,6 +296,28 @@ test "with a single clause and when" do
assert_same code, @short_length
end
+ test "keeps parens if argument includes keyword list" do
+ assert_same """
+ fn [] when is_integer(x) ->
+ x + 42
+ end
+ """
+
+ bad = """
+ fn (input: x) when is_integer(x) ->
+ x + 42
+ end
+ """
+
+ good = """
+ fn [input: x] when is_integer(x) ->
+ x + 42
+ end
+ """
+
+ assert_format bad, good
+ end
+
test "with a single clause, followed by a newline, and can fit in one line" do
assert_same """
fn
diff --git a/lib/elixir/test/elixir/kernel/binary_test.exs b/lib/elixir/test/elixir/kernel/binary_test.exs
index dadcfaee6..dcb80f5c4 100644
--- a/lib/elixir/test/elixir/kernel/binary_test.exs
+++ b/lib/elixir/test/elixir/kernel/binary_test.exs
@@ -255,6 +255,12 @@ test "bitsyntax size using guard expressions in match context" do
assert <<1::size((^foo).bar)>> = <<1::5>>
end
+ test "bitsyntax size with pinned integer" do
+ a = 1
+ b = <<2, 3>>
+ assert <<^a, ^b::binary>> = <<1, 2, 3>>
+ end
+
test "automatic size computation of matched bitsyntax variable" do
var = "foo"
<<^var::binary, rest::binary>> = "foobar"
diff --git a/lib/elixir/test/elixir/uri_test.exs b/lib/elixir/test/elixir/uri_test.exs
index e465cb323..8772ec97d 100644
--- a/lib/elixir/test/elixir/uri_test.exs
+++ b/lib/elixir/test/elixir/uri_test.exs
@@ -277,6 +277,32 @@ test "preserves empty fragments" do
test "preserves an empty query" do
assert URI.new!("http://foo.com/?").query == ""
end
+
+ test "without scheme, undefined port after host translates to nil" do
+ assert URI.new!("//https://www.example.com") ==
+ %URI{
+ scheme: nil,
+ userinfo: nil,
+ host: "https",
+ port: nil,
+ path: "//www.example.com",
+ query: nil,
+ fragment: nil
+ }
+ end
+
+ test "with scheme, undefined port after host translates to nil" do
+ assert URI.new!("myscheme://myhost:/path/info") ==
+ %URI{
+ scheme: "myscheme",
+ userinfo: nil,
+ host: "myhost",
+ port: nil,
+ path: "/path/info",
+ query: nil,
+ fragment: nil
+ }
+ end
end
test "http://http://http://@http://http://?http://#http://" do
diff --git a/lib/iex/test/iex/helpers_test.exs b/lib/iex/test/iex/helpers_test.exs
index 18b19ddce..56e8c0144 100644
--- a/lib/iex/test/iex/helpers_test.exs
+++ b/lib/iex/test/iex/helpers_test.exs
@@ -332,17 +332,20 @@ test "shows help" do
assert help =~ "Welcome to Interactive Elixir"
end
+ @tag :erlang_doc
test "prints Erlang module documentation" do
captured = capture_io(fn -> h(:timer) end)
assert captured =~ "This module provides useful functions related to time."
end
+ @tag :erlang_doc
test "prints Erlang module function specs" do
captured = capture_io(fn -> h(:timer.sleep() / 1) end)
assert captured =~ ":timer.sleep/1"
assert captured =~ "-spec sleep(Time) -> ok when Time :: timeout()."
end
+ @tag :erlang_doc
test "handles non-existing Erlang module function" do
captured = capture_io(fn -> h(:timer.baz() / 1) end)
assert captured =~ "No documentation for :timer.baz was found"
@@ -1008,13 +1011,15 @@ defmodule TypeSample do
cleanup_modules([TypeSample])
end
- test "prints all types in erlang module" do
+ @tag :erlang_doc
+ test "prints all types in Erlang module" do
captured = capture_io(fn -> t(:queue) end)
assert captured =~ "-type queue() :: queue(_)"
assert captured =~ "-opaque queue(Item)"
end
- test "prints single type from erlang module" do
+ @tag :erlang_doc
+ test "prints single type from Erlang module" do
captured = capture_io(fn -> t(:erlang.iovec()) end)
assert captured =~ "-type iovec() :: [binary()]"
assert captured =~ "A list of binaries."
@@ -1024,7 +1029,8 @@ test "prints single type from erlang module" do
assert captured =~ "A list of binaries."
end
- test "handles non-existing types from erlang module" do
+ @tag :erlang_doc
+ test "handles non-existing types from Erlang module" do
captured = capture_io(fn -> t(:erlang.foo()) end)
assert captured =~ "No type information for :erlang.foo was found or :erlang.foo is private"
diff --git a/lib/iex/test/test_helper.exs b/lib/iex/test/test_helper.exs
index f5a55f0aa..0512ba2b9 100644
--- a/lib/iex/test/test_helper.exs
+++ b/lib/iex/test/test_helper.exs
@@ -7,11 +7,19 @@
{line_exclude, line_include} =
if line = System.get_env("LINE"), do: {[:test], [line: line]}, else: {[], []}
+erlang_doc_exclude =
+ if match?({:docs_v1, _, _, _, %{}, _, _}, Code.fetch_docs(:array)) do
+ []
+ else
+ IO.puts("Erlang/OTP compiled without docs, some tests are excluded...")
+ [:erlang_doc]
+ end
+
ExUnit.start(
assert_receive_timeout: assert_timeout,
trace: !!System.get_env("TRACE"),
include: line_include,
- exclude: line_exclude
+ exclude: line_exclude ++ erlang_doc_exclude
)
defmodule IEx.Case do
diff --git a/lib/logger/lib/logger.ex b/lib/logger/lib/logger.ex
index 7eab7eafe..1e16cf98e 100644
--- a/lib/logger/lib/logger.ex
+++ b/lib/logger/lib/logger.ex
@@ -867,9 +867,12 @@ def remove_translator({mod, fun} = translator) when is_atom(mod) and is_atom(fun
defp update_translators(updater) do
:elixir_config.serial(fn ->
+ translators = updater.(Application.fetch_env!(:logger, :translators))
+ Application.put_env(:logger, :translators, translators)
+
with %{filters: filters} <- :logger.get_primary_config(),
{{_, {fun, config}}, filters} <- List.keytake(filters, :logger_translator, 0) do
- config = update_in(config.translators, updater)
+ config = %{config | translators: translators}
:ok = :logger.set_primary_config(:filters, filters ++ [logger_translator: {fun, config}])
end
end)
diff --git a/lib/logger/test/logger/backends/handler_test.exs b/lib/logger/test/logger/backends/handler_test.exs
index 26e744cda..921699048 100644
--- a/lib/logger/test/logger/backends/handler_test.exs
+++ b/lib/logger/test/logger/backends/handler_test.exs
@@ -58,7 +58,9 @@ test "add_translator/1 and remove_translator/1 for error_logger" do
end
test "add_translator/1 and remove_translator/1 for logger formats" do
+ refute {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
assert Logger.add_translator({CustomTranslator, :t})
+ assert {CustomTranslator, :t} in Application.fetch_env!(:logger, :translators)
assert capture_log(fn ->
:logger.info(~c"hello: ~p", [:ok])
diff --git a/lib/mix/lib/mix/tasks/compile.all.ex b/lib/mix/lib/mix/tasks/compile.all.ex
index 268a67275..8f29a2856 100644
--- a/lib/mix/lib/mix/tasks/compile.all.ex
+++ b/lib/mix/lib/mix/tasks/compile.all.ex
@@ -43,7 +43,11 @@ def run(args) do
Code.delete_paths(current_paths -- loaded_paths)
end
- Code.prepend_paths(loaded_paths -- current_paths, cache: true)
+ # Add the current compilation path. compile.elixir and compile.erlang
+ # will also add this path, but only if they run, so we always add it
+ # here too. Furthermore, we don't cache it as we may still write to it.
+ compile_path = to_charlist(Mix.Project.compile_path())
+ Code.prepend_paths([compile_path | loaded_paths -- current_paths], cache: true)
result =
if "--no-compile" in args do
@@ -64,12 +68,6 @@ def run(args) do
Mix.AppLoader.write_cache(app_cache, Map.new(loaded_modules))
end
- # Add the current compilation path. compile.elixir and compile.erlang
- # will also add this path, but only if they run, so we always add it
- # here too. Furthermore, we don't cache it as we may still write to it.
- compile_path = to_charlist(Mix.Project.compile_path())
- _ = Code.prepend_path(compile_path)
-
unless "--no-app-loading" in args do
app = config[:app]
diff --git a/lib/mix/test/mix/tasks/compile.elixir_test.exs b/lib/mix/test/mix/tasks/compile.elixir_test.exs
index 506b29f9e..47734e92f 100644
--- a/lib/mix/test/mix/tasks/compile.elixir_test.exs
+++ b/lib/mix/test/mix/tasks/compile.elixir_test.exs
@@ -19,6 +19,14 @@ test "compiles a project without per environment build" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
+
+ File.write!("lib/a.ex", """
+ defmodule A, do: :ok
+
+ # Also make sure that we access the ebin directory during compilation
+ true = to_charlist(Mix.Project.compile_path()) in :code.get_path()
+ """)
+
Mix.Tasks.Compile.Elixir.run(["--verbose"])
assert File.regular?("_build/shared/lib/sample/ebin/Elixir.A.beam")
@@ -32,6 +40,14 @@ test "compiles a project without per environment build" do
test "compiles a project with per environment build" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
+
+ File.write!("lib/a.ex", """
+ defmodule A, do: :ok
+
+ # Also make sure that we access the ebin directory during compilation
+ true = to_charlist(Mix.Project.compile_path()) in :code.get_path()
+ """)
+
Mix.Tasks.Compile.Elixir.run(["--verbose"])
assert File.regular?("_build/dev/lib/sample/ebin/Elixir.A.beam")
@@ -772,7 +788,7 @@ def inspect(_, _), do: "sample"
end)
end
- test "compiles mtime changed files if content changed but not length" do
+ test "recompiles mtime changed files if content changed but not length" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
assert Mix.Tasks.Compile.Elixir.run(["--verbose"]) == {:ok, []}
@@ -872,7 +888,7 @@ test "does recompile a file restored after a compile error (and .beam file were
end)
end
- test "compiles size changed files" do
+ test "recompiles size changed files" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
past = @old_time
@@ -894,7 +910,7 @@ test "compiles size changed files" do
end)
end
- test "compiles dependent changed modules" do
+ test "recompiles dependent changed modules" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", "defmodule A, do: B.module_info()")
@@ -914,7 +930,7 @@ test "compiles dependent changed modules" do
end)
end
- test "compiles dependent changed modules without beam files" do
+ test "recompiles dependent changed modules without beam files" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
@@ -943,7 +959,7 @@ def a, do: A.__info__(:module)
Code.put_compiler_option(:ignore_module_conflict, false)
end
- test "compiles dependent changed modules even on removal" do
+ test "recompiles dependent changed modules even on removal" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
File.write!("lib/a.ex", "defmodule A, do: B.module_info()")
@@ -964,7 +980,7 @@ test "compiles dependent changed modules even on removal" do
end)
end
- test "compiles dependent changed on conflict" do
+ test "recompiles dependent changed on conflict" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
@@ -991,7 +1007,7 @@ test "compiles dependent changed on conflict" do
end)
end
- test "compiles dependent changed external resources" do
+ test "recompiles dependent changed external resources" do
in_fixture("no_mixfile", fn ->
Mix.Project.push(MixTest.Case.Sample)
tmp = tmp_path("c.eex")