summaryrefslogtreecommitdiffstats
path: root/dom/bindings/PrimitiveConversions.h
blob: 1c5b62ec8fdbb4a27be2d784a205c9f8de9453ad (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
/* -*- 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 "js/Conversions.h"
#include "js/RootingAPI.h"
#include "mozilla/Assertions.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/dom/BindingCallContext.h"

namespace mozilla::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,
                               const char* sourceDescription, 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,
                               const char* sourceDescription, 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,
                               const char* sourceDescription, 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,
                               const char* sourceDescription, 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, typename U,
          bool (*Enforce)(U cx, const char* sourceDescription, const double& d,
                          T* retval)>
struct PrimitiveConversionTraits_ToCheckedIntHelper {
  typedef T jstype;
  typedef T intermediateType;

  static inline bool converter(U cx, JS::Handle<JS::Value> v,
                               const char* sourceDescription, jstype* retval) {
    double intermediate;
    if (!JS::ToNumber(cx, v, &intermediate)) {
      return false;
    }

    return Enforce(cx, sourceDescription, intermediate, retval);
  }
};

template <typename T>
inline bool PrimitiveConversionTraits_EnforceRange(
    BindingCallContext& cx, const char* sourceDescription, const double& d,
    T* retval) {
  static_assert(std::numeric_limits<T>::is_integer,
                "This can only be applied to integers!");

  if (!std::isfinite(d)) {
    return cx.ThrowErrorMessage<MSG_ENFORCE_RANGE_NON_FINITE>(
        sourceDescription, 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 cx.ThrowErrorMessage<MSG_ENFORCE_RANGE_OUT_OF_RANGE>(
        sourceDescription, TypeName<T>::value());
  }

  *retval = static_cast<T>(rounded);
  return true;
}

template <typename T>
struct PrimitiveConversionTraits<T, eEnforceRange>
    : public PrimitiveConversionTraits_ToCheckedIntHelper<
          T, BindingCallContext&, PrimitiveConversionTraits_EnforceRange<T> > {
};

template <typename T>
inline bool PrimitiveConversionTraits_Clamp(JSContext* cx,
                                            const char* sourceDescription,
                                            const double& d, T* retval) {
  static_assert(std::numeric_limits<T>::is_integer,
                "This can only be applied to integers!");

  if (std::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(std::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, JSContext*, 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,
                               const char* sourceDescription, 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,
                               const char* sourceDescription, 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, typename U>
bool ValueToPrimitive(U& cx, JS::Handle<JS::Value> v,
                      const char* sourceDescription, T* retval) {
  typename PrimitiveConversionTraits<T, B>::jstype t;
  if (!PrimitiveConversionTraits<T, B>::converter(cx, v, sourceDescription, &t))
    return false;

  *retval = static_cast<T>(
      static_cast<typename PrimitiveConversionTraits<T, B>::intermediateType>(
          t));
  return true;
}

}  // namespace mozilla::dom

#endif /* mozilla_dom_PrimitiveConversions_h */