Skip to content

Commit 268c7cb

Browse files
Prevent leaking memory when exec_recursive's block raises (#15893)
1 parent 8be0116 commit 268c7cb

File tree

2 files changed

+105
-51
lines changed

2 files changed

+105
-51
lines changed

spec/std/reference_spec.cr

Lines changed: 100 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,30 @@ private module ReferenceSpec
5252
class TestClassWithFinalize
5353
include FinalizeCounter
5454
end
55+
56+
class NestedErrorTestClass
57+
@x = RaisesOnInspectAndClone.new
58+
59+
def_clone
60+
end
61+
62+
class RaisesOnInspectAndClone
63+
def inspect(io : IO)
64+
raise RuntimeError.new("I'm shy")
65+
end
66+
67+
def clone
68+
raise RuntimeError.new("I'm unique")
69+
end
70+
end
71+
end
72+
73+
private def expect_empty_recursive_hashes
74+
Fiber.current.exec_recursive_hash.dup.should be_empty
75+
Fiber.current.exec_recursive_clone_hash.dup.should be_empty
76+
ensure
77+
Fiber.current.exec_recursive_clone_hash.clear
78+
Fiber.current.exec_recursive_hash.clear
5579
end
5680

5781
describe "Reference" do
@@ -71,73 +95,102 @@ describe "Reference" do
7195
(!Reference.new).should be_false
7296
end
7397

74-
it "does inspect" do
75-
r = ReferenceSpec::TestClass.new(1, "hello")
76-
r.inspect.should eq(%(#<ReferenceSpec::TestClass:0x#{r.object_id.to_s(16)} @x=1, @y="hello">))
77-
end
98+
describe "#inspect" do
99+
it "does inspect" do
100+
r = ReferenceSpec::TestClass.new(1, "hello")
101+
r.inspect.should eq(%(#<ReferenceSpec::TestClass:0x#{r.object_id.to_s(16)} @x=1, @y="hello">))
102+
expect_empty_recursive_hashes
103+
end
78104

79-
it "does to_s" do
80-
r = ReferenceSpec::TestClass.new(1, "hello")
81-
r.to_s.should eq(%(#<ReferenceSpec::TestClass:0x#{r.object_id.to_s(16)}>))
82-
end
105+
it "does inspect for class" do
106+
String.inspect.should eq("String")
107+
expect_empty_recursive_hashes
108+
end
83109

84-
it "does inspect for class" do
85-
String.inspect.should eq("String")
110+
it "handles error" do
111+
r = ReferenceSpec::NestedErrorTestClass.new
112+
expect_raises RuntimeError, "I'm shy" do
113+
r.inspect
114+
end
115+
expect_empty_recursive_hashes
116+
end
86117
end
87118

88-
it "does to_s for class" do
89-
String.to_s.should eq("String")
90-
end
119+
describe "#to_s" do
120+
it "does to_s" do
121+
r = ReferenceSpec::TestClass.new(1, "hello")
122+
r.to_s.should eq(%(#<ReferenceSpec::TestClass:0x#{r.object_id.to_s(16)}>))
123+
end
124+
125+
it "does to_s for class" do
126+
String.to_s.should eq("String")
127+
end
91128

92-
it "does to_s for class if virtual" do
93-
[ReferenceSpec::TestClassBase, ReferenceSpec::TestClassSubclass].to_s.should eq("[ReferenceSpec::TestClassBase, ReferenceSpec::TestClassSubclass]")
129+
it "does to_s for class if virtual" do
130+
[ReferenceSpec::TestClassBase, ReferenceSpec::TestClassSubclass].to_s.should eq("[ReferenceSpec::TestClassBase, ReferenceSpec::TestClassSubclass]")
131+
end
94132
end
95133

96134
it "returns itself" do
97135
x = "hello"
98136
x.itself.should be(x)
99137
end
100138

101-
it "dups" do
102-
original = ReferenceSpec::DupCloneClass.new
103-
duplicate = original.dup
104-
duplicate.should_not be(original)
105-
duplicate.x.should eq(original.x)
106-
duplicate.y.should be(original.y)
107-
end
139+
describe "#dup" do
140+
it "dups" do
141+
original = ReferenceSpec::DupCloneClass.new
142+
duplicate = original.dup
143+
duplicate.should_not be(original)
144+
duplicate.x.should eq(original.x)
145+
duplicate.y.should be(original.y)
146+
end
108147

109-
it "can dup class that inherits abstract class" do
110-
original = ReferenceSpec::Concrete.new(2).as(ReferenceSpec::Abstract)
111-
duplicate = original.dup
112-
duplicate.should be_a(ReferenceSpec::Concrete)
113-
duplicate.should_not be(original)
114-
duplicate.x.should eq(original.x)
115-
end
148+
it "can dup class that inherits abstract class" do
149+
original = ReferenceSpec::Concrete.new(2).as(ReferenceSpec::Abstract)
150+
duplicate = original.dup
151+
duplicate.should be_a(ReferenceSpec::Concrete)
152+
duplicate.should_not be(original)
153+
duplicate.x.should eq(original.x)
154+
end
116155

117-
it "clones with def_clone" do
118-
original = ReferenceSpec::DupCloneClass.new
119-
clone = original.clone
120-
clone.should_not be(original)
121-
clone.x.should eq(original.x)
156+
it "calls #finalize on #dup'ed objects" do
157+
obj = ReferenceSpec::TestClassWithFinalize.new
158+
assert_finalizes("dup") { obj.dup }
159+
end
122160
end
123161

124-
it "clones with def_clone (recursive type)" do
125-
original = ReferenceSpec::DupCloneRecursiveClass.new
126-
clone = original.clone
127-
clone.should_not be(original)
128-
clone.x.should eq(original.x)
129-
clone.y.should_not be(original.y)
130-
clone.y.should eq(original.y)
131-
clone.z.should be(clone)
162+
describe "#clone" do
163+
it "clones with def_clone" do
164+
original = ReferenceSpec::DupCloneClass.new
165+
clone = original.clone
166+
clone.should_not be(original)
167+
clone.x.should eq(original.x)
168+
expect_empty_recursive_hashes
169+
end
170+
171+
it "clones with def_clone (recursive type)" do
172+
original = ReferenceSpec::DupCloneRecursiveClass.new
173+
clone = original.clone
174+
clone.should_not be(original)
175+
clone.x.should eq(original.x)
176+
clone.y.should_not be(original.y)
177+
clone.y.should eq(original.y)
178+
clone.z.should be(clone)
179+
expect_empty_recursive_hashes
180+
end
181+
182+
it "handles error" do
183+
r = ReferenceSpec::NestedErrorTestClass.new
184+
expect_raises RuntimeError, "I'm unique" do
185+
r.clone
186+
end
187+
expect_empty_recursive_hashes
188+
end
132189
end
133190

134191
it "pretty_print" do
135192
ReferenceSpec::TestClassBase.new.pretty_inspect.should match(/\A#<ReferenceSpec::TestClassBase:0x[0-9a-f]+>\Z/)
136193
ReferenceSpec::TestClass.new(42, "foo").pretty_inspect.should match(/\A#<ReferenceSpec::TestClass:0x[0-9a-f]+ @x=42, @y="foo">\Z/)
137-
end
138-
139-
it "calls #finalize on #dup'ed objects" do
140-
obj = ReferenceSpec::TestClassWithFinalize.new
141-
assert_finalizes("dup") { obj.dup }
194+
expect_empty_recursive_hashes
142195
end
143196
end

src/reference.cr

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,8 +182,9 @@ class Reference
182182
key = {object_id, method}
183183
hash.put(key, nil) do
184184
yield
185-
hash.delete(key)
186185
return true
186+
ensure
187+
hash.delete(key)
187188
end
188189
false
189190
end
@@ -209,9 +210,9 @@ class Reference
209210
private def exec_recursive_clone(&)
210211
# NOTE: can't use `Set` because of prelude require order
211212
hash = Fiber.current.exec_recursive_clone_hash
212-
clone_object_id = hash[object_id]?
213-
unless clone_object_id
214-
clone_object_id = yield(hash).object_id
213+
clone_object_id = hash.fetch(object_id) do
214+
yield(hash).object_id
215+
ensure
215216
hash.delete(object_id)
216217
end
217218
Pointer(Void).new(clone_object_id).as(self)

0 commit comments

Comments
 (0)