Skip to content

Commit 742ac3d

Browse files
authored
Prevent SelfDrop context mutation across render boundaries (#2082)
1 parent 1954a26 commit 742ac3d

3 files changed

Lines changed: 108 additions & 6 deletions

File tree

lib/liquid/self_drop.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,25 @@ module Liquid
1616
# then the local value takes precedence over the `self` object.
1717
# @liquid_access global
1818
class SelfDrop < Drop
19-
def initialize(context)
19+
def initialize(self_context)
2020
super()
21-
@context = context
21+
@self_context = self_context
2222
end
2323

2424
def [](key)
25-
@context.find_variable(key)
25+
@self_context.find_variable(key)
2626
rescue UndefinedVariable
2727
nil
2828
end
2929

3030
def key?(key)
31-
@context.variable_defined?(key)
31+
@self_context.variable_defined?(key)
3232
end
3333

3434
def to_liquid
3535
self
3636
end
37+
38+
undef context=
3739
end
3840
end

lib/liquid/template.rb

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -151,8 +151,10 @@ def render(*args)
151151

152152
c
153153
when Liquid::Drop
154-
drop = args.shift
155-
drop.context = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
154+
drop = args.shift
155+
c = Context.new([drop, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
156+
drop.context = c if drop.respond_to?(:context=)
157+
c
156158
when Hash
157159
Context.new([args.shift, assigns], instance_assigns, registers, @rethrow_errors, @resource_limits, {}, @environment)
158160
when nil
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# frozen_string_literal: true
2+
3+
require 'test_helper'
4+
5+
class SelfDropContextTest < Minitest::Test
6+
include Liquid
7+
8+
def test_self_drop_passed_as_render_param_preserves_original_scope
9+
source = <<~LIQUID
10+
{%- assign var = 42 -%}
11+
{%- assign s = self -%}
12+
{%- render "snippet1", other_self: s -%}
13+
LIQUID
14+
15+
partials = {
16+
'snippet1' => <<~LIQUID,
17+
{%- assign var = 43 -%}
18+
{{- other_self.var }}|{{ self.var -}}
19+
LIQUID
20+
}
21+
22+
assert_template_result('42|43', source, partials: partials)
23+
end
24+
25+
def test_self_drop_in_render_without_passing_resolves_inner_scope
26+
source = <<~LIQUID
27+
{%- assign var = 42 -%}
28+
{%- render "snippet1" -%}
29+
LIQUID
30+
31+
partials = {
32+
'snippet1' => <<~LIQUID,
33+
{%- assign var = 99 -%}
34+
{{- self.var -}}
35+
LIQUID
36+
}
37+
38+
assert_template_result('99', source, partials: partials)
39+
end
40+
41+
def test_self_drop_passed_to_nested_renders_preserves_each_level
42+
source = <<~LIQUID
43+
{%- assign a = 1 -%}
44+
{%- assign s1 = self -%}
45+
{%- render "snippet1", outer: s1 -%}
46+
LIQUID
47+
48+
partials = {
49+
'snippet1' => <<~LIQUID,
50+
{%- assign a = 2 -%}
51+
{%- assign s2 = self -%}
52+
{%- render "snippet2", outer: outer, middle: s2 -%}
53+
LIQUID
54+
'snippet2' => <<~LIQUID,
55+
{%- assign a = 3 -%}
56+
{{- outer.a }}|{{ middle.a }}|{{ self.a -}}
57+
LIQUID
58+
}
59+
60+
assert_template_result('1|2|3', source, partials: partials)
61+
end
62+
63+
def test_self_drop_reflects_variables_assigned_after_creation
64+
source = <<~LIQUID
65+
{%- assign s = self -%}
66+
{%- assign x = 42 %}{{ s.x -}}
67+
LIQUID
68+
69+
assert_template_result('42', source)
70+
end
71+
72+
def test_self_drop_context_setter_is_undefined
73+
context = Context.new
74+
drop = SelfDrop.new(context)
75+
refute(drop.respond_to?(:context=))
76+
77+
assert_template_result('42', '{{ self.x }}', { 'x' => 42 })
78+
end
79+
80+
def test_self_drop_with_strict_variables_does_not_raise_for_defined_var
81+
t = Template.parse('{{ self.x }}')
82+
result = t.render({ 'x' => 42 }, strict_variables: true)
83+
assert_equal('42', result)
84+
end
85+
86+
def test_self_drop_with_strict_variables_returns_nil_for_undefined_var
87+
t = Template.parse('{{ self.x }}')
88+
result = t.render({}, strict_variables: true)
89+
assert_equal('', result)
90+
end
91+
92+
def test_self_drop_can_be_passed_as_bare_drop_to_render
93+
t = Template.parse('{{ self.x }}')
94+
drop = SelfDrop.new(Context.new({ 'x' => 42 }))
95+
result = t.render(drop)
96+
assert_equal('42', result)
97+
end
98+
end

0 commit comments

Comments
 (0)