From b696beea65bbffd31edac169ccf9708f46ab9652 Mon Sep 17 00:00:00 2001 From: Hein-Pieter van Braam Date: Wed, 15 Feb 2017 14:41:16 +0100 Subject: Correct hash behavior for floating point numbers This fixes HashMap where a key or part of a key is a floating point number. To fix this the following has been done: * HashMap now takes an extra template argument Comparator. This class gets used to compare keys. The default Comperator now works correctly for common types and floating point numbets. * Variant implements ::hash_compare() now. This function implements nan-safe comparison for all types with components that contain floating point numbers. * Variant now has a VariantComparator which uses Variant::hash_compare() safely compare floating point components of variant's types. * The hash functions for floating point numbers will now normalize NaN values so that all floating point numbers that are NaN hash to the same value. C++ module writers that want to use HashMap internally in their modules can now also safeguard against this crash by defining their on Comperator class that safely compares their types. GDScript users, or writers of modules that don't use HashMap internally in their modules don't need to do anything. This fixes #7354 and fixes #6947. --- core/variant.cpp | 187 +++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 182 insertions(+), 5 deletions(-) (limited to 'core/variant.cpp') diff --git a/core/variant.cpp b/core/variant.cpp index 103c8f6746..132d7a1c88 100644 --- a/core/variant.cpp +++ b/core/variant.cpp @@ -28,6 +28,7 @@ /*************************************************************************/ #include "variant.h" +#include "math_funcs.h" #include "resource.h" #include "print_string.h" #include "scene/main/node.h" @@ -2674,14 +2675,10 @@ uint32_t Variant::hash() const { case INT: { return _data._int; - } break; case REAL: { - MarshallFloat mf; - mf.f=_data._real; - return mf.i; - + return hash_djb2_one_float(_data._real); } break; case STRING: { @@ -2921,6 +2918,186 @@ uint32_t Variant::hash() const { } +#define hash_compare_scalar(p_lhs, p_rhs)\ + ((p_lhs) == (p_rhs)) || (Math::is_nan(p_lhs) == Math::is_nan(p_rhs)) + +#define hash_compare_vector2(p_lhs, p_rhs)\ + (hash_compare_scalar((p_lhs).x, (p_rhs).x)) && \ + (hash_compare_scalar((p_lhs).y, (p_rhs).y)) + +#define hash_compare_vector3(p_lhs, p_rhs)\ + (hash_compare_scalar((p_lhs).x, (p_rhs).x)) && \ + (hash_compare_scalar((p_lhs).y, (p_rhs).y)) && \ + (hash_compare_scalar((p_lhs).z, (p_rhs).z)) + +#define hash_compare_quat(p_lhs, p_rhs)\ + (hash_compare_scalar((p_lhs).x, (p_rhs).x)) && \ + (hash_compare_scalar((p_lhs).y, (p_rhs).y)) && \ + (hash_compare_scalar((p_lhs).z, (p_rhs).z)) && \ + (hash_compare_scalar((p_lhs).w, (p_rhs).w)) + +#define hash_compare_color(p_lhs, p_rhs)\ + (hash_compare_scalar((p_lhs).r, (p_rhs).r)) && \ + (hash_compare_scalar((p_lhs).g, (p_rhs).g)) && \ + (hash_compare_scalar((p_lhs).b, (p_rhs).b)) && \ + (hash_compare_scalar((p_lhs).a, (p_rhs).a)) + +#define hash_compare_pool_array(p_lhs, p_rhs, p_type, p_compare_func)\ + const PoolVector& l = *reinterpret_cast*>(p_lhs);\ + const PoolVector& r = *reinterpret_cast*>(p_rhs);\ + \ + if(l.size() != r.size()) \ + return false; \ + \ + PoolVector::Read lr = l.read(); \ + PoolVector::Read rr = r.read(); \ + \ + for(int i = 0; i < l.size(); ++i) { \ + if(! p_compare_func((lr[0]), (rr[0]))) \ + return false; \ + }\ + \ + return true + +bool Variant::hash_compare(const Variant& p_variant) const { + if (type != p_variant.type) + return false; + + switch( type ) { + case REAL: { + return hash_compare_scalar(_data._real, p_variant._data._real); + } break; + + case VECTOR2: { + const Vector2* l = reinterpret_cast(_data._mem); + const Vector2* r = reinterpret_cast(p_variant._data._mem); + + return hash_compare_vector2(*l, *r); + } break; + + case RECT2: { + const Rect2* l = reinterpret_cast(_data._mem); + const Rect2* r = reinterpret_cast(p_variant._data._mem); + + return (hash_compare_vector2(l->pos, r->pos)) && + (hash_compare_vector2(l->size, r->size)); + } break; + + case TRANSFORM2D: { + Transform2D* l = _data._transform2d; + Transform2D* r = p_variant._data._transform2d; + + for(int i=0;i<3;i++) { + if (! (hash_compare_vector2(l->elements[i], r->elements[i]))) + return false; + } + + return true; + } break; + + case VECTOR3: { + const Vector3* l = reinterpret_cast(_data._mem); + const Vector3* r = reinterpret_cast(p_variant._data._mem); + + return hash_compare_vector3(*l, *r); + } break; + + case PLANE: { + const Plane* l = reinterpret_cast(_data._mem); + const Plane* r = reinterpret_cast(p_variant._data._mem); + + return (hash_compare_vector3(l->normal, r->normal)) && + (hash_compare_scalar(l->d, r->d)); + } break; + + case RECT3: { + const Rect3* l = _data._rect3; + const Rect3* r = p_variant._data._rect3; + + return (hash_compare_vector3(l->pos, r->pos) && + (hash_compare_vector3(l->size, r->size))); + + } break; + + case QUAT: { + const Quat* l = reinterpret_cast(_data._mem); + const Quat* r = reinterpret_cast(p_variant._data._mem); + + return hash_compare_quat(*l, *r); + } break; + + case BASIS: { + const Basis* l = _data._basis; + const Basis* r = p_variant._data._basis; + + for(int i=0;i<3;i++) { + if (! (hash_compare_vector3(l->elements[i], r->elements[i]))) + return false; + } + + return true; + } break; + + case TRANSFORM: { + const Transform* l = _data._transform; + const Transform* r = p_variant._data._transform; + + for(int i=0;i<3;i++) { + if (! (hash_compare_vector3(l->basis.elements[i], r->basis.elements[i]))) + return false; + } + + return hash_compare_vector3(l->origin, r->origin); + } break; + + case COLOR: { + const Color* l = reinterpret_cast(_data._mem); + const Color* r = reinterpret_cast(p_variant._data._mem); + + return hash_compare_color(*l, *r); + } break; + + case ARRAY: { + const Array& l = *(reinterpret_cast(_data._mem)); + const Array& r = *(reinterpret_cast(p_variant._data._mem)); + + if(l.size() != r.size()) + return false; + + for(int i = 0; i < l.size(); ++i) { + if(! l[0].hash_compare(r[0])) + return false; + } + + return true; + } break; + + case POOL_REAL_ARRAY: { + hash_compare_pool_array(_data._mem, p_variant._data._mem, real_t, hash_compare_scalar); + } break; + + case POOL_VECTOR2_ARRAY: { + hash_compare_pool_array(_data._mem, p_variant._data._mem, Vector2, hash_compare_vector2); + } break; + + case POOL_VECTOR3_ARRAY: { + hash_compare_pool_array(_data._mem, p_variant._data._mem, Vector3, hash_compare_vector3); + } break; + + case POOL_COLOR_ARRAY: { + hash_compare_pool_array(_data._mem, p_variant._data._mem, Color, hash_compare_color); + } break; + + default: + bool v; + Variant r; + evaluate(OP_EQUAL,*this,p_variant,r,v); + return r; + } + + return false; +} + bool Variant::is_ref() const { -- cgit v1.2.3