Skip to content

Commit 628e973

Browse files
itaratonirvdrum
andcommitted
Fix singleton class lookup bug when singleton class is replaced on the instance.
Co-authored-by: Kevin Menard <[email protected]> Co-authored-by: Peter Arato <[email protected]>
1 parent 3e9d8d8 commit 628e973

File tree

5 files changed

+30
-2
lines changed

5 files changed

+30
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ New features:
66
Bug fixes:
77

88
* Fix `Dir.glob` returning blank string entry with leading `**/` in glob and `base:` argument (@rwstauner).
9+
* Fixing singleton class lookup after an object's singleton class has been replaced (@itarato).
910

1011
Compatibility:
1112

spec/ruby/language/method_spec.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,22 @@ def m(x)
176176
m('a', &nil).should == [false]
177177
end
178178
end
179+
180+
context "with a replaced singleton class" do
181+
it "looks up singleton methods from the fresh singleton class after an object instance got a new one" do
182+
require "socket"
183+
proxy = -> (io) { io.foo }
184+
185+
io = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM)
186+
io.define_singleton_method(:foo) { "old" }
187+
proxy.call(io).should == "old"
188+
189+
# `IO#reopen` is an example where an object's singleton class is replaced.
190+
io.reopen(Socket.new(Socket::AF_INET, Socket::SOCK_STREAM))
191+
io.define_singleton_method(:foo) { "new" }
192+
proxy.call(io).should == "new"
193+
end
194+
end
179195
end
180196

181197
describe "An element assignment method send" do

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
import org.truffleruby.core.proc.ProcOperations;
6969
import org.truffleruby.core.proc.RubyProc;
7070
import org.truffleruby.core.string.RubyString;
71+
import org.truffleruby.core.support.RubyIO;
7172
import org.truffleruby.core.symbol.RubySymbol;
7273
import org.truffleruby.core.thread.RubyThread;
7374
import org.truffleruby.extra.ffi.Pointer;
@@ -425,7 +426,7 @@ public abstract static class VMSetClassNode extends PrimitiveArrayArgumentsNode
425426

426427
@TruffleBoundary
427428
@Specialization
428-
protected RubyDynamicObject setClass(RubyDynamicObject object, RubyClass newClass) {
429+
protected RubyDynamicObject setClass(RubyIO object, RubyClass newClass) {
429430
SharedObjects.propagate(getLanguage(), object, newClass);
430431
synchronized (object) {
431432
object.setMetaClass(newClass);

src/main/java/org/truffleruby/language/RubyGuards.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.truffleruby.core.regexp.RubyRegexp;
3434
import org.truffleruby.core.string.ImmutableRubyString;
3535
import org.truffleruby.core.string.RubyString;
36+
import org.truffleruby.core.support.RubyIO;
3637
import org.truffleruby.core.symbol.RubySymbol;
3738
import org.truffleruby.interop.ToJavaStringNode;
3839
import org.truffleruby.language.arguments.ArgumentsDescriptor;
@@ -137,6 +138,11 @@ public static boolean isRubyModule(Object value) {
137138
return value instanceof RubyModule;
138139
}
139140

141+
@Idempotent
142+
public static boolean isRubyIO(Object value) {
143+
return value instanceof RubyIO;
144+
}
145+
140146
public static boolean isRubyRegexp(Object value) {
141147
return value instanceof RubyRegexp;
142148
}

src/main/java/org/truffleruby/language/objects/MetaClassNode.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ public final RubyClass executeCached(Object value) {
4242
public abstract RubyClass execute(Node node, Object value);
4343

4444
@Specialization(
45-
guards = { "isSingleContext()", "object == cachedObject", "metaClass.isSingleton" },
45+
guards = {
46+
"isSingleContext()",
47+
"object == cachedObject",
48+
"metaClass.isSingleton",
49+
"!isRubyIO(cachedObject)" },
4650
limit = "1")
4751
protected static RubyClass singleton(Node node, RubyDynamicObject object,
4852
@Cached("object") RubyDynamicObject cachedObject,

0 commit comments

Comments
 (0)