Skip to content

Commit bc601a9

Browse files
authored
Merge pull request rails#54133 from flavorjones/flavorjones-strict-locals-exception-backtrace
fix: `ArgumentError`s raised during template rendering
2 parents 272fb90 + c868503 commit bc601a9

File tree

4 files changed

+49
-11
lines changed

4 files changed

+49
-11
lines changed

actionview/CHANGELOG.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
* Argument errors related to strict locals in templates now raise an
2+
`ActionView::StrictLocalsError`, and all other argument errors are reraised as-is.
3+
4+
Previously, any `ArgumentError` raised during template rendering was swallowed during strict
5+
local error handling, so that an `ArgumentError` unrelated to strict locals (e.g., a helper
6+
method invoked with incorrect arguments) would be replaced by a similar `ArgumentError` with an
7+
unrelated backtrace, making it difficult to debug templates.
8+
9+
Now, any `ArgumentError` unrelated to strict locals is reraised, preserving the original
10+
backtrace for developers.
11+
12+
Also note that `ActionView::StrictLocalsError` is a subclass of `ArgumentError`, so any existing
13+
code that rescues `ArgumentError` will continue to work.
14+
15+
Fixes #52227.
16+
17+
*Mike Dalessio*
18+
119
* Improve error highlighting of multi-line methods in ERB templates or
220
templates where the error occurs within a do-end block.
321

actionview/lib/action_view/base.rb

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -267,15 +267,12 @@ def _run(method, template, locals, buffer, add_to_stack: true, has_strict_locals
267267
begin
268268
public_send(method, locals, buffer, **locals, &block)
269269
rescue ArgumentError => argument_error
270-
raise(
271-
ArgumentError,
272-
argument_error.
273-
message.
274-
gsub("unknown keyword:", "unknown local:").
275-
gsub("missing keyword:", "missing local:").
276-
gsub("no keywords accepted", "no locals accepted").
277-
concat(" for #{@current_template.short_identifier}")
278-
)
270+
public_send_line = __LINE__ - 2
271+
frame = argument_error.backtrace_locations[1]
272+
if frame.path == __FILE__ && frame.lineno == public_send_line
273+
raise StrictLocalsError.new(argument_error, @current_template)
274+
end
275+
raise
279276
end
280277
else
281278
public_send(method, locals, buffer, &block)

actionview/lib/action_view/template/error.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,17 @@ def message
2727
end
2828
end
2929

30+
class StrictLocalsError < ArgumentError # :nodoc:
31+
def initialize(argument_error, template)
32+
message = argument_error.message.
33+
gsub("unknown keyword:", "unknown local:").
34+
gsub("missing keyword:", "missing local:").
35+
gsub("no keywords accepted", "no locals accepted").
36+
concat(" for #{template.short_identifier}")
37+
super(message)
38+
end
39+
end
40+
3041
class MissingTemplate < ActionViewError # :nodoc:
3142
attr_reader :path, :paths, :prefixes, :partial
3243

actionview/test/template/template_test.rb

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -220,22 +220,34 @@ def test_default_locals_can_be_specified
220220
assert_equal "Hello", render
221221
end
222222

223-
def test_required_locals_can_be_specified
223+
def test_required_locals_must_be_specified
224224
error = assert_raises(ActionView::Template::Error) do
225225
@template = new_template("<%# locals: (message:) -%>")
226226
render
227227
end
228228

229229
assert_match(/missing local: :message for hello template/, error.message)
230+
assert_instance_of ActionView::StrictLocalsError, error.cause
230231
end
231232

232-
def test_extra_locals_raises_error
233+
def test_extra_locals_raises_strict_locals_error
233234
error = assert_raises(ActionView::Template::Error) do
234235
@template = new_template("<%# locals: (message:) -%>")
235236
render(message: "Hi", foo: "bar")
236237
end
237238

238239
assert_match(/unknown local: :foo for hello template/, error.message)
240+
assert_instance_of ActionView::StrictLocalsError, error.cause
241+
end
242+
243+
def test_argument_error_in_the_template_is_not_hijacked_by_strict_locals_checking
244+
error = assert_raises(ActionView::Template::Error) do
245+
@template = new_template("<%# locals: () -%>\n<%= hello(:invalid_argument) %>")
246+
render
247+
end
248+
249+
assert_match(/in ['`]hello'/, error.backtrace.first)
250+
assert_instance_of ArgumentError, error.cause
239251
end
240252

241253
def test_rails_injected_locals_does_not_raise_error_if_not_passed

0 commit comments

Comments
 (0)