Skip to content

Commit 3c7e171

Browse files
committed
[GR-19220] Refactor detect_recursion (#2114)
PullRequest: truffleruby/2072
2 parents 9e00c2b + 2de0551 commit 3c7e171

File tree

17 files changed

+499
-153
lines changed

17 files changed

+499
-153
lines changed

lib/truffle/truffle/cext.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1508,7 +1508,7 @@ def rb_rescue2(b_proc, data1, r_proc, data2, rescued)
15081508
def rb_exec_recursive(func, obj, arg)
15091509
result = nil
15101510

1511-
recursive = Thread.detect_recursion(obj) do
1511+
recursive = Truffle::ThreadOperations.detect_recursion(obj) do
15121512
result = Primitive.cext_unwrap(Primitive.call_with_c_mutex(func, [Primitive.cext_wrap(obj), Primitive.cext_wrap(arg), 0]))
15131513
end
15141514

Lines changed: 249 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,249 @@
1+
# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved. This
2+
# code is released under a tri EPL/GPL/LGPL license. You can use it,
3+
# redistribute it and/or modify it under the terms of the:
4+
#
5+
# Eclipse Public License version 2.0, or
6+
# GNU General Public License version 2, or
7+
# GNU Lesser General Public License version 2.1.
8+
9+
require_relative '../../ruby/spec_helper'
10+
11+
module TruffleThreadDetectRecursionSpecFixtures
12+
def self.check_recursion_to_depth(obj, depth)
13+
# checks that obj recurses to a given depth
14+
return false unless obj.respond_to?(:each)
15+
Truffle::ThreadOperations.detect_recursion(obj) do
16+
if depth > 1
17+
obj.each do |el|
18+
if check_recursion_to_depth(el, depth-1)
19+
return true
20+
end
21+
end
22+
end
23+
end
24+
end
25+
26+
def self.check_double_recursion_equality_to_depth(obj1, obj2, depth)
27+
# checks that obj1 and obj2 are both recursive and equal structurally
28+
# (because detect_pair_recursion on two objects is only used during object comparison,
29+
# and aborts after inequality is discovered)
30+
return false unless obj1.class == obj2.class
31+
return false unless obj1.respond_to?(:each)
32+
return false unless obj1.size == obj2.size
33+
34+
Truffle::ThreadOperations.detect_pair_recursion(obj1, obj2) do
35+
if depth > 1
36+
if obj1.class == Hash
37+
obj1.each do |key, val|
38+
return false unless obj2.has_key?(key)
39+
if check_double_recursion_equality_to_depth(val, obj2[key], depth-1)
40+
return true
41+
end
42+
end
43+
else
44+
obj1.size.times do |i|
45+
if check_double_recursion_equality_to_depth(obj1[i], obj2[i], depth-1)
46+
return true
47+
end
48+
end
49+
end
50+
end
51+
end
52+
end
53+
end
54+
55+
describe "Thread#detect_recursion" do
56+
57+
describe "for empty arrays" do
58+
it "returns false" do
59+
a = []
60+
10.times do |i|
61+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
62+
end
63+
end
64+
end
65+
66+
describe "for empty hashes" do
67+
it "returns false" do
68+
a = {}
69+
10.times do |i|
70+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
71+
end
72+
end
73+
end
74+
75+
describe "for single arrays" do
76+
it "for non-recursive arrays returns false" do
77+
a = [1,[2,[3], 4],[[[5,6,7]]]]
78+
79+
10.times do |i|
80+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
81+
end
82+
end
83+
84+
it "for recursive arrays returns true after sufficient depth to detect recursion" do
85+
a = []
86+
a << [[[a]]]
87+
88+
b = []
89+
b << [1,[2,[3,b],4],5]
90+
91+
10.times do |i|
92+
if i < 5
93+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
94+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(b, i).should be_false
95+
else
96+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_true
97+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(b, i).should be_true
98+
end
99+
end
100+
end
101+
end
102+
103+
describe "for single hashes" do
104+
it "for non-recursive hashes returns false" do
105+
a = {:q => {:w => "qwe" }, :t => {:q => {:w => "qwe" }, :t => {:q => {:w => "qwe" }}}}
106+
107+
10.times do |i|
108+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
109+
end
110+
end
111+
112+
it "for recursive hashes returns true after sufficient depth to detect recursion" do
113+
a = {:q => {:w => "qwe" }}
114+
a[:t] = a
115+
116+
10.times do |i|
117+
if i < 3
118+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
119+
else
120+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_true
121+
end
122+
end
123+
end
124+
end
125+
126+
describe "for single structs" do
127+
it "for recursive structs returns true after sufficient depth to detect recursion" do
128+
car = Struct.new(:make, :model, :year)
129+
a = car.new("Honda", "Accord", "1998")
130+
a[:make] = a
131+
132+
10.times do |i|
133+
if i < 2
134+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
135+
else
136+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_true
137+
end
138+
end
139+
end
140+
end
141+
142+
describe "for single mixtures of types" do
143+
it "for recursive structs returns true after sufficient depth to detect recursion" do
144+
car = Struct.new(:make, :model, :year)
145+
a = car.new("Honda", "Accord", "1998")
146+
a[:make] = [{:car_make => ["a mess", {:here => a}]}]
147+
148+
10.times do |i|
149+
if i < 8
150+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_false
151+
else
152+
TruffleThreadDetectRecursionSpecFixtures.check_recursion_to_depth(a, i).should be_true
153+
end
154+
end
155+
end
156+
end
157+
158+
describe "for a pair of arrays" do
159+
it "returns false when structure differs" do
160+
a = []
161+
a << a
162+
163+
c = [[[[[[[[[a,1]]]]]]]]]
164+
165+
10.times do |i|
166+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, c, i).should be_false
167+
end
168+
end
169+
170+
it "returns true after sufficient depth to detect recursion and equivalent structure" do
171+
a = []
172+
a << a
173+
174+
b = []
175+
b << [[[[[[b]]]]]]
176+
177+
10.times do |i|
178+
if i < 8
179+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_false
180+
else
181+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_true
182+
end
183+
end
184+
end
185+
end
186+
187+
describe "for a pair of hashes" do
188+
it "returns false when structure differs" do
189+
a = {:q => {:w => "qwe" }}
190+
a[:t] = a
191+
192+
b = {:q => {:w => "qwe" }, :t => {:t => {:w => "qwe" }, :q => a}}
193+
194+
10.times do |i|
195+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_false
196+
end
197+
end
198+
199+
it "returns true after sufficient depth to detect recursion and equivalent structure" do
200+
a = {:q => {:w => "qwe" }}
201+
a[:t] = a
202+
203+
b = {:q => {:w => "qwe" }, :t => {:q => {:w => "qwe" }, :t => a}}
204+
205+
10.times do |i|
206+
if i < 4
207+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_false
208+
else
209+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_true
210+
end
211+
end
212+
end
213+
end
214+
215+
describe "for a pair of structs" do
216+
it "returns true after sufficient depth to detect recursion and equivalent structure" do
217+
car = Struct.new(:make, :model, :year)
218+
a = car.new("Honda", "Accord", "1998")
219+
a[:make] = a
220+
b = car.new(a, "Accord", "1998")
221+
222+
10.times do |i|
223+
if i < 3
224+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_false
225+
else
226+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_true
227+
end
228+
end
229+
end
230+
end
231+
232+
describe "for a pair of mixtures of types" do
233+
it "returns true after sufficient depth to detect recursion and equivalent structure" do
234+
car = Struct.new(:make, :model, :year)
235+
a = car.new("Honda", "Accord", "1998")
236+
a[:make] = [{:car_make => ["a mess", {:here => a}]}]
237+
b = car.new([{:car_make => ["a mess", {:here => a}]}], "Accord", "1998")
238+
239+
20.times do |i|
240+
if i < 11
241+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_false
242+
else
243+
TruffleThreadDetectRecursionSpecFixtures.check_double_recursion_equality_to_depth(a, b, i).should be_true
244+
end
245+
end
246+
end
247+
end
248+
249+
end

src/main/java/org/truffleruby/core/CoreLibrary.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1039,6 +1039,7 @@ public boolean isTruffleBootMainMethod(SharedMethodInfo info) {
10391039
"/core/truffle/exception_operations.rb",
10401040
"/core/truffle/feature_loader.rb",
10411041
"/core/truffle/gem_util.rb",
1042+
"/core/truffle/thread_operations.rb",
10421043
"/core/thread.rb",
10431044
"/core/true.rb",
10441045
"/core/type.rb",

src/main/ruby/truffleruby/core/array.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ def <=>(other)
8989

9090
total = other.size
9191

92-
Thread.detect_recursion self, other do
92+
Truffle::ThreadOperations.detect_pair_recursion self, other do
9393
i = 0
9494
count = Primitive.min(total, size)
9595

@@ -132,7 +132,7 @@ def ==(other)
132132

133133
return false unless size == other.size
134134

135-
Thread.detect_recursion self, other do
135+
Truffle::ThreadOperations.detect_pair_recursion self, other do
136136
i = 0
137137
total = size
138138

@@ -325,7 +325,7 @@ def eql?(other)
325325
return false unless other.kind_of?(Array)
326326
return false if size != other.size
327327

328-
Thread.detect_recursion self, other do
328+
Truffle::ThreadOperations.detect_pair_recursion self, other do
329329
i = 0
330330
each do |x|
331331
return false unless x.eql? other[i]
@@ -484,7 +484,7 @@ def hash
484484

485485
# If we've seen self, unwind back to the outer version
486486
if objects.key? id
487-
raise Thread::InnerRecursionDetected
487+
raise Truffle::ThreadOperations::InnerRecursionDetected
488488
end
489489

490490
# .. or compute the hash value like normal
@@ -508,7 +508,7 @@ def hash
508508
# An inner version will raise to return back here, indicating that
509509
# the whole structure is recursive. In which case, abandon most of
510510
# the work and return a simple hash value.
511-
rescue Thread::InnerRecursionDetected
511+
rescue Truffle::ThreadOperations::InnerRecursionDetected
512512
return size
513513
ensure
514514
objects.delete :__detect_outermost_recursion__
@@ -549,7 +549,7 @@ def inspect
549549
comma = ', '
550550
result = +'['
551551

552-
return +'[...]' if Thread.detect_recursion self do
552+
return +'[...]' if Truffle::ThreadOperations.detect_recursion self do
553553
each_with_index do |element, index|
554554
temp = Truffle::Type.rb_inspect(element)
555555
result.force_encoding(temp.encoding) if index == 0
@@ -568,7 +568,7 @@ def join(sep=nil)
568568
return ''.encode(Encoding::US_ASCII) if size == 0
569569

570570
out = +''
571-
raise ArgumentError, 'recursive array join' if Thread.detect_recursion self do
571+
raise ArgumentError, 'recursive array join' if Truffle::ThreadOperations.detect_recursion self do
572572
sep = Primitive.nil?(sep) ? $, : StringValue(sep)
573573

574574
# We've manually unwound the first loop entry for performance
@@ -1263,7 +1263,7 @@ def recursively_flatten(array, out, max_levels = -1)
12631263
end
12641264

12651265
max_levels -= 1
1266-
recursion = Thread.detect_recursion(array) do
1266+
recursion = Truffle::ThreadOperations.detect_recursion(array) do
12671267
array = Truffle::Type.coerce_to(array, Array, :to_ary)
12681268

12691269
i = 0

src/main/ruby/truffleruby/core/comparable.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ module Comparable
3030
def ==(other)
3131
return true if equal?(other)
3232

33-
return false if Thread.detect_recursion(self, other) do
33+
return false if Truffle::ThreadOperations.detect_pair_recursion(self, other) do
3434
unless comp = (self <=> other)
3535
return false
3636
end

src/main/ruby/truffleruby/core/enumerator.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -704,7 +704,7 @@ def rewind
704704
end
705705

706706
def inspect
707-
return "#<#{self.class.name}: ...>" if Thread.detect_recursion(self) do
707+
return "#<#{self.class.name}: ...>" if Truffle::ThreadOperations.detect_recursion(self) do
708708
return "#<#{self.class.name}: #{@enums}>"
709709
end
710710
end

src/main/ruby/truffleruby/core/file.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ def self.join(*args)
733733
when String
734734
first = first.dup
735735
when Array
736-
recursion = Thread.detect_recursion(first) do
736+
recursion = Truffle::ThreadOperations.detect_recursion(first) do
737737
first = join(*first)
738738
end
739739

@@ -754,7 +754,7 @@ def self.join(*args)
754754
when String
755755
value = el
756756
when Array
757-
recursion = Thread.detect_recursion(el) do
757+
recursion = Truffle::ThreadOperations.detect_recursion(el) do
758758
value = join(*el)
759759
end
760760

0 commit comments

Comments
 (0)