Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions lib/liquid/block_body.rb
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,10 @@ def self.rescue_render_node(context, output, line_number, exc, blank_tag)
case exc
when MemoryError
raise
when StackLevelError
# Nesting is a resource limit; continuing can multiply work exponentially.
context.handle_error(exc, line_number)
raise
when UndefinedVariable, UndefinedDropMethod, UndefinedFilter
context.handle_error(exc, line_number)
else
Expand Down
5 changes: 4 additions & 1 deletion test/integration/error_handling_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -328,7 +328,10 @@ def test_error_is_raised_during_parse_with_template_name
},
)

assert_equal("Liquid error (some/path/snippet line 1): Nesting too deep", template.render(context))
error = assert_raises(Liquid::StackLevelError) do
template.render(context)
end
assert_equal("Liquid error (some/path/snippet line 1): Nesting too deep", error.to_s)
end

def test_internal_error_is_raised_with_template_name
Expand Down
25 changes: 25 additions & 0 deletions test/integration/security_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,29 @@ def test_more_than_max_depth_nested_blocks_raises_exception
Template.parse(code).render!
end
end

def test_recursive_includes_fail_fast_on_stack_level_error
with_block_max_depth(4) do
file_system = StubFileSystem.new(
"recurse" => "({% include 'recurse' %})\n[{% include 'recurse' %}]",
)
template = Template.parse("{% include 'recurse' %}", error_mode: :lax)

assert_raises(Liquid::StackLevelError) do
template.render({}, registers: { file_system: file_system })
end
end
end

private

def with_block_max_depth(depth)
original_depth = Liquid::Block::MAX_DEPTH
Liquid::Block.send(:remove_const, :MAX_DEPTH)
Liquid::Block.const_set(:MAX_DEPTH, depth)
yield
ensure
Liquid::Block.send(:remove_const, :MAX_DEPTH)
Liquid::Block.const_set(:MAX_DEPTH, original_depth)
end
end # SecurityTest
Loading