From f5bf1bab0b19bc2faf442e0f343444ff160fca95 Mon Sep 17 00:00:00 2001 From: Sokolov Yura aka funny_falcon Date: Sun, 25 Jun 2017 22:59:52 +0300 Subject: [PATCH] change computation of hash value. Prepare hash infrastructor to future change of hashing algrorithm to protect against Hash DoS. Class|Struct should define method `def hash(hasher)` and call `hasher << @ivar` inside. As an option, for speed, and for backward compatibility, `def hash` still could be implemented. It will be used for Hash of matched type. `Thread#hash` and `Signal#hash` is implemented as unseeded cause they are used before `StdHasher @@seed` is initialized. Hash::Hasher is default hasher that uses `hash(hasher)` and it is used as default seeded hasher. Also, number normalization for hashing introduced, ie rule 'equality forces hash equality' is forced (`a == b` => `a.hash == b.hash`). Normalization idea is borrowed from Python implementation. It fixes several issues with BigInt and BigFloat on 32bit platform, but not all issues. Fixes #4578 Fixes #3932 Prerequisite for #4557 Replaces #4581 Correlates with #4653 --- spec/std/big/big_float_spec.cr | 84 ++++++++++++- spec/std/big/big_int_spec.cr | 22 +++- spec/std/big/big_rational_spec.cr | 8 +- spec/std/bool_spec.cr | 5 +- spec/std/enum_spec.cr | 2 +- spec/std/hash_spec.cr | 4 +- spec/std/struct_spec.cr | 7 +- spec/std/time/span_spec.cr | 2 +- src/big/big_float.cr | 75 +++++++++++- src/big/big_int.cr | 39 ++++-- src/big/big_rational.cr | 38 +++++- src/big/lib_gmp.cr | 10 ++ src/bool.cr | 7 +- src/char.cr | 6 + src/class.cr | 5 +- src/compiler/crystal/syntax/ast.cr | 15 ++- src/enum.cr | 7 +- src/event/signal_handler.cr | 1 + src/float.cr | 62 ++++++++-- src/hash.cr | 27 +++-- src/hash/hasher.cr | 188 +++++++++++++++++++++++++++++ src/http/headers.cr | 13 +- src/indexable.cr | 12 +- src/int.cr | 36 +++++- src/json/any.cr | 5 - src/named_tuple.cr | 14 +-- src/nil.cr | 7 +- src/number.cr | 18 +++ src/number/hash_normalize.cr | 105 ++++++++++++++++ src/object.cr | 39 ++++-- src/prelude.cr | 1 + src/proc.cr | 5 +- src/reference.cr | 7 +- src/set.cr | 4 - src/signal.cr | 11 ++ src/string.cr | 13 +- src/struct.cr | 9 +- src/symbol.cr | 9 +- src/thread.cr | 6 + src/time.cr | 4 - src/tuple.cr | 11 +- src/xml/namespace.cr | 5 +- src/xml/node.cr | 5 +- src/xml/node_set.cr | 5 +- src/yaml/any.cr | 5 - 45 files changed, 804 insertions(+), 159 deletions(-) create mode 100644 src/hash/hasher.cr create mode 100644 src/number/hash_normalize.cr diff --git a/spec/std/big/big_float_spec.cr b/spec/std/big/big_float_spec.cr index f369a23af19b..7138355bf887 100644 --- a/spec/std/big/big_float_spec.cr +++ b/spec/std/big/big_float_spec.cr @@ -2,6 +2,66 @@ require "spec" require "big_float" describe "BigFloat" do + describe "new" do + bsi = "123456789012345678901" + bfsi = BigFloat.new(bsi) + bsf = "1234567890.12345678901" + bfsf = BigFloat.new(bsf) + it "new(String)" do + bfsi.to_s.should eq(bsi) + bfsf.to_s.should eq(bsf) + end + + it "new(BigInt)" do + bi = BigInt.new(bsi) + bfi = BigFloat.new(bi) + bfi.should eq(bfsi) + bfi.to_s.should eq(bsi) + end + + it "new(BigRational)" do + br = BigRational.new(1, 3) + bfr = BigFloat.new(br) + bf = BigFloat.new(1) / BigFloat.new(3) + bfr.should eq(bf) + end + + it "new(BigFloat)" do + bffi = BigFloat.new(bfsi) + bffi.should eq(bfsi) + bfff = BigFloat.new(bfsf) + bfff.should eq(bfsf) + end + + it "new(Int)" do + BigFloat.new(1_u8).to_s.should eq("1") + BigFloat.new(1_u16).to_s.should eq("1") + BigFloat.new(1_u32).to_s.should eq("1") + BigFloat.new(1_u64).to_s.should eq("1") + BigFloat.new(1_i8).to_s.should eq("1") + BigFloat.new(1_i16).to_s.should eq("1") + BigFloat.new(1_i32).to_s.should eq("1") + BigFloat.new(1_i64).to_s.should eq("1") + BigFloat.new(-1_i8).to_s.should eq("-1") + BigFloat.new(-1_i16).to_s.should eq("-1") + BigFloat.new(-1_i32).to_s.should eq("-1") + BigFloat.new(-1_i64).to_s.should eq("-1") + + BigFloat.new(255_u8).to_s.should eq("255") + BigFloat.new(65535_u16).to_s.should eq("65535") + BigFloat.new(4294967295_u32).to_s.should eq("4294967295") + BigFloat.new(18446744073709551615_u64).to_s.should eq("18446744073709551615") + BigFloat.new(127_i8).to_s.should eq("127") + BigFloat.new(32767_i16).to_s.should eq("32767") + BigFloat.new(2147483647_i32).to_s.should eq("2147483647") + BigFloat.new(9223372036854775807_i64).to_s.should eq("9223372036854775807") + BigFloat.new(-128_i8).to_s.should eq("-128") + BigFloat.new(-32768_i16).to_s.should eq("-32768") + BigFloat.new(-2147483648_i32).to_s.should eq("-2147483648") + BigFloat.new(-9223372036854775808_i64).to_s.should eq("-9223372036854775808") + end + end + describe "-@" do bf = "0.12345".to_big_f it { (-bf).to_s.should eq("-0.12345") } @@ -91,7 +151,7 @@ describe "BigFloat" do it { "48600000".to_big_f.to_s.should eq("48600000") } it { "12345678.87654321".to_big_f.to_s.should eq("12345678.87654321") } it { "9.000000000000987".to_big_f.to_s.should eq("9.000000000000987") } - it { "12345678901234567".to_big_f.to_s.should eq("12345678901234567") } + it { "1234567890123456789".to_big_f.to_s.should eq("1234567890123456789") } end describe "#inspect" do @@ -99,8 +159,26 @@ describe "BigFloat" do end it "#hash" do - b = 123.to_big_f - b.hash.should eq(b.to_f64.hash) + bf = 123.to_big_f + bf.hash.should eq(123.hash) + bf.hash.should eq(bf.to_f64.hash) + + bi = "123456789012345678901".to_big_i + bf = bi.to_big_f + bf.should eq(bi) + bf.hash_normalize.should eq(bi.hash_normalize) + bf.hash.should eq(bi.hash) + + f = 123.06125 + bf = f.to_big_f + bf.hash.should eq(f.hash) + + bf = 1.to_big_f + bf = bf * 0x80000000 * 0x80000000 * 0x80000000 + f = 1.0_f64 + f = f * 0x80000000 * 0x80000000 * 0x80000000 + bf.hash_normalize.should eq(f.hash_normalize) + bf.hash.should eq(f.hash) end it "clones" do diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr index 816ca78410c8..fdb6d795666e 100644 --- a/spec/std/big/big_int_spec.cr +++ b/spec/std/big/big_int_spec.cr @@ -291,12 +291,30 @@ describe "BigInt" do it "#hash" do hash = 5.to_big_i.hash - hash.should eq(5) - typeof(hash).should eq(UInt64) + hash.should eq(5.hash) + end + + it "#hash_normalize" do + hn = 5.to_big_i.hash_normalize + hn.should eq(5.hash_normalize) + hn = 500000000000000_u64.to_big_i.hash_normalize + hn.should eq(500000000000000_u64.hash_normalize) + + bi = 1.to_big_i + bi = bi << 93 + f = 1.0_f64 + f = f * 0x80000000 * 0x80000000 * 0x80000000 + bi.hash_normalize.should eq(f.hash_normalize) end it "clones" do x = 1.to_big_i x.clone.should eq(x) end + + it "#to_big_f" do + s = "123456789012345678901" + x = BigInt.new(s) + x.to_big_f.should eq BigFloat.new(s) + end end diff --git a/spec/std/big/big_rational_spec.cr b/spec/std/big/big_rational_spec.cr index 6ed3d7f8dddd..24b91564309b 100644 --- a/spec/std/big/big_rational_spec.cr +++ b/spec/std/big/big_rational_spec.cr @@ -149,7 +149,7 @@ describe BigRational do it "#hash" do b = br(10, 3) hash = b.hash - hash.should eq(b.to_f64.hash) + hash.should eq(b.to_big_f.hash) end it "is a number" do @@ -160,4 +160,10 @@ describe BigRational do x = br(10, 3) x.clone.should eq(x) end + + it "#to_big_f" do + x = br(10, 3) + f = BigFloat.new(10) / BigFloat.new(3) + x.to_big_f.should eq(f) + end end diff --git a/spec/std/bool_spec.cr b/spec/std/bool_spec.cr index 960ed8653f33..fc5eb6e548c6 100644 --- a/spec/std/bool_spec.cr +++ b/spec/std/bool_spec.cr @@ -28,8 +28,9 @@ describe "Bool" do end describe "hash" do - it { true.hash.should eq(1) } - it { false.hash.should eq(0) } + it { true.hash.should eq(true.hash) } + it { false.hash.should eq(false.hash) } + it { true.hash.should_not eq(false.hash) } end describe "to_s" do diff --git a/spec/std/enum_spec.cr b/spec/std/enum_spec.cr index ff55cdd6e014..a3c17aaf6575 100644 --- a/spec/std/enum_spec.cr +++ b/spec/std/enum_spec.cr @@ -142,7 +142,7 @@ describe Enum do end it "has hash" do - SpecEnum::Two.hash.should eq(1.hash) + SpecEnum::Two.hash.should_not eq(SpecEnum::One.hash) end it "parses" do diff --git a/spec/std/hash_spec.cr b/spec/std/hash_spec.cr index 4097a4aefecb..328df1564b38 100644 --- a/spec/std/hash_spec.cr +++ b/spec/std/hash_spec.cr @@ -145,8 +145,8 @@ describe "Hash" do end end - it "works with mixed types" do - {1 => :a, "a" => 1, 1.0 => "a", :a => 1.0}.values_at(1, "a", 1.0, :a).should eq({:a, 1, "a", 1.0}) + it "works with mixed types and normalized numbers" do + {1 => :a, "a" => 1, 2.0 => "a", :a => 1.0}.values_at(1, 2, "a", 1.0, 2.0, :a).should eq({:a, "a", 1, :a, "a", 1.0}) end end diff --git a/spec/std/struct_spec.cr b/spec/std/struct_spec.cr index f266068d92e6..25aadb08fbdd 100644 --- a/spec/std/struct_spec.cr +++ b/spec/std/struct_spec.cr @@ -42,11 +42,14 @@ describe "Struct" do it "does hash" do s = StructSpec::TestClass.new(1, "hello") - s.hash.should eq(31 + "hello".hash) + hasher = Hash::Hasher.new + hasher << 1 + hasher << "hello" + s.hash.should eq(hasher.digest) end it "does hash for struct wrapper (#1940)" do - StructSpec::BigIntWrapper.new(BigInt.new(0)).hash.should eq(0) + StructSpec::BigIntWrapper.new(BigInt.new(0)).hash.should eq(BigInt.new(0).hash) end it "does dup" do diff --git a/spec/std/time/span_spec.cr b/spec/std/time/span_spec.cr index 472472fa664f..1405cb63ffb5 100644 --- a/spec/std/time/span_spec.cr +++ b/spec/std/time/span_spec.cr @@ -176,7 +176,7 @@ describe Time::Span do end it "test hash code" do - Time::Span.new(77).hash.should eq(77) + Time::Span.new(77).hash.should eq(77.hash) end it "test subtract" do diff --git a/src/big/big_float.cr b/src/big/big_float.cr index d747e0c73517..1061ce503316 100644 --- a/src/big/big_float.cr +++ b/src/big/big_float.cr @@ -18,7 +18,39 @@ struct BigFloat < Float end def initialize(num : Number) - LibGMP.mpf_init_set_d(out @mpf, num.to_f64) + # XXX: this case is workaround of Crystal's unrealiable method overloading + # remove it when separate BigFloat.new(BigInt) will pass the spec + case num + when BigInt + LibGMP.mpf_init(out @mpf) + LibGMP.mpf_set_z(self, num) + when BigRational + LibGMP.mpf_init(out @mpf) + LibGMP.mpf_set_q(self, num) + when BigFloat + LibGMP.mpf_init(out @mpf) + LibGMP.mpf_set(self, num) + when Int8, Int16, Int32 + LibGMP.mpf_init_set_si(out @mpf, num) + when UInt8, UInt16, UInt32 + LibGMP.mpf_init_set_ui(out @mpf, num) + when Int64 + if LibGMP::Long == Int64 + LibGMP.mpf_init_set_si(out @mpf, num) + else + LibGMP.mpf_init(out @mpf) + LibGMP.mpf_set_z(self, num.to_big_i) + end + when UInt64 + if LibGMP::ULong == UInt64 + LibGMP.mpf_init_set_ui(out @mpf, num) + else + LibGMP.mpf_init(out @mpf) + LibGMP.mpf_set_z(self, num.to_big_i) + end + else + LibGMP.mpf_init_set_d(out @mpf, num.to_f64) + end end def initialize(num : Float, precision : Int) @@ -35,8 +67,19 @@ struct BigFloat < Float new(mpf) end - def hash - to_f64.hash + def hash_normalize + # more exact version of `remainder(hash_modulus).to_f.hash_normalize + LibGMP.mpf_get_d_2exp(out exp, self) + frac = BigFloat.new { |mpf| + if exp >= 0 + LibGMP.mpf_div_2exp(mpf, self, exp) + else + LibGMP.mpf_mul_2exp(mpf, self, -exp) + end + } + float_normalize_wrap do + float_normalize_reference(frac, exp) + end end def self.default_precision @@ -51,16 +94,36 @@ struct BigFloat < Float LibGMP.mpf_cmp(self, other) end - def <=>(other : Float) + def <=>(other : BigInt) + LibGMP.mpf_cmp_z(self, other) + end + + def <=>(other : Float32 | Float64) LibGMP.mpf_cmp_d(self, other.to_f64) end def <=>(other : Int::Signed) - LibGMP.mpf_cmp_si(self, other.to_i64) + if LibGMP::Long == Int64 + LibGMP.mpf_cmp_si(self, other.to_i64) + elsif other.is_a?(Int8 | Int16 | Int32) + LibGMP.mpf_cmp_si(self, LibGMP::Long.new(other)) + else + LibGMP.mpf_cmp(self, other.to_big_f) + end end def <=>(other : Int::Unsigned) - LibGMP.mpf_cmp_ui(self, other.to_u64) + if LibGMP::ULong == UInt64 + LibGMP.mpf_cmp_ui(self, other.to_u64) + elsif other.is_a?(UInt8 | UInt16 | UInt32) + LibGMP.mpf_cmp_ui(self, LibGMP::ULong.new(other)) + else + LibGMP.mpf_cmp(self, other.to_big_f) + end + end + + def <=>(other : Number) + LibGMP.mpf_cmp(self, other.to_big_f) end def - diff --git a/src/big/big_int.cr b/src/big/big_int.cr index 21048cc9fb3f..74011c16f35c 100644 --- a/src/big/big_int.cr +++ b/src/big/big_int.cr @@ -264,8 +264,19 @@ struct BigInt < Int io << "_big_i" end - def hash - to_u64 + private HASH_MODULUS_INT_P = BigInt.new((1_u64 << HASH_BITS) - 1) + private HASH_MODULUS_INT_N = -BigInt.new((1_u64 << HASH_BITS) - 1) + + def hash_normalize + # it should calculate `remainder(hash_modulus)` + if LibGMP::ULong == UInt64 + v = int_to_hashnorm(LibGMP.tdiv_ui(self, hash_modulus)) + self < 0 ? -v : v + elsif self >= HASH_MODULUS_INT_P || self <= HASH_MODULUS_INT_N + unsafe_truncated_mod(HASH_MODULUS_INT_P).to_i64 + else + self.to_i64 + end end # Returns a string representation of self. @@ -311,19 +322,23 @@ struct BigInt < Int end def to_i8 - to_i64.to_i8 + to_i32.to_i8 end def to_i16 - to_i64.to_i16 + to_i32.to_i16 end def to_i32 - to_i64.to_i32 + LibGMP.get_si(self).to_i32 end def to_i64 - LibGMP.get_si(self) + if LibGMP::Long == Int64 || (self <= Int32::MAX && self >= Int32::MIN) + LibGMP.get_si(self).to_i64 + else + to_s.to_i64 + end end def to_u @@ -331,19 +346,23 @@ struct BigInt < Int end def to_u8 - to_u64.to_u8 + to_u32.to_u8 end def to_u16 - to_u64.to_u16 + to_u32.to_u16 end def to_u32 - to_u64.to_u32 + LibGMP.get_ui(self).to_u32 end def to_u64 - LibGMP.get_ui(self).to_u64 + if LibGMP::ULong == UInt64 || (self <= UInt32::MAX && self >= UInt32::MIN) + LibGMP.get_ui(self).to_u64 + else + to_s.to_u64 + end end def to_f diff --git a/src/big/big_rational.cr b/src/big/big_rational.cr index a8ddd30d871e..8a555f72eecc 100644 --- a/src/big/big_rational.cr +++ b/src/big/big_rational.cr @@ -41,6 +41,22 @@ struct BigRational < Number initialize(num, 1) end + # Creates a exact representation of float as rational. + # + # It ensures that `BigRational.new(f) == f` + # It relies on fact, that mantissa is at most 53 bits + def initialize(num : Float32 | Float64) + frac, exp = Math.frexp num + ifrac = (frac.to_f64 * (1.to_i64 << 53).to_f64).to_i64 + exp -= 53 + initialize ifrac, 1 + if exp >= 0 + LibGMP.mpq_mul_2exp(out @mpq, self, exp) + else + LibGMP.mpq_div_2exp(out @mpq, self, -exp) + end + end + # :nodoc: def initialize(@mpq : LibGMP::MPQ) end @@ -64,8 +80,12 @@ struct BigRational < Number LibGMP.mpq_cmp(mpq, other) end + def <=>(other : Float32 | Float64) + self <=> BigRational.new(other) + end + def <=>(other : Float) - self.to_f <=> other + to_big_f <=> other.to_big_f end def <=>(other : Int) @@ -139,8 +159,20 @@ struct BigRational < Number BigRational.new { |mpq| LibGMP.mpq_abs(mpq, self) } end - def hash - to_f64.hash + private HASH_MODULUS_RAT_P = BigRational.new((1_u64 << HASH_BITS) - 1) + private HASH_MODULUS_RAT_N = -BigRational.new((1_u64 << HASH_BITS) - 1) + + def hash_normalize + # more exact version of `remainder(hash_modulus).to_f.hash_normalize` + rem = self + if self >= HASH_MODULUS_RAT_P || self <= HASH_MODULUS_RAT_N + num = numerator + denom = denominator + div = num.tdiv(denom) + floor = div.tdiv(hash_modulus) + rem -= floor * hash_modulus + end + rem.to_big_f.hash_normalize end # Returns the `Float64` representing this rational. diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr index 012eeee9750c..e9890381df8c 100644 --- a/src/big/lib_gmp.cr +++ b/src/big/lib_gmp.cr @@ -63,6 +63,7 @@ lib LibGMP fun tdiv_r = __gmpz_tdiv_r(rop : MPZ*, op1 : MPZ*, op2 : MPZ*) fun tdiv_r_ui = __gmpz_tdiv_r_ui(rop : MPZ*, op1 : MPZ*, op2 : ULong) + fun tdiv_ui = __gmpz_tdiv_ui(op1 : MPZ*, op2 : ULong) : ULong fun neg = __gmpz_neg(rop : MPZ*, op : MPZ*) fun abs = __gmpz_abs(rop : MPZ*, op : MPZ*) @@ -142,6 +143,8 @@ lib LibGMP fun mpf_init = __gmpf_init(x : MPF*) fun mpf_init2 = __gmpf_init2(x : MPF*, prec : BitcntT) fun mpf_init_set_d = __gmpf_init_set_d(rop : MPF*, op : Double) + fun mpf_init_set_ui = __gmpf_init_set_ui(rop : MPF*, op : ULong) + fun mpf_init_set_si = __gmpf_init_set_si(rop : MPF*, op : Long) fun mpf_init_set_str = __gmpf_init_set_str(rop : MPF*, str : UInt8*, base : Int) : Int # # Precision @@ -152,10 +155,14 @@ lib LibGMP fun mpf_get_str = __gmpf_get_str(str : UInt8*, expptr : MpExp*, base : Int, n_digits : LibC::SizeT, op : MPF*) : UInt8* fun mpf_get_d = __gmpf_get_d(op : MPF*) : Double fun mpf_set_d = __gmpf_set_d(op : MPF*, op : Double) + fun mpf_set = __gmpf_set(op : MPF*, op : MPF*) + fun mpf_set_z = __gmpf_set_z(op : MPF*, op : MPZ*) + fun mpf_set_q = __gmpf_set_q(op : MPF*, op : MPQ*) fun mpf_get_si = __gmpf_get_si(op : MPF*) : Long fun mpf_get_ui = __gmpf_get_ui(op : MPF*) : ULong fun mpf_ceil = __gmpf_ceil(rop : MPF*, op : MPF*) fun mpf_floor = __gmpf_floor(rop : MPF*, op : MPF*) + fun mpf_get_d_2exp = __gmpf_get_d_2exp(exp : Long*, op : MPF*) : Double # # Arithmetic fun mpf_add = __gmpf_add(rop : MPF*, op1 : MPF*, op2 : MPF*) @@ -165,12 +172,15 @@ lib LibGMP fun mpf_neg = __gmpf_neg(rop : MPF*, op : MPF*) fun mpf_abs = __gmpf_abs(rop : MPF*, op : MPF*) fun mpf_pow_ui = __gmpf_pow_ui(rop : MPF*, op1 : MPF*, op2 : ULong) + fun mpf_mul_2exp = __gmpf_mul_2exp(rop : MPF*, op1 : MPF*, op2 : BitcntT) + fun mpf_div_2exp = __gmpf_div_2exp(rop : MPF*, op1 : MPF*, op2 : BitcntT) # # Comparison fun mpf_cmp = __gmpf_cmp(op1 : MPF*, op2 : MPF*) : Int fun mpf_cmp_d = __gmpf_cmp_d(op1 : MPF*, op2 : Double) : Int fun mpf_cmp_ui = __gmpf_cmp_ui(op1 : MPF*, op2 : ULong) : Int fun mpf_cmp_si = __gmpf_cmp_si(op1 : MPF*, op2 : Long) : Int + fun mpf_cmp_z = __gmpf_cmp_z(op1 : MPF*, op2 : MPZ*) : Int fun mpf_integer_p = __gmpf_integer_p(op : MPF*) : Int diff --git a/src/bool.cr b/src/bool.cr index 5e7f5f81ae56..898b79ac81d3 100644 --- a/src/bool.cr +++ b/src/bool.cr @@ -41,9 +41,10 @@ struct Bool self != other end - # Returns a hash value for this boolean: 0 for `false`, 1 for `true`. - def hash - self ? 1 : 0 + # Protocol method for generic hashing. + def hash(hasher) + hasher << (self ? 1 : 0) + hasher end # Returns `"true"` for `true` and `"false"` for `false`. diff --git a/src/char.cr b/src/char.cr index 0fd31d1c97c2..3e5619a9abdd 100644 --- a/src/char.cr +++ b/src/char.cr @@ -419,6 +419,12 @@ struct Char ord end + # Protocol method for generic hashing. + def hash(hasher) + hasher.raw ord + hasher + end + # Returns a Char that is one codepoint bigger than this char's codepoint. # # ``` diff --git a/src/class.cr b/src/class.cr index cfe17b1e900c..5634b64a0b3f 100644 --- a/src/class.cr +++ b/src/class.cr @@ -3,8 +3,9 @@ class Class to_s(io) end - def hash - crystal_type_id + def hash(hasher) + hasher.raw(crystal_type_id) + hasher end def ==(other : Class) diff --git a/src/compiler/crystal/syntax/ast.cr b/src/compiler/crystal/syntax/ast.cr index 76a5fce49342..911ea3120b39 100644 --- a/src/compiler/crystal/syntax/ast.cr +++ b/src/compiler/crystal/syntax/ast.cr @@ -1175,8 +1175,9 @@ module Crystal self end - def hash - 0 + def hash(hasher) + hasher << 0 + hasher end end @@ -1545,8 +1546,9 @@ module Crystal Self.new end - def hash - 0 + def hash(hasher) + hasher << 0 + hasher end end @@ -2025,8 +2027,9 @@ module Crystal Underscore.new end - def hash - 0 + def hash(hasher) + hasher << 0 + hasher end end diff --git a/src/enum.cr b/src/enum.cr index e9758ba05d13..76deb2dcfafb 100644 --- a/src/enum.cr +++ b/src/enum.cr @@ -274,9 +274,10 @@ struct Enum value == other.value end - # Returns a hash value. This is the hash of the underlying value. - def hash - value.hash + # Protocol method for generic hashing. + def hash(hasher) + hasher.raw(value) + hasher end # Iterates each values in a Flags Enum. diff --git a/src/event/signal_handler.cr b/src/event/signal_handler.cr index 857a2bea9701..de3caf5a2a0e 100644 --- a/src/event/signal_handler.cr +++ b/src/event/signal_handler.cr @@ -1,5 +1,6 @@ require "c/signal" require "c/unistd" +require "signal" # :nodoc: # Singleton that runs Signal events (libevent2) in it's own Fiber. diff --git a/src/float.cr b/src/float.cr index 6bd4dbc1644f..ae47d23a7e69 100644 --- a/src/float.cr +++ b/src/float.cr @@ -1,6 +1,7 @@ require "c/stdio" require "c/string" require "./float/printer" +require "./number/hash_normalize" # Float is the base type of all floating point numbers. # @@ -153,13 +154,35 @@ struct Float32 io << "_f32" end - def hash - unsafe_as(Int32) - end - def clone self end + + include Number::HashNormalize + + def hash_normalize + float_normalize_wrap do + # Reference implementation: + # ``` + # frac, exp = Math.frexp self + # float_normalize_reference(frac, exp) + # ``` + # This optimized version works on every architecture where endianess + # of Float32 and Int32 matches and float is IEEE754. All supported + # architectures fall into this category. + unsafe_int = unsafe_as(Int32) + exp = (((unsafe_int >> 23) & 0xff) - 127) + mantissa = unsafe_int & ((1 << 23) - 1) + if exp > -127 + exp -= 23 + mantissa |= 1 << 23 + else + # subnormals + exp -= 22 + end + {int_to_hashnorm(mantissa), exp} + end + end end struct Float64 @@ -211,11 +234,34 @@ struct Float64 Printer.print(self, io) end - def hash - unsafe_as(Int64) - end - def clone self end + + include Number::HashNormalize + + def hash_normalize + float_normalize_wrap do + # Reference implementation: + # ``` + # frac, exp = Math.frexp self + # float_normalize_reference(frac, exp) + # ``` + # This optimized version works on every architecture where endianess + # of Float64 and Int64 matches and float is IEEE754. All supported + # architectures fall into this category. + unsafe_int = unsafe_as(Int64) + exp = (((unsafe_int >> 52) & 0x7ff) - 1023) + mantissa = unsafe_int & ((1_u64 << 52) - 1) + if exp > -1023 + exp -= 52 + mantissa |= 1_u64 << 52 + else + # subnormals + exp -= 51 + end + + {int_to_hashnorm(mantissa), exp} + end + end end diff --git a/src/hash.cr b/src/hash.cr index 6d7fe7567b52..b79f86caa3db 100644 --- a/src/hash.cr +++ b/src/hash.cr @@ -706,18 +706,19 @@ class Hash(K, V) true end - # See also: `Object#hash`. - # - # ``` - # foo = {"foo" => "bar"} - # foo.hash # => 3247054 - # ``` - def hash - hash = size + # Protocol method for generic hashing. + # NOTE: it should be independent of iteration order. + def hash(hasher) + hasher.raw(size) + digest = hasher.digest each do |key, value| - hash += key.hash ^ value.hash + copy = hasher.clone + copy << key + copy << value + digest += copy.digest end - hash + hasher.raw(digest) + hasher end # Duplicates a `Hash`. @@ -864,7 +865,11 @@ class Hash(K, V) end private def bucket_index(key) - key.hash.to_u32.remainder(@buckets_size).to_i + hash_key(key).to_u32.remainder(@buckets_size).to_i + end + + protected def hash_key(key) + key.hash end private def calculate_new_size(size) diff --git a/src/hash/hasher.cr b/src/hash/hasher.cr new file mode 100644 index 000000000000..03dc87c5d2a5 --- /dev/null +++ b/src/hash/hasher.cr @@ -0,0 +1,188 @@ +require "crystal/system/random" + +# Hasher usable for `def hash(hasher)` should satisfy protocol: +# ``` +# class MyHasher +# # Value should implement commutative `+` for `Hash#hash(hasher)` +# alias Value +# +# # must be implemented to mix sizes of collections, and pointers (object_id) +# def raw(v : Int::Primitive) +# # mutate +# nil +# end +# +# # must be implemented for Hash#hash +# def raw(v : Value) +# # mutate +# nil +# end +# +# def <<(b : Bytes) +# # mutate +# nil +# end +# +# def <<(n : Nil) +# # mutate +# nil +# end +# +# def <<(v) +# # v.hash will return hasher +# # if hasher is a struct, then it will be copy +# copy_from v.hash(self) +# nil +# end +# +# # digest returns hashsum for current state without state mutation +# def digest : Value +# end +# +# # should be implemented for `Hash#hash(hasher)` +# def clone +# copy_of_current_state +# end +# end +# ``` + +class Hash + # Hasher used as standard hasher in `Object#hash` + struct Hasher + # Type for Hash::Hasher#digest + alias Value = UInt32 + + @@seed = uninitialized StaticArray(UInt32, 1) + buf = pointerof(@@seed).as(Pointer(UInt8)) + Crystal::System::Random.random_bytes(buf.to_slice(sizeof(typeof(@@seed)))) + + protected getter a : UInt32 = 0_u32 + + # Construct hasher with per-process seed. + def initialize + @a = @@seed[0] + end + + # Construct hasher with custom seed. + def initialize(@a : UInt32) + end + + # Calculate hashsum for value + def self.hashit(value) : Value + s = new(@@seed[0]) + s << value + s.digest + end + + # Make copy of self. + # + # Primary usage is for `Hash#hash` calculation. + def clone : self + self.class.new(@a) + end + + # Mix nil to state + def <<(v : Nil) : Nil + permute_nil() + nil + end + + # Mix raw value without number normalizing + def raw(v : Int8 | UInt8) : Nil + permute(v.to_u8) + nil + end + + # Mix raw value without number normalizing + def raw(v : Int16 | Int32 | UInt16 | UInt32) : Nil + permute(v.to_u32) + nil + end + + # Mix raw value without number normalizing + def raw(v : Int64 | UInt64) : Nil + high = (v >> 32).to_u32 + # This condition here cause of some 32bit issue in LLVM binding, + # so compiler_spec doesn't pass without it. + # Feel free to comment and debug. + if high != 0_u32 + permute(high) + end + permute(v.to_u32) + nil + end + + # Mix slice of bytes to state (for string hashing) + def <<(b : Bytes) : Nil + permute(b) + nil + end + + # Mix any value to state. `value` should implement `hash(hasher)` method. + # + # Numbers implement this method in a way equal numbers are hashed + # to same hashsum. + def <<(value) : Nil + cp = value.hash(self) + @a = cp.a + nil + end + + # Returns hashsum for current state. + # It doesn't mutate hasher itself. + def digest : Value + a = @a + a ^= a >> 17 + a *= 0xb8b34b2d_u32 + a ^= a >> 16 + a + end + + # String representaion. + # + # It is overloaded to protect against occasional output. + # `inspect` is not overloaded, though. + def to_s(io : IO) + io << "Hash::Hasher()" + end + + protected def permute_nil + # LFSR + mx = (@a.to_i32 >> 31).to_u32 & 0xa8888eef_u32 + @a = (@a << 1) ^ mx + end + + protected def permute(v : UInt8) + @a = @a * 31 + v + end + + protected def permute(v : UInt32) + @a = @a * 31 + v + end + + protected def permute(buf : Bytes) + buf.each do |b| + @a = @a * 31 + b + end + end + + # unseeded is used for types that are used in early startup + def self.unseeded(v : Int8 | Int16 | UInt8 | UInt16 | Int32 | UInt32) + h = v.to_u32 + h ^= h >> 16 + h *= 0x52c6a2d9_u32 + h ^ (h >> 16) + end + + # unseeded is used for types that are used in early startup + def self.unseeded(v : Int64 | UInt64) + h = (v >> 32).to_u32 + h ^= h >> 16 + h *= 0xb8b34b2d_u32 + h += v.to_u32 + h ^= h >> 16 + h *= 0x52c6a2d9_u32 + h ^ (h >> 16) + end + end +end diff --git a/src/http/headers.cr b/src/http/headers.cr index e47ff17765b0..f25a2371e2b4 100644 --- a/src/http/headers.cr +++ b/src/http/headers.cr @@ -9,13 +9,12 @@ struct HTTP::Headers record Key, name : String do forward_missing_to @name - def hash - h = 0 - name.each_byte do |c| - c = normalize_byte(c) - h = 31 * h + c + def hash(hasher) + hasher.raw(bytesize.to_u32) + name.each_byte do |b| + hasher.raw normalize_byte(b) end - h + hasher end def ==(key2) @@ -44,7 +43,7 @@ struct HTTP::Headers return byte if char.ascii_lowercase? || char == '-' # Optimize the common case return byte + 32 if char.ascii_uppercase? - return '-'.ord if char == '_' + return '-'.ord.to_u8 if char == '_' byte end diff --git a/src/indexable.cr b/src/indexable.cr index fa78b16f7b21..bc873c98497f 100644 --- a/src/indexable.cr +++ b/src/indexable.cr @@ -271,13 +271,13 @@ module Indexable(T) first { nil } end - # Returns a hash code based on `self`'s size and elements. - # - # See also: `Object#hash`. - def hash - reduce(31 * size) do |memo, elem| - 31 * memo + elem.hash + # Protocol method for generic hashing. + def hash(hasher) + hasher.raw(size.to_u32) + each do |elem| + hasher << elem end + hasher end # Returns the index of the first appearance of *value* in `self` diff --git a/src/int.cr b/src/int.cr index 0fbcf49d72b5..d073f350ab16 100644 --- a/src/int.cr +++ b/src/int.cr @@ -316,10 +316,6 @@ struct Int !even? end - def hash - self - end - def succ self + 1 end @@ -594,6 +590,10 @@ struct Int8 def clone self end + + def hash_normalize + self + end end struct Int16 @@ -616,6 +616,10 @@ struct Int16 def clone self end + + def hash_normalize + self + end end struct Int32 @@ -638,6 +642,10 @@ struct Int32 def clone self end + + def hash_normalize + self + end end struct Int64 @@ -660,6 +668,10 @@ struct Int64 def clone self end + + def hash_normalize + unsafe_mod(hash_modulus) + end end struct UInt8 @@ -682,6 +694,10 @@ struct UInt8 def clone self end + + def hash_normalize + self + end end struct UInt16 @@ -704,6 +720,10 @@ struct UInt16 def clone self end + + def hash_normalize + self + end end struct UInt32 @@ -726,6 +746,10 @@ struct UInt32 def clone self end + + def hash_normalize + self + end end struct UInt64 @@ -748,4 +772,8 @@ struct UInt64 def clone self end + + def hash_normalize + unsafe_mod(hash_modulus) + end end diff --git a/src/json/any.cr b/src/json/any.cr index fecfa508bd6d..21c7dbca2143 100644 --- a/src/json/any.cr +++ b/src/json/any.cr @@ -261,11 +261,6 @@ struct JSON::Any raw == other end - # :nodoc: - def hash - raw.hash - end - # :nodoc: def to_json(json : JSON::Builder) raw.to_json(json) diff --git a/src/named_tuple.cr b/src/named_tuple.cr index cd7468b0a418..8f8d182df6ca 100644 --- a/src/named_tuple.cr +++ b/src/named_tuple.cr @@ -159,16 +159,14 @@ struct NamedTuple yield end - # Returns a hash value based on this name tuple's size, keys and values. - # - # See also: `Object#hash`. - def hash - hash = 31 * size + # Protocol method for generic hashing. + def hash(hasher) + hasher.raw(size) {% for key in T.keys.sort %} - hash = 31 * hash + {{key.symbolize}}.hash - hash = 31 * hash + self[{{key.symbolize}}].hash + hasher << {{key.symbolize}} + hasher << self[{{key.symbolize}}] {% end %} - hash + hasher end # Same as `to_s`. diff --git a/src/nil.cr b/src/nil.cr index 644fcc98cb71..5948772b3a46 100644 --- a/src/nil.cr +++ b/src/nil.cr @@ -67,9 +67,10 @@ struct Nil false end - # Returns `0`. - def hash - 0 + # Protocol method for generic hashing. + def hash(hasher) + hasher << nil + hasher end # Returns an empty string. diff --git a/src/number.cr b/src/number.cr index cf32b3ae6b96..5cdd67627e2c 100644 --- a/src/number.cr +++ b/src/number.cr @@ -1,3 +1,5 @@ +require "./number/hash_normalize" + # The top-level number type. struct Number include Comparable(Number) @@ -255,6 +257,22 @@ struct Number self == 0 end + include Number::HashNormalize + + # Protocol method for generic hashing + # All number types should define `hash_normalize`, so equal number will + # produce equal normalized value. + # Integer numbers should calculate `self.remainder(hash_modulus)` + # Float64 and Float32 version generalize it for numbers with fractional part. + # BigFloat and BigRational should calculate it as equivalent to + # `(v.remainder hash_modulus).to_f.hash_normalize`, though more precise + # calculation used. + # See comments in "number/hash_normalize.cr" + def hash(hasher) + hasher.raw hash_normalize.to_i64 + hasher + end + private class StepIterator(T, L, B) include Iterator(T) diff --git a/src/number/hash_normalize.cr b/src/number/hash_normalize.cr new file mode 100644 index 000000000000..ec82a72afdd7 --- /dev/null +++ b/src/number/hash_normalize.cr @@ -0,0 +1,105 @@ +module Number::HashNormalize + # Based on https://github.com/python/cpython/blob/f051e43/Python/pyhash.c#L34 + # + # For numeric types, the hash of a number x is based on the reduction + # of x modulo the Mersen Prime P = 2**HASH_BITS - 1. It's designed + # so that hash(x) == hash(y) whenever x and y are numerically equal, + # even if x and y have different types. + # A quick summary of the hashing strategy: + # (1) First define the 'reduction of x modulo P' for any rational + # number x; this is a standard extension of the usual notion of + # reduction modulo P for integers. If x == p/q (written in lowest + # terms), the reduction is interpreted as the reduction of p times + # the inverse of the reduction of q, all modulo P; if q is exactly + # divisible by P then define the reduction to be infinity. So we've + # got a well-defined map + # reduce : { rational numbers } -> { 0, 1, 2, ..., P-1, infinity }. + # (2) Now for a rational number x, define hash(x) by: + # reduce(x) if x >= 0 + # -reduce(-x) if x < 0 + # If the result of the reduction is infinity (this is impossible for + # integers, floats and Decimals) then use the predefined hash value + # HASH_INF for x >= 0, or -HASH_INF for x < 0, instead. + # HASH_INF, -HASH_INF and HASH_NAN are also used for the + # hashes of float and Decimal infinities and nans. + # A selling point for the above strategy is that it makes it possible + # to compute hashes of decimal and binary floating-point numbers + # efficiently, even if the exponent of the binary or decimal number + # is large. The key point is that + # reduce(x * y) == reduce(x) * reduce(y) (modulo HASH_MODULUS) + # provided that {reduce(x), reduce(y)} != {0, infinity}. The reduction of a + # binary or decimal float is never infinity, since the denominator is a power + # of 2 (for binary) or a divisor of a power of 10 (for decimal). So we have, + # for nonnegative x, + # reduce(x * 2**e) == reduce(x) * reduce(2**e) % HASH_MODULUS + # reduce(x * 10**e) == reduce(x) * reduce(10**e) % HASH_MODULUS + # and reduce(10**e) can be computed efficiently by the usual modular + # exponentiation algorithm. For reduce(2**e) it's even better: since + # P is of the form 2**n-1, reduce(2**e) is 2**(e mod n), and multiplication + # by 2**(e mod n) modulo 2**n-1 just amounts to a rotation of bits. + + # XXX: private methods used instead of constants cause current crystal + # compiler inlines methods better. Looks like it treats constants as + # real variables, and check for their initialization on every access. + + private HASH_BITS = 61 + + private def hash_bits + 61 + end + + private def int_to_hashnorm(v) + v.to_i64 + end + + private def hash_modulus + int_to_hashnorm((1_u64 << hash_bits) - 1) + end + + private def hash_nan + int_to_hashnorm 0 + end + + private def hash_infinity + int_to_hashnorm 314159 + end + + # This function is for reference implementation, and it is used for BigFloat. + # For Float64 and Float32 all supported architectures allows more effective + # bitwise calculation. + # Arguments `frac` and `exp` are result of equivalent `Math.frexp`, though + # for `BigFloat` custom calculation used for more precision. + private def float_normalize_reference(frac, exp) + if self < 0 + frac = -frac + end + # process 28 bits at a time; this should work well both for binary + # and hexadecimal floating point. + x = int_to_hashnorm(0) + while frac > 0 + x = ((x << 28) & hash_modulus) | x >> (hash_bits - 28) + frac *= 268435456.0 # 2**28 + exp -= 28 + y = frac.to_u32 # pull out integer part + frac -= y + x += y + x -= hash_modulus if x >= hash_modulus + end + {x, exp} + end + + private def float_normalize_wrap + return int_to_hashnorm(0) if nan? + if infinite? + return self > 0 ? +hash_infinity : -hash_infinity + end + + x, exp = yield + + # adjust for the exponent; first reduce it modulo HASH_BITS + exp = exp >= 0 ? exp % hash_bits : hash_bits - 1 - ((-1 - exp) % hash_bits) + x = ((x << exp) & hash_modulus) | x >> (hash_bits - exp) + + x * (self < 0 ? -1 : 1) + end +end diff --git a/src/object.cr b/src/object.cr index 1f66f1837494..1c2073521885 100644 --- a/src/object.cr +++ b/src/object.cr @@ -64,7 +64,29 @@ class Object # # The hash value is used along with `==` by the `Hash` class to determine if two objects # reference the same hash key. - abstract def hash + def hash + Hash::Hasher.hashit self + end + + # Protocol method for generic hashing. + # + # You should use `hasher << @v` for mixing values. It will recursively call + # `hash(hasher)` on values. `hash(hasher)` on numbers defined to generate + # same hash value for equal number of different types. For performance sake + # use `hasher.raw @v` if you want mix integer as abstract value and not as a + # number. + # + # Cause hasher could be a struct, `hash(hasher)` have to return hasher. + # Also, `hasher.<<` method is not chainable, unlike other `<<` methods. + # + # def hash(hasher) + # hasher.raw @size + # each do |elem| + # hasher << elem + # end + # hasher + # end + abstract def hash(hasher) # Returns a string representation of this object. # @@ -1078,7 +1100,7 @@ class Object {% end %} end - # Defines a `hash` method computed from the given fields. + # Defines a `hash(hasher)` method computed from the given fields. # # ``` # class Person @@ -1090,16 +1112,11 @@ class Object # end # ``` macro def_hash(*fields) - def hash - {% if fields.size == 1 %} - {{fields[0]}}.hash - {% else %} - hash = 0 - {% for field in fields %} - hash = 31 * hash + {{field}}.hash - {% end %} - hash + def hash(hasher) + {% for field in fields %} + hasher << {{field}} {% end %} + hasher end end diff --git a/src/prelude.cr b/src/prelude.cr index 4aa5e5954b57..a42129dfafd7 100644 --- a/src/prelude.cr +++ b/src/prelude.cr @@ -17,6 +17,7 @@ require "iterable" require "iterator" require "indexable" require "string" +require "hash/hasher" # Alpha-sorted list require "array" diff --git a/src/proc.cr b/src/proc.cr index df48e26f4429..1bf225b6bdec 100644 --- a/src/proc.cr +++ b/src/proc.cr @@ -181,8 +181,9 @@ struct Proc call(other) end - def hash - internal_representation.hash + def hash(hasher) + hasher << internal_representation + hasher end def clone diff --git a/src/reference.cr b/src/reference.cr index 94034e03c3bf..e360bf8179fb 100644 --- a/src/reference.cr +++ b/src/reference.cr @@ -50,9 +50,10 @@ class Reference {% end %} end - # Returns this reference's `object_id` as the hash value. - def hash - object_id + # Protocol method for generic hashing. + def hash(hasher) + hasher.raw object_id + hasher end def inspect(io : IO) : Nil diff --git a/src/set.cr b/src/set.cr index 991196e0867e..48750009fc76 100644 --- a/src/set.cr +++ b/src/set.cr @@ -308,10 +308,6 @@ struct Set(T) pp.list("Set{", self, "}") end - def hash - @hash.hash - end - # Returns `true` if the set and the given set have at least one element in # common. # diff --git a/src/signal.cr b/src/signal.cr index 575654984965..266332a4de38 100644 --- a/src/signal.cr +++ b/src/signal.cr @@ -142,6 +142,17 @@ enum Signal Signal::PIPE.ignore Signal::CHLD.reset end + + # There is no much of signals, so don't bother with hashing. + # And we couldn't use seeded hash, because seed is not filled yet. + # :nodoc: + def hash + value + end + + def hash(hasher) + hasher.raw value + end end # :nodoc: diff --git a/src/string.cr b/src/string.cr index ecf0a89348e4..087fd37e6aa4 100644 --- a/src/string.cr +++ b/src/string.cr @@ -3929,15 +3929,10 @@ class String sprintf self, other end - # Returns a hash based on this string’s size and content. - # - # See also: `Object#hash`. - def hash - h = 0 - each_byte do |c| - h = 31 * h + c - end - h + # Protocol method for generic hashing. + def hash(hasher) + hasher << to_slice + hasher end # Returns the number of unicode codepoints in this string. diff --git a/src/struct.cr b/src/struct.cr index 57bda79ad9eb..c730332df280 100644 --- a/src/struct.cr +++ b/src/struct.cr @@ -73,12 +73,13 @@ struct Struct # Returns a hash value based on this struct's instance variables hash values. # # See also: `Object#hash` - def hash : Int32 - hash = 0 + + # Protocol method for generic hashing. + def hash(hasher) {% for ivar in @type.instance_vars %} - hash = 31 * hash + @{{ivar.id}}.hash.to_i32 + hasher << @{{ivar.id}} {% end %} - hash + hasher end # Appends this struct's name and instance variables names and values diff --git a/src/symbol.cr b/src/symbol.cr index 8e90a7207c68..5e46c3eaf05c 100644 --- a/src/symbol.cr +++ b/src/symbol.cr @@ -15,11 +15,10 @@ struct Symbol include Comparable(Symbol) - # Generates an `Int32` hash value for this symbol. - # - # See also: `Object#hash`. - def hash : Int32 - to_i + # Protocol method for generic hashing. + def hash(hasher) + hasher.raw to_i + hasher end # Compares symbol with other based on `String#<=>` method. Returns `-1`, `0` diff --git a/src/thread.cr b/src/thread.cr index 8102a033dbe0..c7c9bcfb4b59 100644 --- a/src/thread.cr +++ b/src/thread.cr @@ -48,6 +48,12 @@ class Thread end end + # override, cause Hash::Hasher's seed is not initialized yet + # :nodoc: + def hash + Hash::Hasher.unseeded object_id + end + # All threads, so the GC can see them (GC doesn't scan thread locals) # and we can find the current thread on platforms that don't support # thread local storage (eg: OpenBSD) diff --git a/src/time.cr b/src/time.cr index 9d2c270f1148..9cc76aad0577 100644 --- a/src/time.cr +++ b/src/time.cr @@ -309,10 +309,6 @@ struct Time end end - def hash - @encoded - end - def self.days_in_month(year, month) : Int32 unless 1 <= month <= 12 raise ArgumentError.new "Invalid month" diff --git a/src/tuple.cr b/src/tuple.cr index 01933b6f4980..71b65466daab 100644 --- a/src/tuple.cr +++ b/src/tuple.cr @@ -307,15 +307,12 @@ struct Tuple size <=> other.size end - # Returns a hash value based on this tuple's length and contents. - # - # See also: `Object#hash`. - def hash - hash = 31 * size + # Protocol method for generic hashing. + def hash(hasher) {% for i in 0...T.size %} - hash = 31 * hash + self[{{i}}].hash + hasher << self[{{i}}] {% end %} - hash + hasher end # Returns a tuple containing cloned elements of this tuple using the `clone` method. diff --git a/src/xml/namespace.cr b/src/xml/namespace.cr index 43fa3d2bad69..9336930bfb58 100644 --- a/src/xml/namespace.cr +++ b/src/xml/namespace.cr @@ -4,8 +4,9 @@ struct XML::Namespace def initialize(@document : Node, @ns : LibXML::NS*) end - def hash - object_id + def hash(hasher) + hasher.raw object_id + hasher end def href diff --git a/src/xml/node.cr b/src/xml/node.cr index 3c23a238036f..f71064d4205e 100644 --- a/src/xml/node.cr +++ b/src/xml/node.cr @@ -160,8 +160,9 @@ struct XML::Node end # Returns this node's `#object_id` as the hash value. - def hash - object_id + def hash(hasher) + hasher.raw object_id + hasher end # Returns the content for this Node. diff --git a/src/xml/node_set.cr b/src/xml/node_set.cr index 00c8c759f039..215684278065 100644 --- a/src/xml/node_set.cr +++ b/src/xml/node_set.cr @@ -28,8 +28,9 @@ struct XML::NodeSet size == 0 end - def hash - object_id + def hash(hasher) + hasher.raw object_id + hasher end def inspect(io) diff --git a/src/yaml/any.cr b/src/yaml/any.cr index 1d2956c41261..efb5160a76f4 100644 --- a/src/yaml/any.cr +++ b/src/yaml/any.cr @@ -194,11 +194,6 @@ struct YAML::Any raw == other end - # :nodoc: - def hash - raw.hash - end - # :nodoc: def to_yaml(io) raw.to_yaml(io)