/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ /** * Conversions from jsval to primitive values */ #ifndef mozilla_dom_PrimitiveConversions_h #define mozilla_dom_PrimitiveConversions_h #include <limits> #include <math.h> #include <stdint.h> #include "jsapi.h" #include "js/Conversions.h" #include "mozilla/Assertions.h" #include "mozilla/ErrorResult.h" #include "mozilla/FloatingPoint.h" namespace mozilla { namespace dom { template<typename T> struct TypeName { }; template<> struct TypeName<int8_t> { static const char* value() { return "byte"; } }; template<> struct TypeName<uint8_t> { static const char* value() { return "octet"; } }; template<> struct TypeName<int16_t> { static const char* value() { return "short"; } }; template<> struct TypeName<uint16_t> { static const char* value() { return "unsigned short"; } }; template<> struct TypeName<int32_t> { static const char* value() { return "long"; } }; template<> struct TypeName<uint32_t> { static const char* value() { return "unsigned long"; } }; template<> struct TypeName<int64_t> { static const char* value() { return "long long"; } }; template<> struct TypeName<uint64_t> { static const char* value() { return "unsigned long long"; } }; enum ConversionBehavior { eDefault, eEnforceRange, eClamp }; template<typename T, ConversionBehavior B> struct PrimitiveConversionTraits { }; template<typename T> struct DisallowedConversion { typedef int jstype; typedef int intermediateType; private: static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v, jstype* retval) { MOZ_CRASH("This should never be instantiated!"); } }; struct PrimitiveConversionTraits_smallInt { // The output of JS::ToInt32 is determined as follows: // 1) The value is converted to a double // 2) Anything that's not a finite double returns 0 // 3) The double is rounded towards zero to the nearest integer // 4) The resulting integer is reduced mod 2^32. The output of this // operation is an integer in the range [0, 2^32). // 5) If the resulting number is >= 2^31, 2^32 is subtracted from it. // // The result of all this is a number in the range [-2^31, 2^31) // // WebIDL conversions for the 8-bit, 16-bit, and 32-bit integer types // are defined in the same way, except that step 4 uses reduction mod // 2^8 and 2^16 for the 8-bit and 16-bit types respectively, and step 5 // is only done for the signed types. // // C/C++ define integer conversion semantics to unsigned types as taking // your input integer mod (1 + largest value representable in the // unsigned type). Since 2^32 is zero mod 2^8, 2^16, and 2^32, // converting to the unsigned int of the relevant width will correctly // perform step 4; in particular, the 2^32 possibly subtracted in step 5 // will become 0. // // Once we have step 4 done, we're just going to assume 2s-complement // representation and cast directly to the type we really want. // // So we can cast directly for all unsigned types and for int32_t; for // the smaller-width signed types we need to cast through the // corresponding unsigned type. typedef int32_t jstype; typedef int32_t intermediateType; static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v, jstype* retval) { return JS::ToInt32(cx, v, retval); } }; template<> struct PrimitiveConversionTraits<int8_t, eDefault> : PrimitiveConversionTraits_smallInt { typedef uint8_t intermediateType; }; template<> struct PrimitiveConversionTraits<uint8_t, eDefault> : PrimitiveConversionTraits_smallInt { }; template<> struct PrimitiveConversionTraits<int16_t, eDefault> : PrimitiveConversionTraits_smallInt { typedef uint16_t intermediateType; }; template<> struct PrimitiveConversionTraits<uint16_t, eDefault> : PrimitiveConversionTraits_smallInt { }; template<> struct PrimitiveConversionTraits<int32_t, eDefault> : PrimitiveConversionTraits_smallInt { }; template<> struct PrimitiveConversionTraits<uint32_t, eDefault> : PrimitiveConversionTraits_smallInt { }; template<> struct PrimitiveConversionTraits<int64_t, eDefault> { typedef int64_t jstype; typedef int64_t intermediateType; static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v, jstype* retval) { return JS::ToInt64(cx, v, retval); } }; template<> struct PrimitiveConversionTraits<uint64_t, eDefault> { typedef uint64_t jstype; typedef uint64_t intermediateType; static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v, jstype* retval) { return JS::ToUint64(cx, v, retval); } }; template<typename T> struct PrimitiveConversionTraits_Limits { static inline T min() { return std::numeric_limits<T>::min(); } static inline T max() { return std::numeric_limits<T>::max(); } }; template<> struct PrimitiveConversionTraits_Limits<int64_t> { static inline int64_t min() { return -(1LL << 53) + 1; } static inline int64_t max() { return (1LL << 53) - 1; } }; template<> struct PrimitiveConversionTraits_Limits<uint64_t> { static inline uint64_t min() { return 0; } static inline uint64_t max() { return (1LL << 53) - 1; } }; template<typename T, bool (*Enforce)(JSContext* cx, const double& d, T* retval)> struct PrimitiveConversionTraits_ToCheckedIntHelper { typedef T jstype; typedef T intermediateType; static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v, jstype* retval) { double intermediate; if (!JS::ToNumber(cx, v, &intermediate)) { return false; } return Enforce(cx, intermediate, retval); } }; template<typename T> inline bool PrimitiveConversionTraits_EnforceRange(JSContext* cx, const double& d, T* retval) { static_assert(std::numeric_limits<T>::is_integer, "This can only be applied to integers!"); if (!mozilla::IsFinite(d)) { return ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_NON_FINITE, TypeName<T>::value()); } bool neg = (d < 0); double rounded = floor(neg ? -d : d); rounded = neg ? -rounded : rounded; if (rounded < PrimitiveConversionTraits_Limits<T>::min() || rounded > PrimitiveConversionTraits_Limits<T>::max()) { return ThrowErrorMessage(cx, MSG_ENFORCE_RANGE_OUT_OF_RANGE, TypeName<T>::value()); } *retval = static_cast<T>(rounded); return true; } template<typename T> struct PrimitiveConversionTraits<T, eEnforceRange> : public PrimitiveConversionTraits_ToCheckedIntHelper<T, PrimitiveConversionTraits_EnforceRange<T> > { }; template<typename T> inline bool PrimitiveConversionTraits_Clamp(JSContext* cx, const double& d, T* retval) { static_assert(std::numeric_limits<T>::is_integer, "This can only be applied to integers!"); if (mozilla::IsNaN(d)) { *retval = 0; return true; } if (d >= PrimitiveConversionTraits_Limits<T>::max()) { *retval = PrimitiveConversionTraits_Limits<T>::max(); return true; } if (d <= PrimitiveConversionTraits_Limits<T>::min()) { *retval = PrimitiveConversionTraits_Limits<T>::min(); return true; } MOZ_ASSERT(mozilla::IsFinite(d)); // Banker's rounding (round ties towards even). // We move away from 0 by 0.5f and then truncate. That gets us the right // answer for any starting value except plus or minus N.5. With a starting // value of that form, we now have plus or minus N+1. If N is odd, this is // the correct result. If N is even, plus or minus N is the correct result. double toTruncate = (d < 0) ? d - 0.5 : d + 0.5; T truncated = static_cast<T>(toTruncate); if (truncated == toTruncate) { /* * It was a tie (since moving away from 0 by 0.5 gave us the exact integer * we want). Since we rounded away from 0, we either already have an even * number or we have an odd number but the number we want is one closer to * 0. So just unconditionally masking out the ones bit should do the trick * to get us the value we want. */ truncated &= ~1; } *retval = truncated; return true; } template<typename T> struct PrimitiveConversionTraits<T, eClamp> : public PrimitiveConversionTraits_ToCheckedIntHelper<T, PrimitiveConversionTraits_Clamp<T> > { }; template<ConversionBehavior B> struct PrimitiveConversionTraits<bool, B> : public DisallowedConversion<bool> {}; template<> struct PrimitiveConversionTraits<bool, eDefault> { typedef bool jstype; typedef bool intermediateType; static inline bool converter(JSContext* /* unused */, JS::Handle<JS::Value> v, jstype* retval) { *retval = JS::ToBoolean(v); return true; } }; template<ConversionBehavior B> struct PrimitiveConversionTraits<float, B> : public DisallowedConversion<float> {}; template<ConversionBehavior B> struct PrimitiveConversionTraits<double, B> : public DisallowedConversion<double> {}; struct PrimitiveConversionTraits_float { typedef double jstype; typedef double intermediateType; static inline bool converter(JSContext* cx, JS::Handle<JS::Value> v, jstype* retval) { return JS::ToNumber(cx, v, retval); } }; template<> struct PrimitiveConversionTraits<float, eDefault> : PrimitiveConversionTraits_float { }; template<> struct PrimitiveConversionTraits<double, eDefault> : PrimitiveConversionTraits_float { }; template<typename T, ConversionBehavior B> bool ValueToPrimitive(JSContext* cx, JS::Handle<JS::Value> v, T* retval) { typename PrimitiveConversionTraits<T, B>::jstype t; if (!PrimitiveConversionTraits<T, B>::converter(cx, v, &t)) return false; *retval = static_cast<T>( static_cast<typename PrimitiveConversionTraits<T, B>::intermediateType>(t)); return true; } } // namespace dom } // namespace mozilla #endif /* mozilla_dom_PrimitiveConversions_h */