Compare commits

...
16 Commits
Author SHA1 Message Date
José Valim 327063cc84 Release v1.16.3 2024-05-21 00:14:25 +02:00
José Valim 795d0a9583 More fixes 2024-05-20 16:51:13 +02:00
José Valim 1150ebad03 Update CHANGELOG 2024-05-06 12:50:25 +02:00
José Valim 18feec4cd8 Add brackets around keyword lists when formatting with when, closes #13503 2024-05-06 12:44:54 +02:00
José Valim 7d5920206b Check that docs are not hidden in suite, closes #13516 2024-05-02 01:26:48 +02:00
José Valim a694447601 Ensure translators are persisted across logger restarts 2024-05-01 11:40:12 +02:00
José Valim 19ae388dda Add .bat/.com disclaimers to System.cmd and Port 2024-04-17 11:29:02 +02:00
José Valim 6b5edea5f1 Fix --dbg handling in bin/elixir, closes #13482 2024-04-08 09:31:20 +02:00
Arne Bedurftig b736c23af9 Fix typo in basic types (#13481)
The exclamation mark was missing in the result of the concatenation.
2024-04-07 20:58:08 +02:00
Theodor Fiedler 63f0000ace Translate :undefined URI port to nil (#13464)
Resolves #13462

When a url string is schema less and the host is followed by a colon
without setting the actual port, `:uri_string.parse/1` returns the
port to be `:undefined`. As long as the url string is parseable,
we should translate `:undefined` to `nil`, in order to ensure we return
a valid URI struct.
2024-04-04 12:43:16 +02:00
José Valim 5e8b187d08 Ensure compile paths are available during compilation, closes #13458 2024-04-01 12:18:01 +02:00
Jean Klingler aea1a47b8d Only infer size in pinned variable when needed (#13423) 2024-03-20 19:00:17 +09:00
Jean Klingler eeba8e992d Fix Enum.slide/3 example in cheatsheet (#13054) 2024-03-16 17:09:52 +01:00
Steven C dd42dd8620 Fix typo around Enum.slide/3 in the Enum cheatsheet (#13053) 2024-03-16 17:09:45 +01:00
José Valim ed1ddfd338 Add parens to private macro example, closes #13411 2024-03-13 12:28:36 +01:00
José Valim d17df054e2 Skip tests if Erlang was compiled without docs, closes #13322 2024-03-10 19:07:22 +01:00
22 changed files with 231 additions and 41 deletions
+21
View File
@@ -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 (2024-05-21)
### 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
+1 -1
View File
@@ -1 +1 @@
1.16.2
1.16.3
+3 -3
View File
@@ -1,7 +1,7 @@
#!/bin/sh
set -e
ELIXIR_VERSION=1.16.2
ELIXIR_VERSION=1.16.3
if [ $# -eq 0 ] || { [ $# -eq 1 ] && { [ "$1" = "--help" ] || [ "$1" = "-h" ]; }; }; then
cat <<USAGE >&2
@@ -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)
+1 -1
View File
@@ -1,6 +1,6 @@
@if defined ELIXIR_CLI_ECHO (@echo on) else (@echo off)
set ELIXIR_VERSION=1.16.2
set ELIXIR_VERSION=1.16.3
setlocal enabledelayedexpansion
if ""%1""=="""" if ""%2""=="""" goto documentation
+8
View File
@@ -1959,6 +1959,14 @@ defmodule Code.Formatter 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
+21
View File
@@ -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
+19
View File
@@ -1005,6 +1005,25 @@ defmodule System 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"])
+5 -5
View File
@@ -656,16 +656,16 @@ defmodule URI 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
@@ -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
@@ -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:
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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),
@@ -296,6 +296,28 @@ defmodule Code.Formatter.GeneralTest 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
@@ -255,6 +255,12 @@ defmodule Kernel.BinaryTest 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"
+26
View File
@@ -277,6 +277,32 @@ defmodule URITest 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
+8 -1
View File
@@ -216,7 +216,14 @@ defmodule ExUnit.CaptureIOTest do
end)
capture_io("\"a", fn ->
assert :io.scan_erl_form(~c">") == {:error, {1, :erl_scan, {:string, 34, ~c"a"}}, 1}
error =
if System.otp_release() >= "27" do
{1, :erl_scan, {:unterminated, :string, ~c"a"}}
else
{1, :erl_scan, {:string, 34, ~c"a"}}
end
assert :io.scan_erl_form(~c">") == {:error, error, 1}
assert :io.scan_erl_form(~c">") == {:eof, 1}
end)
+41 -8
View File
@@ -332,17 +332,26 @@ defmodule IEx.HelpersTest 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()."
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-spec sleep(Time) -> ok when Time :: timeout()."
else
assert captured =~ "sleep(Time)"
end
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,23 +1017,47 @@ defmodule IEx.HelpersTest 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)"
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-type queue() :: queue(_)"
assert captured =~ "-opaque queue(Item)"
else
assert captured =~ "queue()"
assert captured =~ "queue(Item)"
end
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()]"
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-type iovec() :: [binary()]"
else
assert captured =~ "iovec()"
end
assert captured =~ "A list of binaries."
captured = capture_io(fn -> t(:erlang.iovec() / 0) end)
assert captured =~ "-type iovec() :: [binary()]"
# TODO Fix for OTP 27 once specs are available
if System.otp_release() < "27" do
assert captured =~ "-type iovec() :: [binary()]"
else
assert captured =~ "iovec()"
end
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"
+9 -1
View File
@@ -7,11 +7,19 @@ IEx.configure(colors: [enabled: false])
{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
+4 -1
View File
@@ -867,9 +867,12 @@ defmodule Logger do
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)
@@ -58,7 +58,9 @@ defmodule Logger.Backends.HandlerTest 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])
+5 -7
View File
@@ -43,7 +43,11 @@ defmodule Mix.Tasks.Compile.All 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 @@ defmodule Mix.Tasks.Compile.All 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]
+23 -7
View File
@@ -19,6 +19,14 @@ defmodule Mix.Tasks.Compile.ElixirTest 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 @@ defmodule Mix.Tasks.Compile.ElixirTest 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 @@ defmodule Mix.Tasks.Compile.ElixirTest do
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 @@ defmodule Mix.Tasks.Compile.ElixirTest do
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 @@ defmodule Mix.Tasks.Compile.ElixirTest 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 @@ defmodule Mix.Tasks.Compile.ElixirTest 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 @@ defmodule Mix.Tasks.Compile.ElixirTest do
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 @@ defmodule Mix.Tasks.Compile.ElixirTest 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 @@ defmodule Mix.Tasks.Compile.ElixirTest 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")