From 956f8cb8d417e8d789e05893f84f4c2e4021d7a3 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 2 Nov 2023 16:53:28 -0500 Subject: [PATCH 1/4] Implement fake pack 'p' behavior We can't provide an actual pointer, so this just encodes the identity hashcode as a long. --- core/src/main/java/org/jruby/util/Pack.java | 38 +++++++++++++++++++++ spec/tags/ruby/core/array/pack/p_tags.txt | 4 --- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/core/src/main/java/org/jruby/util/Pack.java b/core/src/main/java/org/jruby/util/Pack.java index 922e33bc323..dbf9c291b23 100644 --- a/core/src/main/java/org/jruby/util/Pack.java +++ b/core/src/main/java/org/jruby/util/Pack.java @@ -478,6 +478,44 @@ public void encode(Ruby runtime, IRubyObject o, ByteList result){ }; converters['q' + BE] = tmp; if (Platform.BIT_WIDTH == 64) converters['j' + BE] = tmp; + + // pointer; we can't provide a real pointer, so we just use identity hashcode + tmp = new QuadConverter(8) { + @Override + public IRubyObject decode(Ruby runtime, ByteBuffer format) { + return runtime.getNil(); + } + + @Override + public void encode(Ruby runtime, IRubyObject from, ByteList result) { + if (from.isNil()) { + encodeLongBigEndian(result, 0); + } else { + encodeLongBigEndian(result, System.identityHashCode(from)); + } + } + }; + + converters['p'] = tmp; + + // pointer; we can't provide a real pointer, so we just use identity hashcode + tmp = new QuadConverter(8) { + @Override + public IRubyObject decode(Ruby runtime, ByteBuffer format) { + return runtime.getNil(); + } + + @Override + public void encode(Ruby runtime, IRubyObject from, ByteList result) { + if (from.isNil()) { + encodeLongBigEndian(result, 0); + } else { + encodeLongBigEndian(result, System.identityHashCode(from.convertToString())); + } + } + }; + + converters['P'] = tmp; } public static int unpackInt_i(ByteBuffer enc) { diff --git a/spec/tags/ruby/core/array/pack/p_tags.txt b/spec/tags/ruby/core/array/pack/p_tags.txt index e3c7e8641da..119c2aafe78 100644 --- a/spec/tags/ruby/core/array/pack/p_tags.txt +++ b/spec/tags/ruby/core/array/pack/p_tags.txt @@ -1,6 +1,2 @@ fails:Array#pack with format 'P' round-trips a string through pack and unpack -fails:Array#pack with format 'P' with nil gives a null pointer fails:Array#pack with format 'p' round-trips a string through pack and unpack -fails:Array#pack with format 'p' with nil gives a null pointer -fails:Array#pack with format 'P' produces as many bytes as there are in a pointer -fails:Array#pack with format 'p' produces as many bytes as there are in a pointer From b3b4fb5510cba3fa14b859464b5c8ca5c05f7df2 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 2 Nov 2023 21:04:42 -0500 Subject: [PATCH 2/4] Fixes for iterative methods when adding elts We can't assume the size will remain the same throughout the loop, so these all need to check it each iteration. This allows items to be added during the loop and still get picked up. Fixes a handful of rubyspec tags. --- core/src/main/java/org/jruby/RubyArray.java | 38 +++++++------ .../main/java/org/jruby/RubyEnumerable.java | 55 +++++++------------ spec/tags/ruby/core/array/collect_tags.txt | 2 - spec/tags/ruby/core/array/filter_tags.txt | 1 - spec/tags/ruby/core/array/keep_if_tags.txt | 1 - spec/tags/ruby/core/array/map_tags.txt | 2 - spec/tags/ruby/core/array/reject_tags.txt | 1 - spec/tags/ruby/core/array/select_tags.txt | 1 - spec/tags/ruby/core/array/sort_by_tags.txt | 1 - spec/tags/ruby/core/array/to_h_tags.txt | 1 - 10 files changed, 39 insertions(+), 64 deletions(-) delete mode 100644 spec/tags/ruby/core/array/collect_tags.txt delete mode 100644 spec/tags/ruby/core/array/filter_tags.txt delete mode 100644 spec/tags/ruby/core/array/keep_if_tags.txt delete mode 100644 spec/tags/ruby/core/array/map_tags.txt delete mode 100644 spec/tags/ruby/core/array/reject_tags.txt delete mode 100644 spec/tags/ruby/core/array/select_tags.txt delete mode 100644 spec/tags/ruby/core/array/sort_by_tags.txt delete mode 100644 spec/tags/ruby/core/array/to_h_tags.txt diff --git a/core/src/main/java/org/jruby/RubyArray.java b/core/src/main/java/org/jruby/RubyArray.java index 6bd4ce03b8d..8dcc59cdcd7 100644 --- a/core/src/main/java/org/jruby/RubyArray.java +++ b/core/src/main/java/org/jruby/RubyArray.java @@ -2243,30 +2243,29 @@ public IRubyObject to_h(ThreadContext context) { @JRubyMethod(name = "to_h") public IRubyObject to_h(ThreadContext context, Block block) { Ruby runtime = context.runtime; - int realLength = this.realLength; boolean useSmallHash = realLength <= 10; RubyHash hash = useSmallHash ? RubyHash.newSmallHash(runtime) : RubyHash.newHash(runtime); for (int i = 0; i < realLength; i++) { - IRubyObject e = eltInternal(i); + IRubyObject e = eltOk(i); IRubyObject elt = block.isGiven() ? block.yield(context, e) : e; IRubyObject key_value_pair = elt.checkArrayType(); if (key_value_pair == context.nil) { - throw context.runtime.newTypeError("wrong element type " + elt.getMetaClass().getRealClass() + " at " + i + " (expected array)"); + throw runtime.newTypeError("wrong element type " + elt.getMetaClass().getRealClass() + " at " + i + " (expected array)"); } RubyArray ary = (RubyArray)key_value_pair; if (ary.getLength() != 2) { - throw context.runtime.newArgumentError("wrong array length at " + i + " (expected 2, was " + ary.getLength() + ")"); + throw runtime.newArgumentError("wrong array length at " + i + " (expected 2, was " + ary.getLength() + ")"); } if (useSmallHash) { - hash.fastASetSmall(runtime, ary.eltInternal(0), ary.eltInternal(1), true); + hash.fastASetSmall(runtime, ary.eltOk(0), ary.eltOk(1), true); } else { - hash.fastASet(runtime, ary.eltInternal(0), ary.eltInternal(1), true); + hash.fastASet(runtime, ary.eltOk(0), ary.eltOk(1), true); } } return hash; @@ -2770,17 +2769,16 @@ public RubyArray collectArray(ThreadContext context, Block block) { final Ruby runtime = context.runtime; - IRubyObject[] arr = IRubyObject.array(realLength); + RubyArray ary = RubyArray.newArray(runtime, realLength); int i = 0; for (; i < realLength; i++) { // Do not coarsen the "safe" check, since it will misinterpret AIOOBE from the yield // See JRUBY-5434 - safeArraySet(runtime, arr, i, block.yieldNonArray(context, eltOk(i), null)); // arr[i] = ... + ary.store(i, block.yieldNonArray(context, eltOk(i), null)); // arr[i] = ... } - // use iteration count as new size in case something was deleted along the way - return newArrayMayCopy(context.runtime, arr, 0, i); + return ary; } /** @@ -2820,7 +2818,7 @@ public RubyArray collectBang(ThreadContext context, Block block) { if (!block.isGiven()) throw context.runtime.newLocalJumpErrorNoBlock(); modify(); - for (int i = 0, len = realLength; i < len; i++) { + for (int i = 0; i < realLength; i++) { // Do not coarsen the "safe" check, since it will misinterpret AIOOBE from the yield // See JRUBY-5434 storeInternal(i, block.yield(context, eltOk(i))); @@ -2901,23 +2899,21 @@ public IRubyObject select_bang(ThreadContext context, Block block) { boolean modified = false; final Ruby runtime = context.runtime; - final int len = realLength; final int beg = begin; int len0 = 0, len1 = 0; try { int i1, i2; - for (i1 = i2 = 0; i1 < len; len0 = ++i1) { - final IRubyObject[] values = this.values; + for (i1 = i2 = 0; i1 < realLength; len0 = ++i1) { // Do not coarsen the "safe" check, since it will misinterpret // AIOOBE from the yield (see JRUBY-5434) - IRubyObject value = safeArrayRef(runtime, values, begin + i1); + IRubyObject value = eltOk(i1); if (!block.yield(context, value).isTrue()) { modified = true; continue; } - if (i1 != i2) safeArraySet(runtime, values, beg + i2, value); + if (i1 != i2) eltSetOk(i2, (T) value); len1 = ++i2; } return (i1 == i2) ? context.nil : this; @@ -3038,8 +3034,14 @@ public IRubyObject delete_at(IRubyObject obj) { * */ public final IRubyObject rejectCommon(ThreadContext context, Block block) { - RubyArray ary = aryDup(); - ary.rejectBang(context, block); + RubyArray ary = RubyArray.newArray(context.runtime); + + for (int i = 0; i < realLength; i++) { + IRubyObject v = eltOk(i); + if (!block.yieldSpecific(context, v).isTrue()) { + ary.push(v); + } + } return ary; } diff --git a/core/src/main/java/org/jruby/RubyEnumerable.java b/core/src/main/java/org/jruby/RubyEnumerable.java index b8eed6abef4..6939961a99a 100644 --- a/core/src/main/java/org/jruby/RubyEnumerable.java +++ b/core/src/main/java/org/jruby/RubyEnumerable.java @@ -61,6 +61,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; @@ -496,55 +497,37 @@ public static IRubyObject sort(ThreadContext context, IRubyObject self, final Bl @JRubyMethod public static IRubyObject sort_by(final ThreadContext context, IRubyObject self, final Block block) { final Ruby runtime = context.runtime; - DoubleObject[] valuesAndCriteria; + List> valuesAndCriteria; if (!block.isGiven()) { return enumeratorizeWithSize(context, self, "sort_by", (SizeFn) RubyEnumerable::size); } final CachingCallSite each = eachSite(context); - if (self instanceof RubyArray) { - RubyArray selfArray = (RubyArray) self; - final DoubleObject[] valuesAndCriteriaArray = new DoubleObject[selfArray.size()]; + final ArrayList> valuesAndCriteriaList = new ArrayList<>(); - each(context, each, self, new JavaInternalBlockBody(runtime, Signature.OPTIONAL) { - final AtomicInteger i = new AtomicInteger(0); - @Override - public IRubyObject yield(ThreadContext context1, IRubyObject[] args) { - return doYield(context1, null, packEnumValues(context1, args)); - } - @Override - protected IRubyObject doYield(ThreadContext context1, Block unused, IRubyObject value) { - valuesAndCriteriaArray[i.getAndIncrement()] = new DoubleObject<>(value, block.yield(context1, value)); - return context1.nil; - } - }); - - valuesAndCriteria = valuesAndCriteriaArray; - } else { - final ArrayList> valuesAndCriteriaList = new ArrayList<>(); - - callEach(context, each, self, Signature.OPTIONAL, new BlockCallback() { - public IRubyObject call(ThreadContext context1, IRubyObject[] args, Block unused) { - return call(context1, packEnumValues(context1, args), unused); - } - @Override - public IRubyObject call(ThreadContext context1, IRubyObject arg, Block unused) { - valuesAndCriteriaList.add(new DoubleObject<>(arg, block.yield(context1, arg))); - return context1.nil; + callEach(context, each, self, Signature.OPTIONAL, new BlockCallback() { + public IRubyObject call(ThreadContext context1, IRubyObject[] args, Block unused) { + return call(context1, packEnumValues(context1, args), unused); + } + @Override + public IRubyObject call(ThreadContext context1, IRubyObject arg, Block unused) { + IRubyObject value = block.yield(context1, arg); + synchronized (valuesAndCriteriaList) { + valuesAndCriteriaList.add(new DoubleObject<>(arg, value)); } - }); + return context1.nil; + } + }); - valuesAndCriteria = valuesAndCriteriaList.toArray(new DoubleObject[valuesAndCriteriaList.size()]); - } + valuesAndCriteria = valuesAndCriteriaList; - Arrays.sort(valuesAndCriteria, + Collections.sort(valuesAndCriteria, (o1, o2) -> RubyComparable.cmpint(context, invokedynamic(context, o1.object2, OP_CMP, o2.object2), o1.object2, o2.object2)); - - IRubyObject dstArray[] = new IRubyObject[valuesAndCriteria.length]; + IRubyObject dstArray[] = new IRubyObject[valuesAndCriteria.size()]; for (int i = 0; i < dstArray.length; i++) { - dstArray[i] = valuesAndCriteria[i].object1; + dstArray[i] = valuesAndCriteria.get(i).object1; } return RubyArray.newArrayMayCopy(runtime, dstArray); diff --git a/spec/tags/ruby/core/array/collect_tags.txt b/spec/tags/ruby/core/array/collect_tags.txt deleted file mode 100644 index 851556fd413..00000000000 --- a/spec/tags/ruby/core/array/collect_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Array#collect tolerates increasing an array size during iteration -fails:Array#collect! tolerates increasing an array size during iteration diff --git a/spec/tags/ruby/core/array/filter_tags.txt b/spec/tags/ruby/core/array/filter_tags.txt deleted file mode 100644 index 39c9774512e..00000000000 --- a/spec/tags/ruby/core/array/filter_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Array#filter! tolerates increasing an array size during iteration diff --git a/spec/tags/ruby/core/array/keep_if_tags.txt b/spec/tags/ruby/core/array/keep_if_tags.txt deleted file mode 100644 index f3922b6ee78..00000000000 --- a/spec/tags/ruby/core/array/keep_if_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Array#keep_if tolerates increasing an array size during iteration diff --git a/spec/tags/ruby/core/array/map_tags.txt b/spec/tags/ruby/core/array/map_tags.txt deleted file mode 100644 index 697bf207bae..00000000000 --- a/spec/tags/ruby/core/array/map_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:Array#map tolerates increasing an array size during iteration -fails:Array#map! tolerates increasing an array size during iteration diff --git a/spec/tags/ruby/core/array/reject_tags.txt b/spec/tags/ruby/core/array/reject_tags.txt deleted file mode 100644 index 9e575149bd8..00000000000 --- a/spec/tags/ruby/core/array/reject_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Array#reject tolerates increasing an array size during iteration diff --git a/spec/tags/ruby/core/array/select_tags.txt b/spec/tags/ruby/core/array/select_tags.txt deleted file mode 100644 index 6c1d81fccf0..00000000000 --- a/spec/tags/ruby/core/array/select_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Array#select! tolerates increasing an array size during iteration diff --git a/spec/tags/ruby/core/array/sort_by_tags.txt b/spec/tags/ruby/core/array/sort_by_tags.txt deleted file mode 100644 index 2ddaecccf03..00000000000 --- a/spec/tags/ruby/core/array/sort_by_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Array#sort_by! tolerates increasing an array size during iteration diff --git a/spec/tags/ruby/core/array/to_h_tags.txt b/spec/tags/ruby/core/array/to_h_tags.txt deleted file mode 100644 index 76bb1e91335..00000000000 --- a/spec/tags/ruby/core/array/to_h_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:Array#to_h tolerates increasing an array size during iteration From 3b72d5282781c21ff1761275bf7202d642bd8a30 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 2 Nov 2023 21:18:21 -0500 Subject: [PATCH 3/4] Also add notImplemented flag to anno CRuby uses its rb_f_notimplement marker when defining Dir.chroot in an environment that does not support it. This is equivalent. --- core/src/main/java/org/jruby/RubyDir.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/jruby/RubyDir.java b/core/src/main/java/org/jruby/RubyDir.java index f091c0f0bc2..475908429a0 100644 --- a/core/src/main/java/org/jruby/RubyDir.java +++ b/core/src/main/java/org/jruby/RubyDir.java @@ -469,7 +469,7 @@ public static IRubyObject chdir(ThreadContext context, IRubyObject recv, IRubyOb /** * Changes the root directory (only allowed by super user). Not available on all platforms. */ - @JRubyMethod(name = "chroot", required = 1, meta = true) + @JRubyMethod(name = "chroot", required = 1, meta = true, notImplemented = true) public static IRubyObject chroot(IRubyObject recv, IRubyObject path) { throw recv.getRuntime().newNotImplementedError("chroot not implemented: chroot is non-portable and is not supported."); } From d9359bc691640657acafdda573db2162ed2c7dd8 Mon Sep 17 00:00:00 2001 From: Charles Oliver Nutter Date: Thu, 2 Nov 2023 21:24:58 -0500 Subject: [PATCH 4/4] Actually try to coerce arg with to_str --- core/src/main/java/org/jruby/RubyGlobal.java | 5 +++-- spec/tags/ruby/core/env/delete_tags.txt | 1 - spec/tags/ruby/core/env/has_key_tags.txt | 1 - spec/tags/ruby/core/env/include_tags.txt | 1 - spec/tags/ruby/core/env/key_tags.txt | 2 -- spec/tags/ruby/core/env/member_tags.txt | 1 - 6 files changed, 3 insertions(+), 8 deletions(-) delete mode 100644 spec/tags/ruby/core/env/delete_tags.txt delete mode 100644 spec/tags/ruby/core/env/has_key_tags.txt delete mode 100644 spec/tags/ruby/core/env/include_tags.txt delete mode 100644 spec/tags/ruby/core/env/key_tags.txt delete mode 100644 spec/tags/ruby/core/env/member_tags.txt diff --git a/core/src/main/java/org/jruby/RubyGlobal.java b/core/src/main/java/org/jruby/RubyGlobal.java index 780dfa1c180..2f61a394e9d 100644 --- a/core/src/main/java/org/jruby/RubyGlobal.java +++ b/core/src/main/java/org/jruby/RubyGlobal.java @@ -730,11 +730,12 @@ private IRubyObject case_aware_op_aset(ThreadContext context, IRubyObject key, f } protected static IRubyObject verifyStringLike(ThreadContext context, IRubyObject test) { - if (!isStringLike(test)) { + IRubyObject string = test.checkStringType(); + if (string.isNil()) { throw context.runtime.newTypeError("no implicit conversion of " + test.getMetaClass() + " into String"); } - return test; + return string; } private static RubyString verifyValidKey(ThreadContext context, RubyString key, IRubyObject value) { diff --git a/spec/tags/ruby/core/env/delete_tags.txt b/spec/tags/ruby/core/env/delete_tags.txt deleted file mode 100644 index 9e6952780b2..00000000000 --- a/spec/tags/ruby/core/env/delete_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:ENV.delete removes the variable coerced with #to_str diff --git a/spec/tags/ruby/core/env/has_key_tags.txt b/spec/tags/ruby/core/env/has_key_tags.txt deleted file mode 100644 index 61a8b340704..00000000000 --- a/spec/tags/ruby/core/env/has_key_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:ENV.has_key? coerces the key with #to_str diff --git a/spec/tags/ruby/core/env/include_tags.txt b/spec/tags/ruby/core/env/include_tags.txt deleted file mode 100644 index 0a97b1b2683..00000000000 --- a/spec/tags/ruby/core/env/include_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:ENV.include? coerces the key with #to_str diff --git a/spec/tags/ruby/core/env/key_tags.txt b/spec/tags/ruby/core/env/key_tags.txt deleted file mode 100644 index 59714a50cb4..00000000000 --- a/spec/tags/ruby/core/env/key_tags.txt +++ /dev/null @@ -1,2 +0,0 @@ -fails:ENV.key? coerces the key with #to_str -fails:ENV.key coerces the key element with #to_str diff --git a/spec/tags/ruby/core/env/member_tags.txt b/spec/tags/ruby/core/env/member_tags.txt deleted file mode 100644 index fa4125fc08e..00000000000 --- a/spec/tags/ruby/core/env/member_tags.txt +++ /dev/null @@ -1 +0,0 @@ -fails:ENV.member? coerces the key with #to_str