Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: jruby/jruby
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: c28f62a2d877
Choose a base ref
...
head repository: jruby/jruby
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 9334a2b4adb4
Choose a head ref
  • 3 commits
  • 3 files changed
  • 1 contributor

Commits on May 12, 2016

  1. Re-port Array#hash and some related logic to fix #3884.

    It appears MRI defers recursion detection to the elements of an
    array and has other logic for combining hashes of those elements.
    I also added better initial hash value similar to MRI's use of a
    pointer in memory, which appears to be the main thing helping this
    issue, since now the hash of an empty array is a useful number.
    headius committed May 12, 2016
    Copy the full SHA
    1b92064 View commit details
  2. Add a spec for hashing a double-nested array with inner zarray.

    Relates to #3884, where JRuby
    was always returning the same hash for certain combinations of
    nested array with an innermost empty array.
    headius committed May 12, 2016
    2
    Copy the full SHA
    ac53c54 View commit details
  3. Clean up imports.

    headius committed May 12, 2016
    Copy the full SHA
    9334a2b View commit details
Showing with 38 additions and 20 deletions.
  1. +12 −20 core/src/main/java/org/jruby/RubyArray.java
  2. +22 −0 core/src/main/java/org/jruby/runtime/Helpers.java
  3. +4 −0 spec/ruby/core/array/hash_spec.rb
32 changes: 12 additions & 20 deletions core/src/main/java/org/jruby/RubyArray.java
Original file line number Diff line number Diff line change
@@ -37,7 +37,6 @@
***** END LICENSE BLOCK *****/
package org.jruby;

import org.jcodings.Encoding;
import org.jcodings.specific.USASCIIEncoding;
import org.jruby.anno.JRubyClass;
import org.jruby.anno.JRubyMethod;
@@ -79,7 +78,6 @@
import static org.jruby.RubyEnumerator.enumeratorizeWithSize;
import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.Visibility.PRIVATE;
import static org.jruby.runtime.invokedynamic.MethodNames.HASH;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_CMP;
import static org.jruby.RubyEnumerator.SizeFn;

@@ -677,24 +675,18 @@ public RubyFixnum hash19(ThreadContext context) {
*
*/
@JRubyMethod(name = "hash")
public RubyFixnum hash(final ThreadContext context) {
return (RubyFixnum) context.runtime.execRecursiveOuter(new Ruby.RecursiveFunction() {
public IRubyObject call(IRubyObject obj, boolean recur) {
final Ruby runtime = context.runtime;
int begin = RubyArray.this.begin;
long h = realLength;
if (recur) {
h ^= RubyNumeric.num2long(invokedynamic(context, runtime.getArray(), HASH));
} else {
for (int i = begin; i < begin + realLength; i++) {
h = (h << 1) | (h < 0 ? 1 : 0);
final IRubyObject value = safeArrayRef(runtime, values, i);
h ^= RubyNumeric.num2long(invokedynamic(context, value, HASH));
}
}
return runtime.newFixnum(h);
}
}, RubyArray.this);
public RubyFixnum hash(ThreadContext context) {
final Ruby runtime = context.runtime;
int begin = RubyArray.this.begin;
long h = (realLength << 32) & System.identityHashCode(RubyArray.class);
for (int i = begin; i < begin + realLength; i++) {
h = (h << 1) | (h < 0 ? 1 : 0);
final IRubyObject value = safeArrayRef(runtime, values, i);
RubyFixnum n = Helpers.safeHash(context, value);
h = (h * 31) + n.getLongValue();
}

return runtime.newFixnum(h);
}

/** rb_ary_store
22 changes: 22 additions & 0 deletions core/src/main/java/org/jruby/runtime/Helpers.java
Original file line number Diff line number Diff line change
@@ -48,7 +48,9 @@
import org.jcodings.specific.UTF8Encoding;
import org.jcodings.unicode.UnicodeEncoding;

import static org.jruby.runtime.Helpers.invokedynamic;
import static org.jruby.runtime.invokedynamic.MethodNames.EQL;
import static org.jruby.runtime.invokedynamic.MethodNames.HASH;
import static org.jruby.runtime.invokedynamic.MethodNames.OP_EQUAL;
import static org.jruby.util.CodegenUtils.sig;
import static org.jruby.util.StringSupport.EMPTY_STRING_ARRAY;
@@ -2714,6 +2716,26 @@ public static boolean isRequiredKeywordArgumentValueNode(Node asgnNode) {
return asgnNode.childNodes().get(0) instanceof RequiredKeywordArgumentValueNode;
}

// MRI: rb_hash
public static RubyFixnum safeHash(final ThreadContext context, IRubyObject obj) {
final Ruby runtime = context.runtime;
IRubyObject hval = runtime.execRecursiveOuter(new Ruby.RecursiveFunction() {
public IRubyObject call(IRubyObject obj, boolean recur) {
if (recur) return RubyFixnum.zero(runtime);
return invokedynamic(context, obj, HASH);
}
}, obj);

while (!(hval instanceof RubyFixnum)) {
if (hval instanceof RubyBignum) {
// This is different from MRI because we don't have rb_integer_pack
return ((RubyBignum) hval).hash();
}
hval = hval.convertToInteger();
}
return (RubyFixnum) hval;
}

@Deprecated
public static String encodeParameterList(List<String[]> args) {
if (args.size() == 0) return "NONE";
4 changes: 4 additions & 0 deletions spec/ruby/core/array/hash_spec.rb
Original file line number Diff line number Diff line change
@@ -79,4 +79,8 @@ def obj.hash() @hash end
a.hash.should == b.hash
a.should eql(b)
end

it "produces different hashes for nested arrays with different values and empty terminator" do
[1, [1, []]].hash.should_not == [2, [2, []]]
end
end