From 2ac95e911633fd5059e72e2b36737562da12723d Mon Sep 17 00:00:00 2001 From: sabiwara Date: Sat, 20 Sep 2025 10:15:01 +0900 Subject: [PATCH 1/3] Assert scope is not match/guard when using escaped regexes --- lib/elixir/lib/kernel.ex | 6 ++++++ lib/elixir/lib/regex.ex | 1 + lib/elixir/test/elixir/macro_test.exs | 10 ++++++++-- lib/elixir/test/elixir/regex_test.exs | 19 +++++++++++++++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index c2476ea290a..f8c6e7068c4 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -7004,6 +7004,12 @@ defmodule Kernel do ## Shared functions + @doc false + defmacro __assert_assert_no_match_or_guard_scope__(exp) do + assert_no_match_or_guard_scope(__CALLER__.context, exp) + nil + end + defp assert_module_scope(env, fun, arity) do case env.module do nil -> raise ArgumentError, "cannot invoke #{fun}/#{arity} outside module" diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index d07f4479c25..47cf316b7f7 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -1018,6 +1018,7 @@ defmodule Regex do {:ok, exported} = :re.compile(regex.source, [:export] ++ regex.opts) quote do + __assert_assert_no_match_or_guard_scope__("an escaped Regex") :re.import(unquote(Macro.escape(exported))) end diff --git a/lib/elixir/test/elixir/macro_test.exs b/lib/elixir/test/elixir/macro_test.exs index ba9889d6b2c..a2d1a817fa3 100644 --- a/lib/elixir/test/elixir/macro_test.exs +++ b/lib/elixir/test/elixir/macro_test.exs @@ -167,8 +167,14 @@ defmodule MacroTest do [], [ __struct__: Regex, - re_pattern: - {{:., [], [:re, :import]}, [], [{:{}, [], [:re_exported_pattern | _]}]}, + re_pattern: { + :__block__, + [], + [ + {:__assert_assert_no_match_or_guard_scope__, _, _}, + {{:., [], [:re, :import]}, [], [{:{}, [], [:re_exported_pattern | _]}]} + ] + }, source: "foo", opts: [] ] diff --git a/lib/elixir/test/elixir/regex_test.exs b/lib/elixir/test/elixir/regex_test.exs index 166a3281360..7ab7e1c79c3 100644 --- a/lib/elixir/test/elixir/regex_test.exs +++ b/lib/elixir/test/elixir/regex_test.exs @@ -28,6 +28,25 @@ defmodule RegexTest do end end + @tag :re_import + test "module attribute in match context" do + assert_raise( + ArgumentError, + ~r/invalid expression in match, an escaped Regex is not allowed in patterns such as function clauses/, + fn -> + Code.eval_quoted( + quote do + defmodule ModAttrGuard do + @regex ~r/example/ + def example?(@regex), do: true + def example?(_), do: false + end + end + ) + end + ) + end + test "multiline" do refute Regex.match?(~r/^b$/, "a\nb\nc") assert Regex.match?(~r/^b$/m, "a\nb\nc") From c1dee89adb5cd1126eb301ded559aa3e130d6280 Mon Sep 17 00:00:00 2001 From: sabiwara Date: Sat, 20 Sep 2025 17:27:52 +0900 Subject: [PATCH 2/3] Use Regex.__compile_pattern__ macro instead --- lib/elixir/lib/kernel.ex | 6 ------ lib/elixir/lib/regex.ex | 15 +++++++++++++-- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/lib/elixir/lib/kernel.ex b/lib/elixir/lib/kernel.ex index f8c6e7068c4..c2476ea290a 100644 --- a/lib/elixir/lib/kernel.ex +++ b/lib/elixir/lib/kernel.ex @@ -7004,12 +7004,6 @@ defmodule Kernel do ## Shared functions - @doc false - defmacro __assert_assert_no_match_or_guard_scope__(exp) do - assert_no_match_or_guard_scope(__CALLER__.context, exp) - nil - end - defp assert_module_scope(env, fun, arity) do case env.module do nil -> raise ArgumentError, "cannot invoke #{fun}/#{arity} outside module" diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index 47cf316b7f7..c0b2451a043 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -1018,8 +1018,8 @@ defmodule Regex do {:ok, exported} = :re.compile(regex.source, [:export] ++ regex.opts) quote do - __assert_assert_no_match_or_guard_scope__("an escaped Regex") - :re.import(unquote(Macro.escape(exported))) + require Regex + Regex.__import_pattern__(unquote(Macro.escape(exported))) end # TODO: Remove this when we require Erlang/OTP 28.1+ @@ -1042,4 +1042,15 @@ defmodule Regex do } end end + + @doc false + defmacro __import_pattern__(pattern) do + if __CALLER__.context in [:match, :guard] do + raise ArgumentError, "escaped" + end + + quote do + :re.import(unquote(pattern)) + end + end end From 4012cd0256386ab62946e92866f434f58bf61758 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Valim?= Date: Thu, 25 Sep 2025 12:54:49 +0200 Subject: [PATCH 3/3] Add nil check for regex pattern in pattern AST --- lib/elixir/lib/regex.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/elixir/lib/regex.ex b/lib/elixir/lib/regex.ex index c0b2451a043..3cf41bd8d99 100644 --- a/lib/elixir/lib/regex.ex +++ b/lib/elixir/lib/regex.ex @@ -1008,6 +1008,9 @@ defmodule Regex do pattern_ast = cond do + is_nil(regex.re_pattern) -> + nil + # TODO: Remove this when we require Erlang/OTP 28+ # Before OTP 28.0, patterns did not contain any refs and could be safely be escaped :erlang.system_info(:otp_release) < [?2, ?8] ->