]>
Commit | Line | Data |
---|---|---|
7c673cae FG |
1 | /* |
2 | * Ecmascript specification algorithm and conversion helpers. | |
3 | * | |
4 | * These helpers encapsulate the primitive Ecmascript operation | |
5 | * semantics, and are used by the bytecode executor and the API | |
6 | * (among other places). Note that some primitives are only | |
7 | * implemented as part of the API and have no "internal" helper. | |
8 | * (This is the case when an internal helper would not really be | |
9 | * useful; e.g. the operation is rare, uses value stack heavily, | |
10 | * etc.) | |
11 | * | |
12 | * The operation arguments depend on what is required to implement | |
13 | * the operation: | |
14 | * | |
15 | * - If an operation is simple and stateless, and has no side | |
16 | * effects, it won't take an duk_hthread argument and its | |
17 | * arguments may be duk_tval pointers (which are safe as long | |
18 | * as no side effects take place). | |
19 | * | |
20 | * - If complex coercions are required (e.g. a "ToNumber" coercion) | |
21 | * or errors may be thrown, the operation takes an duk_hthread | |
22 | * argument. This also implies that the operation may have | |
23 | * arbitrary side effects, invalidating any duk_tval pointers. | |
24 | * | |
25 | * - For operations with potential side effects, arguments can be | |
26 | * taken in several ways: | |
27 | * | |
28 | * a) as duk_tval pointers, which makes sense if the "common case" | |
29 | * can be resolved without side effects (e.g. coercion); the | |
30 | * arguments are pushed to the valstack for coercion if | |
31 | * necessary | |
32 | * | |
33 | * b) as duk_tval values | |
34 | * | |
35 | * c) implicitly on value stack top | |
36 | * | |
37 | * d) as indices to the value stack | |
38 | * | |
39 | * Future work: | |
40 | * | |
41 | * - Argument styles may not be the most sensible in every case now. | |
42 | * | |
43 | * - In-place coercions might be useful for several operations, if | |
44 | * in-place coercion is OK for the bytecode executor and the API. | |
45 | */ | |
46 | ||
47 | #include "duk_internal.h" | |
48 | ||
49 | /* | |
50 | * [[DefaultValue]] (E5 Section 8.12.8) | |
51 | * | |
52 | * ==> implemented in the API. | |
53 | */ | |
54 | ||
55 | /* | |
56 | * ToPrimitive() (E5 Section 9.1) | |
57 | * | |
58 | * ==> implemented in the API. | |
59 | */ | |
60 | ||
61 | /* | |
62 | * ToBoolean() (E5 Section 9.2) | |
63 | */ | |
64 | ||
65 | DUK_INTERNAL duk_bool_t duk_js_toboolean(duk_tval *tv) { | |
66 | switch (DUK_TVAL_GET_TAG(tv)) { | |
67 | case DUK_TAG_UNDEFINED: | |
68 | case DUK_TAG_NULL: | |
69 | return 0; | |
70 | case DUK_TAG_BOOLEAN: | |
71 | return DUK_TVAL_GET_BOOLEAN(tv); | |
72 | case DUK_TAG_STRING: { | |
73 | duk_hstring *h = DUK_TVAL_GET_STRING(tv); | |
74 | DUK_ASSERT(h != NULL); | |
75 | return (DUK_HSTRING_GET_BYTELEN(h) > 0 ? 1 : 0); | |
76 | } | |
77 | case DUK_TAG_OBJECT: { | |
78 | return 1; | |
79 | } | |
80 | case DUK_TAG_BUFFER: { | |
81 | /* mimic semantics for strings */ | |
82 | duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); | |
83 | DUK_ASSERT(h != NULL); | |
84 | return (DUK_HBUFFER_GET_SIZE(h) > 0 ? 1 : 0); | |
85 | } | |
86 | case DUK_TAG_POINTER: { | |
87 | void *p = DUK_TVAL_GET_POINTER(tv); | |
88 | return (p != NULL ? 1 : 0); | |
89 | } | |
90 | case DUK_TAG_LIGHTFUNC: { | |
91 | return 1; | |
92 | } | |
93 | #if defined(DUK_USE_FASTINT) | |
94 | case DUK_TAG_FASTINT: | |
95 | if (DUK_TVAL_GET_FASTINT(tv) != 0) { | |
96 | return 1; | |
97 | } else { | |
98 | return 0; | |
99 | } | |
100 | #endif | |
101 | default: { | |
102 | /* number */ | |
103 | duk_double_t d; | |
104 | int c; | |
11fdf7f2 | 105 | DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
7c673cae FG |
106 | DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv)); |
107 | d = DUK_TVAL_GET_DOUBLE(tv); | |
108 | c = DUK_FPCLASSIFY((double) d); | |
109 | if (c == DUK_FP_ZERO || c == DUK_FP_NAN) { | |
110 | return 0; | |
111 | } else { | |
112 | return 1; | |
113 | } | |
114 | } | |
115 | } | |
116 | DUK_UNREACHABLE(); | |
117 | } | |
118 | ||
119 | /* | |
120 | * ToNumber() (E5 Section 9.3) | |
121 | * | |
122 | * Value to convert must be on stack top, and is popped before exit. | |
123 | * | |
124 | * See: http://www.cs.indiana.edu/~burger/FP-Printing-PLDI96.pdf | |
125 | * http://www.cs.indiana.edu/~burger/fp/index.html | |
126 | * | |
127 | * Notes on the conversion: | |
128 | * | |
129 | * - There are specific requirements on the accuracy of the conversion | |
130 | * through a "Mathematical Value" (MV), so this conversion is not | |
131 | * trivial. | |
132 | * | |
133 | * - Quick rejects (e.g. based on first char) are difficult because | |
134 | * the grammar allows leading and trailing white space. | |
135 | * | |
136 | * - Quick reject based on string length is difficult even after | |
137 | * accounting for white space; there may be arbitrarily many | |
138 | * decimal digits. | |
139 | * | |
140 | * - Standard grammar allows decimal values ("123"), hex values | |
141 | * ("0x123") and infinities | |
142 | * | |
143 | * - Unlike source code literals, ToNumber() coerces empty strings | |
144 | * and strings with only whitespace to zero (not NaN). | |
145 | */ | |
146 | ||
147 | /* E5 Section 9.3.1 */ | |
148 | DUK_LOCAL duk_double_t duk__tonumber_string_raw(duk_hthread *thr) { | |
149 | duk_context *ctx = (duk_context *) thr; | |
150 | duk_small_uint_t s2n_flags; | |
151 | duk_double_t d; | |
152 | ||
153 | /* Quite lenient, e.g. allow empty as zero, but don't allow trailing | |
154 | * garbage. | |
155 | */ | |
156 | s2n_flags = DUK_S2N_FLAG_TRIM_WHITE | | |
157 | DUK_S2N_FLAG_ALLOW_EXP | | |
158 | DUK_S2N_FLAG_ALLOW_PLUS | | |
159 | DUK_S2N_FLAG_ALLOW_MINUS | | |
160 | DUK_S2N_FLAG_ALLOW_INF | | |
161 | DUK_S2N_FLAG_ALLOW_FRAC | | |
162 | DUK_S2N_FLAG_ALLOW_NAKED_FRAC | | |
163 | DUK_S2N_FLAG_ALLOW_EMPTY_FRAC | | |
164 | DUK_S2N_FLAG_ALLOW_EMPTY_AS_ZERO | | |
165 | DUK_S2N_FLAG_ALLOW_LEADING_ZERO | | |
166 | DUK_S2N_FLAG_ALLOW_AUTO_HEX_INT; | |
167 | ||
168 | duk_numconv_parse(ctx, 10 /*radix*/, s2n_flags); | |
169 | d = duk_get_number(ctx, -1); | |
170 | duk_pop(ctx); | |
171 | ||
172 | return d; | |
173 | } | |
174 | ||
175 | DUK_INTERNAL duk_double_t duk_js_tonumber(duk_hthread *thr, duk_tval *tv) { | |
176 | duk_context *ctx = (duk_hthread *) thr; | |
177 | ||
178 | DUK_ASSERT(thr != NULL); | |
179 | DUK_ASSERT(tv != NULL); | |
180 | ||
181 | switch (DUK_TVAL_GET_TAG(tv)) { | |
182 | case DUK_TAG_UNDEFINED: { | |
183 | /* return a specific NaN (although not strictly necessary) */ | |
184 | duk_double_union du; | |
185 | DUK_DBLUNION_SET_NAN(&du); | |
186 | DUK_ASSERT(DUK_DBLUNION_IS_NORMALIZED(&du)); | |
187 | return du.d; | |
188 | } | |
189 | case DUK_TAG_NULL: { | |
190 | /* +0.0 */ | |
191 | return 0.0; | |
192 | } | |
193 | case DUK_TAG_BOOLEAN: { | |
194 | if (DUK_TVAL_IS_BOOLEAN_TRUE(tv)) { | |
195 | return 1.0; | |
196 | } | |
197 | return 0.0; | |
198 | } | |
199 | case DUK_TAG_STRING: { | |
200 | duk_hstring *h = DUK_TVAL_GET_STRING(tv); | |
201 | duk_push_hstring(ctx, h); | |
202 | return duk__tonumber_string_raw(thr); | |
203 | } | |
204 | case DUK_TAG_OBJECT: { | |
205 | /* Note: ToPrimitive(object,hint) == [[DefaultValue]](object,hint), | |
206 | * so use [[DefaultValue]] directly. | |
207 | */ | |
208 | duk_double_t d; | |
209 | duk_push_tval(ctx, tv); | |
210 | duk_to_defaultvalue(ctx, -1, DUK_HINT_NUMBER); /* 'tv' becomes invalid */ | |
211 | ||
212 | /* recursive call for a primitive value (guaranteed not to cause second | |
213 | * recursion). | |
214 | */ | |
215 | d = duk_js_tonumber(thr, duk_require_tval(ctx, -1)); | |
216 | ||
217 | duk_pop(ctx); | |
218 | return d; | |
219 | } | |
220 | case DUK_TAG_BUFFER: { | |
221 | /* Coerce like a string. This makes sense because addition also treats | |
222 | * buffers like strings. | |
223 | */ | |
224 | duk_hbuffer *h = DUK_TVAL_GET_BUFFER(tv); | |
225 | duk_push_hbuffer(ctx, h); | |
226 | duk_to_string(ctx, -1); /* XXX: expensive, but numconv now expects to see a string */ | |
227 | return duk__tonumber_string_raw(thr); | |
228 | } | |
229 | case DUK_TAG_POINTER: { | |
230 | /* Coerce like boolean */ | |
231 | void *p = DUK_TVAL_GET_POINTER(tv); | |
232 | return (p != NULL ? 1.0 : 0.0); | |
233 | } | |
234 | case DUK_TAG_LIGHTFUNC: { | |
235 | /* +(function(){}) -> NaN */ | |
236 | return DUK_DOUBLE_NAN; | |
237 | } | |
238 | #if defined(DUK_USE_FASTINT) | |
239 | case DUK_TAG_FASTINT: | |
240 | return (duk_double_t) DUK_TVAL_GET_FASTINT(tv); | |
241 | #endif | |
242 | default: { | |
243 | /* number */ | |
11fdf7f2 | 244 | DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv)); |
7c673cae FG |
245 | DUK_ASSERT(DUK_TVAL_IS_DOUBLE(tv)); |
246 | return DUK_TVAL_GET_DOUBLE(tv); | |
247 | } | |
248 | } | |
249 | ||
250 | DUK_UNREACHABLE(); | |
251 | } | |
252 | ||
253 | /* | |
254 | * ToInteger() (E5 Section 9.4) | |
255 | */ | |
256 | ||
257 | /* exposed, used by e.g. duk_bi_date.c */ | |
258 | DUK_INTERNAL duk_double_t duk_js_tointeger_number(duk_double_t x) { | |
259 | duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x); | |
260 | ||
261 | if (c == DUK_FP_NAN) { | |
262 | return 0.0; | |
263 | } else if (c == DUK_FP_ZERO || c == DUK_FP_INFINITE) { | |
264 | /* XXX: FP_ZERO check can be removed, the else clause handles it | |
265 | * correctly (preserving sign). | |
266 | */ | |
267 | return x; | |
268 | } else { | |
269 | duk_small_int_t s = (duk_small_int_t) DUK_SIGNBIT(x); | |
270 | x = DUK_FLOOR(DUK_FABS(x)); /* truncate towards zero */ | |
271 | if (s) { | |
272 | x = -x; | |
273 | } | |
274 | return x; | |
275 | } | |
276 | } | |
277 | ||
278 | DUK_INTERNAL duk_double_t duk_js_tointeger(duk_hthread *thr, duk_tval *tv) { | |
279 | /* XXX: fastint */ | |
280 | duk_double_t d = duk_js_tonumber(thr, tv); /* invalidates tv */ | |
281 | return duk_js_tointeger_number(d); | |
282 | } | |
283 | ||
284 | /* | |
285 | * ToInt32(), ToUint32(), ToUint16() (E5 Sections 9.5, 9.6, 9.7) | |
286 | */ | |
287 | ||
288 | /* combined algorithm matching E5 Sections 9.5 and 9.6 */ | |
289 | DUK_LOCAL duk_double_t duk__toint32_touint32_helper(duk_double_t x, duk_bool_t is_toint32) { | |
290 | duk_small_int_t c = (duk_small_int_t) DUK_FPCLASSIFY(x); | |
291 | duk_small_int_t s; | |
292 | ||
293 | if (c == DUK_FP_NAN || c == DUK_FP_ZERO || c == DUK_FP_INFINITE) { | |
294 | return 0.0; | |
295 | } | |
296 | ||
297 | ||
298 | /* x = sign(x) * floor(abs(x)), i.e. truncate towards zero, keep sign */ | |
299 | s = (duk_small_int_t) DUK_SIGNBIT(x); | |
300 | x = DUK_FLOOR(DUK_FABS(x)); | |
301 | if (s) { | |
302 | x = -x; | |
303 | } | |
304 | ||
305 | /* NOTE: fmod(x) result sign is same as sign of x, which | |
306 | * differs from what Javascript wants (see Section 9.6). | |
307 | */ | |
308 | ||
309 | x = DUK_FMOD(x, DUK_DOUBLE_2TO32); /* -> x in ]-2**32, 2**32[ */ | |
310 | ||
311 | if (x < 0.0) { | |
312 | x += DUK_DOUBLE_2TO32; | |
313 | } | |
314 | /* -> x in [0, 2**32[ */ | |
315 | ||
316 | if (is_toint32) { | |
317 | if (x >= DUK_DOUBLE_2TO31) { | |
318 | /* x in [2**31, 2**32[ */ | |
319 | ||
320 | x -= DUK_DOUBLE_2TO32; /* -> x in [-2**31,2**31[ */ | |
321 | } | |
322 | } | |
323 | ||
324 | return x; | |
325 | } | |
326 | ||
327 | DUK_INTERNAL duk_int32_t duk_js_toint32(duk_hthread *thr, duk_tval *tv) { | |
328 | duk_double_t d; | |
329 | ||
330 | #if defined(DUK_USE_FASTINT) | |
331 | if (DUK_TVAL_IS_FASTINT(tv)) { | |
332 | return DUK_TVAL_GET_FASTINT_I32(tv); | |
333 | } | |
334 | #endif | |
335 | ||
336 | d = duk_js_tonumber(thr, tv); /* invalidates tv */ | |
337 | d = duk__toint32_touint32_helper(d, 1); | |
338 | DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL); | |
339 | DUK_ASSERT(d >= -2147483648.0 && d <= 2147483647.0); /* [-0x80000000,0x7fffffff] */ | |
340 | DUK_ASSERT(d == ((duk_double_t) ((duk_int32_t) d))); /* whole, won't clip */ | |
341 | return (duk_int32_t) d; | |
342 | } | |
343 | ||
344 | ||
345 | DUK_INTERNAL duk_uint32_t duk_js_touint32(duk_hthread *thr, duk_tval *tv) { | |
346 | duk_double_t d; | |
347 | ||
348 | #if defined(DUK_USE_FASTINT) | |
349 | if (DUK_TVAL_IS_FASTINT(tv)) { | |
350 | return DUK_TVAL_GET_FASTINT_U32(tv); | |
351 | } | |
352 | #endif | |
353 | ||
354 | d = duk_js_tonumber(thr, tv); /* invalidates tv */ | |
355 | d = duk__toint32_touint32_helper(d, 0); | |
356 | DUK_ASSERT(DUK_FPCLASSIFY(d) == DUK_FP_ZERO || DUK_FPCLASSIFY(d) == DUK_FP_NORMAL); | |
357 | DUK_ASSERT(d >= 0.0 && d <= 4294967295.0); /* [0x00000000, 0xffffffff] */ | |
358 | DUK_ASSERT(d == ((duk_double_t) ((duk_uint32_t) d))); /* whole, won't clip */ | |
359 | return (duk_uint32_t) d; | |
360 | ||
361 | } | |
362 | ||
363 | DUK_INTERNAL duk_uint16_t duk_js_touint16(duk_hthread *thr, duk_tval *tv) { | |
364 | /* should be a safe way to compute this */ | |
365 | return (duk_uint16_t) (duk_js_touint32(thr, tv) & 0x0000ffffU); | |
366 | } | |
367 | ||
368 | /* | |
369 | * ToString() (E5 Section 9.8) | |
370 | * | |
371 | * ==> implemented in the API. | |
372 | */ | |
373 | ||
374 | /* | |
375 | * ToObject() (E5 Section 9.9) | |
376 | * | |
377 | * ==> implemented in the API. | |
378 | */ | |
379 | ||
380 | /* | |
381 | * CheckObjectCoercible() (E5 Section 9.10) | |
382 | * | |
383 | * Note: no API equivalent now. | |
384 | */ | |
385 | ||
386 | #if 0 /* unused */ | |
387 | DUK_INTERNAL void duk_js_checkobjectcoercible(duk_hthread *thr, duk_tval *tv_x) { | |
388 | duk_small_uint_t tag = DUK_TVAL_GET_TAG(tv_x); | |
389 | ||
390 | /* Note: this must match ToObject() behavior */ | |
391 | ||
392 | if (tag == DUK_TAG_UNDEFINED || | |
393 | tag == DUK_TAG_NULL || | |
394 | tag == DUK_TAG_POINTER || | |
395 | tag == DUK_TAG_BUFFER) { | |
11fdf7f2 | 396 | DUK_ERROR_TYPE(thr, "not object coercible"); |
7c673cae FG |
397 | } |
398 | } | |
399 | #endif | |
400 | ||
401 | /* | |
402 | * IsCallable() (E5 Section 9.11) | |
403 | * | |
404 | * XXX: API equivalent is a separate implementation now, and this has | |
405 | * currently no callers. | |
406 | */ | |
407 | ||
408 | #if 0 /* unused */ | |
409 | DUK_INTERNAL duk_bool_t duk_js_iscallable(duk_tval *tv_x) { | |
410 | duk_hobject *obj; | |
411 | ||
412 | if (!DUK_TVAL_IS_OBJECT(tv_x)) { | |
413 | return 0; | |
414 | } | |
415 | obj = DUK_TVAL_GET_OBJECT(tv_x); | |
416 | DUK_ASSERT(obj != NULL); | |
417 | ||
418 | return DUK_HOBJECT_IS_CALLABLE(obj); | |
419 | } | |
420 | #endif | |
421 | ||
422 | /* | |
423 | * Loose equality, strict equality, and SameValue (E5 Sections 11.9.1, 11.9.4, | |
424 | * 9.12). These have much in common so they can share some helpers. | |
425 | * | |
426 | * Future work notes: | |
427 | * | |
428 | * - Current implementation (and spec definition) has recursion; this should | |
429 | * be fixed if possible. | |
430 | * | |
431 | * - String-to-number coercion should be possible without going through the | |
432 | * value stack (and be more compact) if a shared helper is invoked. | |
433 | */ | |
434 | ||
435 | /* Note that this is the same operation for strict and loose equality: | |
436 | * - E5 Section 11.9.3, step 1.c (loose) | |
437 | * - E5 Section 11.9.6, step 4 (strict) | |
438 | */ | |
439 | ||
440 | DUK_LOCAL duk_bool_t duk__js_equals_number(duk_double_t x, duk_double_t y) { | |
441 | #if defined(DUK_USE_PARANOID_MATH) | |
442 | /* Straightforward algorithm, makes fewer compiler assumptions. */ | |
443 | duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x); | |
444 | duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y); | |
445 | if (cx == DUK_FP_NAN || cy == DUK_FP_NAN) { | |
446 | return 0; | |
447 | } | |
448 | if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) { | |
449 | return 1; | |
450 | } | |
451 | if (x == y) { | |
452 | return 1; | |
453 | } | |
454 | return 0; | |
455 | #else /* DUK_USE_PARANOID_MATH */ | |
456 | /* Better equivalent algorithm. If the compiler is compliant, C and | |
457 | * Ecmascript semantics are identical for this particular comparison. | |
458 | * In particular, NaNs must never compare equal and zeroes must compare | |
459 | * equal regardless of sign. Could also use a macro, but this inlines | |
460 | * already nicely (no difference on gcc, for instance). | |
461 | */ | |
462 | if (x == y) { | |
463 | /* IEEE requires that NaNs compare false */ | |
464 | DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN); | |
465 | DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN); | |
466 | return 1; | |
467 | } else { | |
468 | /* IEEE requires that zeros compare the same regardless | |
469 | * of their signed, so if both x and y are zeroes, they | |
470 | * are caught above. | |
471 | */ | |
472 | DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO)); | |
473 | return 0; | |
474 | } | |
475 | #endif /* DUK_USE_PARANOID_MATH */ | |
476 | } | |
477 | ||
478 | DUK_LOCAL duk_bool_t duk__js_samevalue_number(duk_double_t x, duk_double_t y) { | |
479 | #if defined(DUK_USE_PARANOID_MATH) | |
480 | duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x); | |
481 | duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y); | |
482 | ||
483 | if (cx == DUK_FP_NAN && cy == DUK_FP_NAN) { | |
484 | /* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */ | |
485 | return 1; | |
486 | } | |
487 | if (cx == DUK_FP_ZERO && cy == DUK_FP_ZERO) { | |
488 | /* Note: cannot assume that a non-zero return value of signbit() would | |
489 | * always be the same -- hence cannot (portably) use something like: | |
490 | * | |
491 | * signbit(x) == signbit(y) | |
492 | */ | |
493 | duk_small_int_t sx = (DUK_SIGNBIT(x) ? 1 : 0); | |
494 | duk_small_int_t sy = (DUK_SIGNBIT(y) ? 1 : 0); | |
495 | return (sx == sy); | |
496 | } | |
497 | ||
498 | /* normal comparison; known: | |
499 | * - both x and y are not NaNs (but one of them can be) | |
500 | * - both x and y are not zero (but one of them can be) | |
501 | * - x and y may be denormal or infinite | |
502 | */ | |
503 | ||
504 | return (x == y); | |
505 | #else /* DUK_USE_PARANOID_MATH */ | |
506 | duk_small_int_t cx = (duk_small_int_t) DUK_FPCLASSIFY(x); | |
507 | duk_small_int_t cy = (duk_small_int_t) DUK_FPCLASSIFY(y); | |
508 | ||
509 | if (x == y) { | |
510 | /* IEEE requires that NaNs compare false */ | |
511 | DUK_ASSERT(DUK_FPCLASSIFY(x) != DUK_FP_NAN); | |
512 | DUK_ASSERT(DUK_FPCLASSIFY(y) != DUK_FP_NAN); | |
513 | ||
514 | /* Using classification has smaller footprint than direct comparison. */ | |
515 | if (DUK_UNLIKELY(cx == DUK_FP_ZERO && cy == DUK_FP_ZERO)) { | |
516 | /* Note: cannot assume that a non-zero return value of signbit() would | |
517 | * always be the same -- hence cannot (portably) use something like: | |
518 | * | |
519 | * signbit(x) == signbit(y) | |
520 | */ | |
521 | duk_small_int_t sx = (DUK_SIGNBIT(x) ? 1 : 0); | |
522 | duk_small_int_t sy = (DUK_SIGNBIT(y) ? 1 : 0); | |
523 | return (sx == sy); | |
524 | } | |
525 | return 1; | |
526 | } else { | |
527 | /* IEEE requires that zeros compare the same regardless | |
528 | * of their signed, so if both x and y are zeroes, they | |
529 | * are caught above. | |
530 | */ | |
531 | DUK_ASSERT(!(DUK_FPCLASSIFY(x) == DUK_FP_ZERO && DUK_FPCLASSIFY(y) == DUK_FP_ZERO)); | |
532 | ||
533 | /* Difference to non-strict/strict comparison is that NaNs compare | |
534 | * equal and signed zero signs matter. | |
535 | */ | |
536 | if (DUK_UNLIKELY(cx == DUK_FP_NAN && cy == DUK_FP_NAN)) { | |
537 | /* SameValue(NaN, NaN) = true, regardless of NaN sign or extra bits */ | |
538 | return 1; | |
539 | } | |
540 | return 0; | |
541 | } | |
542 | #endif /* DUK_USE_PARANOID_MATH */ | |
543 | } | |
544 | ||
545 | DUK_INTERNAL duk_bool_t duk_js_equals_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) { | |
546 | duk_context *ctx = (duk_context *) thr; | |
547 | duk_tval *tv_tmp; | |
548 | ||
549 | /* If flags != 0 (strict or SameValue), thr can be NULL. For loose | |
550 | * equals comparison it must be != NULL. | |
551 | */ | |
552 | DUK_ASSERT(flags != 0 || thr != NULL); | |
553 | ||
554 | /* | |
555 | * Same type? | |
556 | * | |
557 | * Note: since number values have no explicit tag in the 8-byte | |
558 | * representation, need the awkward if + switch. | |
559 | */ | |
560 | ||
561 | #if defined(DUK_USE_FASTINT) | |
562 | if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) { | |
563 | if (DUK_TVAL_GET_FASTINT(tv_x) == DUK_TVAL_GET_FASTINT(tv_y)) { | |
564 | return 1; | |
565 | } else { | |
566 | return 0; | |
567 | } | |
568 | } | |
569 | else | |
570 | #endif | |
571 | if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) { | |
572 | /* Catches both doubles and cases where only one argument is a fastint */ | |
573 | if (DUK_UNLIKELY((flags & DUK_EQUALS_FLAG_SAMEVALUE) != 0)) { | |
574 | /* SameValue */ | |
575 | return duk__js_samevalue_number(DUK_TVAL_GET_NUMBER(tv_x), | |
576 | DUK_TVAL_GET_NUMBER(tv_y)); | |
577 | } else { | |
578 | /* equals and strict equals */ | |
579 | return duk__js_equals_number(DUK_TVAL_GET_NUMBER(tv_x), | |
580 | DUK_TVAL_GET_NUMBER(tv_y)); | |
581 | } | |
582 | } else if (DUK_TVAL_GET_TAG(tv_x) == DUK_TVAL_GET_TAG(tv_y)) { | |
583 | switch (DUK_TVAL_GET_TAG(tv_x)) { | |
584 | case DUK_TAG_UNDEFINED: | |
585 | case DUK_TAG_NULL: { | |
586 | return 1; | |
587 | } | |
588 | case DUK_TAG_BOOLEAN: { | |
589 | return DUK_TVAL_GET_BOOLEAN(tv_x) == DUK_TVAL_GET_BOOLEAN(tv_y); | |
590 | } | |
591 | case DUK_TAG_POINTER: { | |
592 | return DUK_TVAL_GET_POINTER(tv_x) == DUK_TVAL_GET_POINTER(tv_y); | |
593 | } | |
594 | case DUK_TAG_STRING: | |
595 | case DUK_TAG_OBJECT: { | |
596 | /* heap pointer comparison suffices */ | |
597 | return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y); | |
598 | } | |
599 | case DUK_TAG_BUFFER: { | |
600 | if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) { | |
601 | /* heap pointer comparison suffices */ | |
602 | return DUK_TVAL_GET_HEAPHDR(tv_x) == DUK_TVAL_GET_HEAPHDR(tv_y); | |
603 | } else { | |
604 | /* non-strict equality for buffers compares contents */ | |
605 | duk_hbuffer *h_x = DUK_TVAL_GET_BUFFER(tv_x); | |
606 | duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); | |
607 | duk_size_t len_x = DUK_HBUFFER_GET_SIZE(h_x); | |
608 | duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); | |
609 | void *buf_x; | |
610 | void *buf_y; | |
611 | if (len_x != len_y) { | |
612 | return 0; | |
613 | } | |
614 | buf_x = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_x); | |
615 | buf_y = (void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); | |
616 | /* if len_x == len_y == 0, buf_x and/or buf_y may | |
617 | * be NULL, but that's OK. | |
618 | */ | |
619 | DUK_ASSERT(len_x == len_y); | |
620 | DUK_ASSERT(len_x == 0 || buf_x != NULL); | |
621 | DUK_ASSERT(len_y == 0 || buf_y != NULL); | |
11fdf7f2 | 622 | return (DUK_MEMCMP((const void *) buf_x, (const void *) buf_y, (size_t) len_x) == 0) ? 1 : 0; |
7c673cae FG |
623 | } |
624 | } | |
625 | case DUK_TAG_LIGHTFUNC: { | |
626 | /* At least 'magic' has a significant impact on function | |
627 | * identity. | |
628 | */ | |
629 | duk_small_uint_t lf_flags_x; | |
630 | duk_small_uint_t lf_flags_y; | |
631 | duk_c_function func_x; | |
632 | duk_c_function func_y; | |
633 | ||
634 | DUK_TVAL_GET_LIGHTFUNC(tv_x, func_x, lf_flags_x); | |
635 | DUK_TVAL_GET_LIGHTFUNC(tv_y, func_y, lf_flags_y); | |
636 | return ((func_x == func_y) && (lf_flags_x == lf_flags_y)) ? 1 : 0; | |
637 | } | |
638 | #if defined(DUK_USE_FASTINT) | |
639 | case DUK_TAG_FASTINT: | |
640 | #endif | |
641 | default: { | |
11fdf7f2 TL |
642 | DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_x)); |
643 | DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_y)); | |
7c673cae FG |
644 | DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x)); |
645 | DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_y)); | |
646 | DUK_UNREACHABLE(); | |
647 | return 0; | |
648 | } | |
649 | } | |
650 | } | |
651 | ||
652 | if ((flags & (DUK_EQUALS_FLAG_STRICT | DUK_EQUALS_FLAG_SAMEVALUE)) != 0) { | |
653 | return 0; | |
654 | } | |
655 | ||
656 | DUK_ASSERT(flags == 0); /* non-strict equality from here on */ | |
657 | ||
658 | /* | |
659 | * Types are different; various cases for non-strict comparison | |
660 | * | |
661 | * Since comparison is symmetric, we use a "swap trick" to reduce | |
662 | * code size. | |
663 | */ | |
664 | ||
665 | /* Undefined/null are considered equal (e.g. "null == undefined" -> true). */ | |
666 | if ((DUK_TVAL_IS_UNDEFINED(tv_x) && DUK_TVAL_IS_NULL(tv_y)) || | |
667 | (DUK_TVAL_IS_NULL(tv_x) && DUK_TVAL_IS_UNDEFINED(tv_y))) { | |
668 | return 1; | |
669 | } | |
670 | ||
671 | /* Number/string-or-buffer -> coerce string to number (e.g. "'1.5' == 1.5" -> true). */ | |
672 | if (DUK_TVAL_IS_NUMBER(tv_x) && (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) { | |
673 | /* the next 'if' is guaranteed to match after swap */ | |
674 | tv_tmp = tv_x; | |
675 | tv_x = tv_y; | |
676 | tv_y = tv_tmp; | |
677 | } | |
678 | if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) && DUK_TVAL_IS_NUMBER(tv_y)) { | |
679 | /* XXX: this is possible without resorting to the value stack */ | |
680 | duk_double_t d1, d2; | |
681 | d2 = DUK_TVAL_GET_NUMBER(tv_y); | |
682 | duk_push_tval(ctx, tv_x); | |
683 | duk_to_string(ctx, -1); /* buffer values are coerced first to string here */ | |
684 | duk_to_number(ctx, -1); | |
685 | d1 = duk_require_number(ctx, -1); | |
686 | duk_pop(ctx); | |
687 | return duk__js_equals_number(d1, d2); | |
688 | } | |
689 | ||
690 | /* Buffer/string -> compare contents. */ | |
691 | if (DUK_TVAL_IS_BUFFER(tv_x) && DUK_TVAL_IS_STRING(tv_y)) { | |
692 | tv_tmp = tv_x; | |
693 | tv_x = tv_y; | |
694 | tv_y = tv_tmp; | |
695 | } | |
696 | if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_BUFFER(tv_y)) { | |
697 | duk_hstring *h_x = DUK_TVAL_GET_STRING(tv_x); | |
698 | duk_hbuffer *h_y = DUK_TVAL_GET_BUFFER(tv_y); | |
699 | duk_size_t len_x = DUK_HSTRING_GET_BYTELEN(h_x); | |
700 | duk_size_t len_y = DUK_HBUFFER_GET_SIZE(h_y); | |
11fdf7f2 TL |
701 | const void *buf_x; |
702 | const void *buf_y; | |
7c673cae FG |
703 | if (len_x != len_y) { |
704 | return 0; | |
705 | } | |
11fdf7f2 TL |
706 | buf_x = (const void *) DUK_HSTRING_GET_DATA(h_x); |
707 | buf_y = (const void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h_y); | |
7c673cae FG |
708 | /* if len_x == len_y == 0, buf_x and/or buf_y may |
709 | * be NULL, but that's OK. | |
710 | */ | |
711 | DUK_ASSERT(len_x == len_y); | |
712 | DUK_ASSERT(len_x == 0 || buf_x != NULL); | |
713 | DUK_ASSERT(len_y == 0 || buf_y != NULL); | |
11fdf7f2 | 714 | return (DUK_MEMCMP((const void *) buf_x, (const void *) buf_y, (size_t) len_x) == 0) ? 1 : 0; |
7c673cae FG |
715 | } |
716 | ||
717 | /* Boolean/any -> coerce boolean to number and try again. If boolean is | |
718 | * compared to a pointer, the final comparison after coercion now always | |
719 | * yields false (as pointer vs. number compares to false), but this is | |
720 | * not special cased. | |
721 | */ | |
722 | if (DUK_TVAL_IS_BOOLEAN(tv_x)) { | |
723 | tv_tmp = tv_x; | |
724 | tv_x = tv_y; | |
725 | tv_y = tv_tmp; | |
726 | } | |
727 | if (DUK_TVAL_IS_BOOLEAN(tv_y)) { | |
728 | /* ToNumber(bool) is +1.0 or 0.0. Tagged boolean value is always 0 or 1. */ | |
729 | duk_bool_t rc; | |
730 | DUK_ASSERT(DUK_TVAL_GET_BOOLEAN(tv_y) == 0 || DUK_TVAL_GET_BOOLEAN(tv_y) == 1); | |
731 | duk_push_tval(ctx, tv_x); | |
732 | duk_push_int(ctx, DUK_TVAL_GET_BOOLEAN(tv_y)); | |
11fdf7f2 TL |
733 | rc = duk_js_equals_helper(thr, |
734 | DUK_GET_TVAL_NEGIDX(ctx, -2), | |
735 | DUK_GET_TVAL_NEGIDX(ctx, -1), | |
736 | 0 /*flags:nonstrict*/); | |
7c673cae FG |
737 | duk_pop_2(ctx); |
738 | return rc; | |
739 | } | |
740 | ||
741 | /* String-number-buffer/object -> coerce object to primitive (apparently without hint), then try again. */ | |
742 | if ((DUK_TVAL_IS_STRING(tv_x) || DUK_TVAL_IS_NUMBER(tv_x) || DUK_TVAL_IS_BUFFER(tv_x)) && | |
743 | DUK_TVAL_IS_OBJECT(tv_y)) { | |
744 | tv_tmp = tv_x; | |
745 | tv_x = tv_y; | |
746 | tv_y = tv_tmp; | |
747 | } | |
748 | if (DUK_TVAL_IS_OBJECT(tv_x) && | |
749 | (DUK_TVAL_IS_STRING(tv_y) || DUK_TVAL_IS_NUMBER(tv_y) || DUK_TVAL_IS_BUFFER(tv_y))) { | |
750 | duk_bool_t rc; | |
751 | duk_push_tval(ctx, tv_x); | |
752 | duk_push_tval(ctx, tv_y); | |
753 | duk_to_primitive(ctx, -2, DUK_HINT_NONE); /* apparently no hint? */ | |
11fdf7f2 TL |
754 | rc = duk_js_equals_helper(thr, |
755 | DUK_GET_TVAL_NEGIDX(ctx, -2), | |
756 | DUK_GET_TVAL_NEGIDX(ctx, -1), | |
757 | 0 /*flags:nonstrict*/); | |
7c673cae FG |
758 | duk_pop_2(ctx); |
759 | return rc; | |
760 | } | |
761 | ||
762 | /* Nothing worked -> not equal. */ | |
763 | return 0; | |
764 | } | |
765 | ||
766 | /* | |
767 | * Comparisons (x >= y, x > y, x <= y, x < y) | |
768 | * | |
769 | * E5 Section 11.8.5: implement 'x < y' and then use negate and eval_left_first | |
770 | * flags to get the rest. | |
771 | */ | |
772 | ||
773 | /* XXX: this should probably just operate on the stack top, because it | |
774 | * needs to push stuff on the stack anyway... | |
775 | */ | |
776 | ||
777 | DUK_INTERNAL duk_small_int_t duk_js_data_compare(const duk_uint8_t *buf1, const duk_uint8_t *buf2, duk_size_t len1, duk_size_t len2) { | |
778 | duk_size_t prefix_len; | |
779 | duk_small_int_t rc; | |
780 | ||
781 | prefix_len = (len1 <= len2 ? len1 : len2); | |
782 | ||
11fdf7f2 TL |
783 | /* DUK_MEMCMP() is guaranteed to return zero (equal) for zero length |
784 | * inputs so no zero length check is needed. | |
7c673cae | 785 | */ |
11fdf7f2 TL |
786 | rc = DUK_MEMCMP((const void *) buf1, |
787 | (const void *) buf2, | |
788 | (size_t) prefix_len); | |
7c673cae FG |
789 | |
790 | if (rc < 0) { | |
791 | return -1; | |
792 | } else if (rc > 0) { | |
793 | return 1; | |
794 | } | |
795 | ||
796 | /* prefix matches, lengths matter now */ | |
797 | if (len1 < len2) { | |
798 | /* e.g. "x" < "xx" */ | |
799 | return -1; | |
800 | } else if (len1 > len2) { | |
801 | return 1; | |
802 | } | |
803 | ||
804 | return 0; | |
805 | } | |
806 | ||
807 | DUK_INTERNAL duk_small_int_t duk_js_string_compare(duk_hstring *h1, duk_hstring *h2) { | |
808 | /* | |
809 | * String comparison (E5 Section 11.8.5, step 4), which | |
810 | * needs to compare codepoint by codepoint. | |
811 | * | |
812 | * However, UTF-8 allows us to use strcmp directly: the shared | |
813 | * prefix will be encoded identically (UTF-8 has unique encoding) | |
814 | * and the first differing character can be compared with a simple | |
815 | * unsigned byte comparison (which strcmp does). | |
816 | * | |
817 | * This will not work properly for non-xutf-8 strings, but this | |
818 | * is not an issue for compliance. | |
819 | */ | |
820 | ||
821 | DUK_ASSERT(h1 != NULL); | |
822 | DUK_ASSERT(h2 != NULL); | |
823 | ||
824 | return duk_js_data_compare((const duk_uint8_t *) DUK_HSTRING_GET_DATA(h1), | |
825 | (const duk_uint8_t *) DUK_HSTRING_GET_DATA(h2), | |
826 | (duk_size_t) DUK_HSTRING_GET_BYTELEN(h1), | |
827 | (duk_size_t) DUK_HSTRING_GET_BYTELEN(h2)); | |
828 | } | |
829 | ||
830 | #if 0 /* unused */ | |
831 | DUK_INTERNAL duk_small_int_t duk_js_buffer_compare(duk_heap *heap, duk_hbuffer *h1, duk_hbuffer *h2) { | |
832 | /* Similar to String comparison. */ | |
833 | ||
834 | DUK_ASSERT(h1 != NULL); | |
835 | DUK_ASSERT(h2 != NULL); | |
836 | DUK_UNREF(heap); | |
837 | ||
838 | return duk_js_data_compare((const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(heap, h1), | |
839 | (const duk_uint8_t *) DUK_HBUFFER_GET_DATA_PTR(heap, h2), | |
840 | (duk_size_t) DUK_HBUFFER_GET_SIZE(h1), | |
841 | (duk_size_t) DUK_HBUFFER_GET_SIZE(h2)); | |
842 | } | |
843 | #endif | |
844 | ||
845 | DUK_INTERNAL duk_bool_t duk_js_compare_helper(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y, duk_small_int_t flags) { | |
846 | duk_context *ctx = (duk_context *) thr; | |
847 | duk_double_t d1, d2; | |
848 | duk_small_int_t c1, c2; | |
849 | duk_small_int_t s1, s2; | |
850 | duk_small_int_t rc; | |
851 | duk_bool_t retval; | |
852 | ||
853 | /* Fast path for fastints */ | |
854 | #if defined(DUK_USE_FASTINT) | |
855 | if (DUK_TVAL_IS_FASTINT(tv_x) && DUK_TVAL_IS_FASTINT(tv_y)) { | |
856 | duk_int64_t v1 = DUK_TVAL_GET_FASTINT(tv_x); | |
857 | duk_int64_t v2 = DUK_TVAL_GET_FASTINT(tv_y); | |
858 | if (v1 < v2) { | |
859 | /* 'lt is true' */ | |
860 | retval = 1; | |
861 | } else { | |
862 | retval = 0; | |
863 | } | |
864 | if (flags & DUK_COMPARE_FLAG_NEGATE) { | |
865 | retval ^= 1; | |
866 | } | |
867 | return retval; | |
868 | } | |
869 | #endif /* DUK_USE_FASTINT */ | |
870 | ||
871 | /* Fast path for numbers (one of which may be a fastint) */ | |
872 | #if 1 /* XXX: make fast paths optional for size minimization? */ | |
873 | if (DUK_TVAL_IS_NUMBER(tv_x) && DUK_TVAL_IS_NUMBER(tv_y)) { | |
874 | d1 = DUK_TVAL_GET_NUMBER(tv_x); | |
875 | d2 = DUK_TVAL_GET_NUMBER(tv_y); | |
876 | c1 = DUK_FPCLASSIFY(d1); | |
877 | c2 = DUK_FPCLASSIFY(d2); | |
878 | ||
879 | if (c1 == DUK_FP_NORMAL && c2 == DUK_FP_NORMAL) { | |
880 | /* XXX: this is a very narrow check, and doesn't cover | |
881 | * zeroes, subnormals, infinities, which compare normally. | |
882 | */ | |
883 | ||
884 | if (d1 < d2) { | |
885 | /* 'lt is true' */ | |
886 | retval = 1; | |
887 | } else { | |
888 | retval = 0; | |
889 | } | |
890 | if (flags & DUK_COMPARE_FLAG_NEGATE) { | |
891 | retval ^= 1; | |
892 | } | |
893 | return retval; | |
894 | } | |
895 | } | |
896 | #endif | |
897 | ||
898 | /* Slow path */ | |
899 | ||
900 | duk_push_tval(ctx, tv_x); | |
901 | duk_push_tval(ctx, tv_y); | |
902 | ||
903 | if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) { | |
904 | duk_to_primitive(ctx, -2, DUK_HINT_NUMBER); | |
905 | duk_to_primitive(ctx, -1, DUK_HINT_NUMBER); | |
906 | } else { | |
907 | duk_to_primitive(ctx, -1, DUK_HINT_NUMBER); | |
908 | duk_to_primitive(ctx, -2, DUK_HINT_NUMBER); | |
909 | } | |
910 | ||
911 | /* Note: reuse variables */ | |
11fdf7f2 TL |
912 | tv_x = DUK_GET_TVAL_NEGIDX(ctx, -2); |
913 | tv_y = DUK_GET_TVAL_NEGIDX(ctx, -1); | |
7c673cae FG |
914 | |
915 | if (DUK_TVAL_IS_STRING(tv_x) && DUK_TVAL_IS_STRING(tv_y)) { | |
916 | duk_hstring *h1 = DUK_TVAL_GET_STRING(tv_x); | |
917 | duk_hstring *h2 = DUK_TVAL_GET_STRING(tv_y); | |
918 | DUK_ASSERT(h1 != NULL); | |
919 | DUK_ASSERT(h2 != NULL); | |
920 | ||
921 | rc = duk_js_string_compare(h1, h2); | |
922 | if (rc < 0) { | |
923 | goto lt_true; | |
924 | } else { | |
925 | goto lt_false; | |
926 | } | |
927 | } else { | |
928 | /* Ordering should not matter (E5 Section 11.8.5, step 3.a) but | |
929 | * preserve it just in case. | |
930 | */ | |
931 | ||
932 | if (flags & DUK_COMPARE_FLAG_EVAL_LEFT_FIRST) { | |
933 | d1 = duk_to_number(ctx, -2); | |
934 | d2 = duk_to_number(ctx, -1); | |
935 | } else { | |
936 | d2 = duk_to_number(ctx, -1); | |
937 | d1 = duk_to_number(ctx, -2); | |
938 | } | |
939 | ||
940 | c1 = (duk_small_int_t) DUK_FPCLASSIFY(d1); | |
941 | s1 = (duk_small_int_t) DUK_SIGNBIT(d1); | |
942 | c2 = (duk_small_int_t) DUK_FPCLASSIFY(d2); | |
943 | s2 = (duk_small_int_t) DUK_SIGNBIT(d2); | |
944 | ||
945 | if (c1 == DUK_FP_NAN || c2 == DUK_FP_NAN) { | |
946 | goto lt_undefined; | |
947 | } | |
948 | ||
949 | if (c1 == DUK_FP_ZERO && c2 == DUK_FP_ZERO) { | |
950 | /* For all combinations: +0 < +0, +0 < -0, -0 < +0, -0 < -0, | |
951 | * steps e, f, and g. | |
952 | */ | |
953 | goto lt_false; | |
954 | } | |
955 | ||
956 | if (d1 == d2) { | |
957 | goto lt_false; | |
958 | } | |
959 | ||
960 | if (c1 == DUK_FP_INFINITE && s1 == 0) { | |
961 | /* x == +Infinity */ | |
962 | goto lt_false; | |
963 | } | |
964 | ||
965 | if (c2 == DUK_FP_INFINITE && s2 == 0) { | |
966 | /* y == +Infinity */ | |
967 | goto lt_true; | |
968 | } | |
969 | ||
970 | if (c2 == DUK_FP_INFINITE && s2 != 0) { | |
971 | /* y == -Infinity */ | |
972 | goto lt_false; | |
973 | } | |
974 | ||
975 | if (c1 == DUK_FP_INFINITE && s1 != 0) { | |
976 | /* x == -Infinity */ | |
977 | goto lt_true; | |
978 | } | |
979 | ||
980 | if (d1 < d2) { | |
981 | goto lt_true; | |
982 | } | |
983 | ||
984 | goto lt_false; | |
985 | } | |
986 | ||
987 | lt_undefined: | |
988 | /* Note: undefined from Section 11.8.5 always results in false | |
989 | * return (see e.g. Section 11.8.3) - hence special treatment here. | |
990 | */ | |
991 | retval = 0; | |
992 | goto cleanup; | |
993 | ||
994 | lt_true: | |
995 | if (flags & DUK_COMPARE_FLAG_NEGATE) { | |
996 | retval = 0; | |
997 | goto cleanup; | |
998 | } else { | |
999 | retval = 1; | |
1000 | goto cleanup; | |
1001 | } | |
1002 | /* never here */ | |
1003 | ||
1004 | lt_false: | |
1005 | if (flags & DUK_COMPARE_FLAG_NEGATE) { | |
1006 | retval = 1; | |
1007 | goto cleanup; | |
1008 | } else { | |
1009 | retval = 0; | |
1010 | goto cleanup; | |
1011 | } | |
1012 | /* never here */ | |
1013 | ||
1014 | cleanup: | |
1015 | duk_pop_2(ctx); | |
1016 | return retval; | |
1017 | } | |
1018 | ||
1019 | /* | |
1020 | * instanceof | |
1021 | */ | |
1022 | ||
1023 | /* | |
1024 | * E5 Section 11.8.6 describes the main algorithm, which uses | |
1025 | * [[HasInstance]]. [[HasInstance]] is defined for only | |
1026 | * function objects: | |
1027 | * | |
1028 | * - Normal functions: | |
1029 | * E5 Section 15.3.5.3 | |
1030 | * - Functions established with Function.prototype.bind(): | |
1031 | * E5 Section 15.3.4.5.3 | |
1032 | * | |
1033 | * For other objects, a TypeError is thrown. | |
1034 | * | |
1035 | * Limited Proxy support: don't support 'getPrototypeOf' trap but | |
1036 | * continue lookup in Proxy target if the value is a Proxy. | |
1037 | */ | |
1038 | ||
1039 | DUK_INTERNAL duk_bool_t duk_js_instanceof(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) { | |
1040 | duk_context *ctx = (duk_context *) thr; | |
1041 | duk_hobject *func; | |
1042 | duk_hobject *val; | |
1043 | duk_hobject *proto; | |
1044 | duk_uint_t sanity; | |
1045 | ||
1046 | /* | |
1047 | * Get the values onto the stack first. It would be possible to cover | |
1048 | * some normal cases without resorting to the value stack. | |
1049 | * | |
1050 | * The right hand side could be a light function (as they generally | |
1051 | * behave like objects). Light functions never have a 'prototype' | |
1052 | * property so E5.1 Section 15.3.5.3 step 3 always throws a TypeError. | |
1053 | * Using duk_require_hobject() is thus correct (except for error msg). | |
1054 | */ | |
1055 | ||
1056 | duk_push_tval(ctx, tv_x); | |
1057 | duk_push_tval(ctx, tv_y); | |
1058 | func = duk_require_hobject(ctx, -1); | |
1059 | ||
1060 | /* | |
1061 | * For bound objects, [[HasInstance]] just calls the target function | |
1062 | * [[HasInstance]]. If that is again a bound object, repeat until | |
1063 | * we find a non-bound Function object. | |
1064 | */ | |
1065 | ||
1066 | /* XXX: this bound function resolution also happens elsewhere, | |
1067 | * move into a shared helper. | |
1068 | */ | |
1069 | ||
1070 | sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY; | |
1071 | do { | |
1072 | /* check func supports [[HasInstance]] (this is checked for every function | |
1073 | * in the bound chain, including the final one) | |
1074 | */ | |
1075 | ||
1076 | if (!DUK_HOBJECT_IS_CALLABLE(func)) { | |
1077 | /* | |
1078 | * Note: of native Ecmascript objects, only Function instances | |
1079 | * have a [[HasInstance]] internal property. Custom objects might | |
1080 | * also have it, but not in current implementation. | |
1081 | * | |
1082 | * XXX: add a separate flag, DUK_HOBJECT_FLAG_ALLOW_INSTANCEOF? | |
1083 | */ | |
11fdf7f2 | 1084 | DUK_ERROR_TYPE(thr, "invalid instanceof rval"); |
7c673cae FG |
1085 | } |
1086 | ||
1087 | if (!DUK_HOBJECT_HAS_BOUND(func)) { | |
1088 | break; | |
1089 | } | |
1090 | ||
1091 | /* [ ... lval rval ] */ | |
1092 | ||
1093 | duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TARGET); /* -> [ ... lval rval new_rval ] */ | |
1094 | duk_replace(ctx, -1); /* -> [ ... lval new_rval ] */ | |
1095 | func = duk_require_hobject(ctx, -1); | |
1096 | ||
1097 | /* func support for [[HasInstance]] checked in the beginning of the loop */ | |
1098 | } while (--sanity > 0); | |
1099 | ||
1100 | if (sanity == 0) { | |
11fdf7f2 | 1101 | DUK_ERROR_RANGE(thr, DUK_STR_BOUND_CHAIN_LIMIT); |
7c673cae FG |
1102 | } |
1103 | ||
1104 | /* | |
1105 | * 'func' is now a non-bound object which supports [[HasInstance]] | |
1106 | * (which here just means DUK_HOBJECT_FLAG_CALLABLE). Move on | |
1107 | * to execute E5 Section 15.3.5.3. | |
1108 | */ | |
1109 | ||
1110 | DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); | |
1111 | DUK_ASSERT(DUK_HOBJECT_IS_CALLABLE(func)); | |
1112 | ||
1113 | /* [ ... lval rval(func) ] */ | |
1114 | ||
1115 | /* Handle lightfuncs through object coercion for now. */ | |
1116 | /* XXX: direct implementation */ | |
1117 | val = duk_get_hobject_or_lfunc_coerce(ctx, -2); | |
1118 | if (!val) { | |
1119 | goto pop_and_false; | |
1120 | } | |
1121 | ||
1122 | duk_get_prop_stridx(ctx, -1, DUK_STRIDX_PROTOTYPE); /* -> [ ... lval rval rval.prototype ] */ | |
1123 | proto = duk_require_hobject(ctx, -1); | |
1124 | duk_pop(ctx); /* -> [ ... lval rval ] */ | |
1125 | ||
1126 | DUK_ASSERT(val != NULL); | |
1127 | ||
1128 | #if defined(DUK_USE_ES6_PROXY) | |
1129 | val = duk_hobject_resolve_proxy_target(thr, val); | |
1130 | DUK_ASSERT(val != NULL); | |
1131 | #endif | |
1132 | ||
1133 | sanity = DUK_HOBJECT_PROTOTYPE_CHAIN_SANITY; | |
1134 | do { | |
1135 | /* | |
1136 | * Note: prototype chain is followed BEFORE first comparison. This | |
1137 | * means that the instanceof lval is never itself compared to the | |
1138 | * rval.prototype property. This is apparently intentional, see E5 | |
1139 | * Section 15.3.5.3, step 4.a. | |
1140 | * | |
1141 | * Also note: | |
1142 | * | |
1143 | * js> (function() {}) instanceof Function | |
1144 | * true | |
1145 | * js> Function instanceof Function | |
1146 | * true | |
1147 | * | |
1148 | * For the latter, h_proto will be Function.prototype, which is the | |
1149 | * built-in Function prototype. Because Function.[[Prototype]] is | |
1150 | * also the built-in Function prototype, the result is true. | |
1151 | */ | |
1152 | ||
1153 | DUK_ASSERT(val != NULL); | |
1154 | val = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, val); | |
1155 | ||
1156 | if (!val) { | |
1157 | goto pop_and_false; | |
1158 | } | |
1159 | ||
1160 | DUK_ASSERT(val != NULL); | |
1161 | #if defined(DUK_USE_ES6_PROXY) | |
1162 | val = duk_hobject_resolve_proxy_target(thr, val); | |
1163 | #endif | |
1164 | ||
1165 | if (val == proto) { | |
1166 | goto pop_and_true; | |
1167 | } | |
1168 | ||
1169 | /* follow prototype chain */ | |
1170 | } while (--sanity > 0); | |
1171 | ||
1172 | if (sanity == 0) { | |
11fdf7f2 | 1173 | DUK_ERROR_RANGE(thr, DUK_STR_PROTOTYPE_CHAIN_LIMIT); |
7c673cae FG |
1174 | } |
1175 | DUK_UNREACHABLE(); | |
1176 | ||
1177 | pop_and_false: | |
1178 | duk_pop_2(ctx); | |
1179 | return 0; | |
1180 | ||
1181 | pop_and_true: | |
1182 | duk_pop_2(ctx); | |
1183 | return 1; | |
1184 | } | |
1185 | ||
1186 | /* | |
1187 | * in | |
1188 | */ | |
1189 | ||
1190 | /* | |
1191 | * E5 Sections 11.8.7, 8.12.6. | |
1192 | * | |
1193 | * Basically just a property existence check using [[HasProperty]]. | |
1194 | */ | |
1195 | ||
1196 | DUK_INTERNAL duk_bool_t duk_js_in(duk_hthread *thr, duk_tval *tv_x, duk_tval *tv_y) { | |
1197 | duk_context *ctx = (duk_context *) thr; | |
1198 | duk_bool_t retval; | |
1199 | ||
1200 | /* | |
1201 | * Get the values onto the stack first. It would be possible to cover | |
1202 | * some normal cases without resorting to the value stack (e.g. if | |
1203 | * lval is already a string). | |
1204 | */ | |
1205 | ||
1206 | /* XXX: The ES5/5.1/6 specifications require that the key in 'key in obj' | |
1207 | * must be string coerced before the internal HasProperty() algorithm is | |
1208 | * invoked. A fast path skipping coercion could be safely implemented for | |
1209 | * numbers (as number-to-string coercion has no side effects). For ES6 | |
1210 | * proxy behavior, the trap 'key' argument must be in a string coerced | |
1211 | * form (which is a shame). | |
1212 | */ | |
1213 | ||
1214 | /* TypeError if rval is not an object (or lightfunc which should behave | |
1215 | * like a Function instance). | |
1216 | */ | |
1217 | duk_push_tval(ctx, tv_x); | |
1218 | duk_push_tval(ctx, tv_y); | |
1219 | duk_require_type_mask(ctx, -1, DUK_TYPE_MASK_OBJECT | DUK_TYPE_MASK_LIGHTFUNC); | |
1220 | duk_to_string(ctx, -2); /* coerce lval with ToString() */ | |
1221 | ||
11fdf7f2 TL |
1222 | retval = duk_hobject_hasprop(thr, |
1223 | DUK_GET_TVAL_NEGIDX(ctx, -1), | |
1224 | DUK_GET_TVAL_NEGIDX(ctx, -2)); | |
7c673cae FG |
1225 | |
1226 | duk_pop_2(ctx); | |
1227 | return retval; | |
1228 | } | |
1229 | ||
1230 | /* | |
1231 | * typeof | |
1232 | * | |
1233 | * E5 Section 11.4.3. | |
1234 | * | |
1235 | * Very straightforward. The only question is what to return for our | |
1236 | * non-standard tag / object types. | |
1237 | * | |
1238 | * There is an unfortunate string constant define naming problem with | |
1239 | * typeof return values for e.g. "Object" and "object"; careful with | |
1240 | * the built-in string defines. The LC_XXX defines are used for the | |
1241 | * lowercase variants now. | |
1242 | */ | |
1243 | ||
1244 | DUK_INTERNAL duk_hstring *duk_js_typeof(duk_hthread *thr, duk_tval *tv_x) { | |
1245 | duk_small_int_t stridx = 0; | |
1246 | ||
11fdf7f2 TL |
1247 | DUK_UNREF(thr); |
1248 | ||
7c673cae FG |
1249 | switch (DUK_TVAL_GET_TAG(tv_x)) { |
1250 | case DUK_TAG_UNDEFINED: { | |
1251 | stridx = DUK_STRIDX_LC_UNDEFINED; | |
1252 | break; | |
1253 | } | |
1254 | case DUK_TAG_NULL: { | |
1255 | /* Note: not a typo, "object" is returned for a null value */ | |
1256 | stridx = DUK_STRIDX_LC_OBJECT; | |
1257 | break; | |
1258 | } | |
1259 | case DUK_TAG_BOOLEAN: { | |
1260 | stridx = DUK_STRIDX_LC_BOOLEAN; | |
1261 | break; | |
1262 | } | |
1263 | case DUK_TAG_POINTER: { | |
1264 | /* implementation specific */ | |
1265 | stridx = DUK_STRIDX_LC_POINTER; | |
1266 | break; | |
1267 | } | |
1268 | case DUK_TAG_STRING: { | |
1269 | stridx = DUK_STRIDX_LC_STRING; | |
1270 | break; | |
1271 | } | |
1272 | case DUK_TAG_OBJECT: { | |
1273 | duk_hobject *obj = DUK_TVAL_GET_OBJECT(tv_x); | |
1274 | DUK_ASSERT(obj != NULL); | |
1275 | if (DUK_HOBJECT_IS_CALLABLE(obj)) { | |
1276 | stridx = DUK_STRIDX_LC_FUNCTION; | |
1277 | } else { | |
1278 | stridx = DUK_STRIDX_LC_OBJECT; | |
1279 | } | |
1280 | break; | |
1281 | } | |
1282 | case DUK_TAG_BUFFER: { | |
1283 | /* implementation specific */ | |
1284 | stridx = DUK_STRIDX_LC_BUFFER; | |
1285 | break; | |
1286 | } | |
1287 | case DUK_TAG_LIGHTFUNC: { | |
1288 | stridx = DUK_STRIDX_LC_FUNCTION; | |
1289 | break; | |
1290 | } | |
1291 | #if defined(DUK_USE_FASTINT) | |
1292 | case DUK_TAG_FASTINT: | |
1293 | #endif | |
1294 | default: { | |
1295 | /* number */ | |
11fdf7f2 | 1296 | DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_x)); |
7c673cae FG |
1297 | DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_x)); |
1298 | stridx = DUK_STRIDX_LC_NUMBER; | |
1299 | break; | |
1300 | } | |
1301 | } | |
1302 | ||
1303 | DUK_ASSERT(stridx >= 0 && stridx < DUK_HEAP_NUM_STRINGS); | |
1304 | return DUK_HTHREAD_GET_STRING(thr, stridx); | |
1305 | } | |
1306 | ||
1307 | /* | |
1308 | * Array index and length | |
1309 | * | |
1310 | * Array index: E5 Section 15.4 | |
1311 | * Array length: E5 Section 15.4.5.1 steps 3.c - 3.d (array length write) | |
1312 | * | |
1313 | * The DUK_HSTRING_GET_ARRIDX_SLOW() and DUK_HSTRING_GET_ARRIDX_FAST() macros | |
1314 | * call duk_js_to_arrayindex_string_helper(). | |
1315 | */ | |
1316 | ||
1317 | DUK_INTERNAL duk_small_int_t duk_js_to_arrayindex_raw_string(const duk_uint8_t *str, duk_uint32_t blen, duk_uarridx_t *out_idx) { | |
11fdf7f2 | 1318 | duk_uarridx_t res; |
7c673cae FG |
1319 | |
1320 | if (blen == 0 || blen > 10) { | |
1321 | goto parse_fail; | |
1322 | } | |
11fdf7f2 | 1323 | if (str[0] == DUK_ASC_0 && blen > 1) { |
7c673cae FG |
1324 | goto parse_fail; |
1325 | } | |
1326 | ||
1327 | /* Accept 32-bit decimal integers, no leading zeroes, signs, etc. | |
1328 | * Leading zeroes are not accepted (zero index "0" is an exception | |
1329 | * handled above). | |
1330 | */ | |
1331 | ||
1332 | res = 0; | |
1333 | while (blen-- > 0) { | |
1334 | duk_uint8_t c = *str++; | |
11fdf7f2 TL |
1335 | if (c >= DUK_ASC_0 && c <= DUK_ASC_9) { |
1336 | /* Careful overflow handling. When multiplying by 10: | |
1337 | * - 0x19999998 x 10 = 0xfffffff0: no overflow, and adding | |
1338 | * 0...9 is safe. | |
1339 | * - 0x19999999 x 10 = 0xfffffffa: no overflow, adding | |
1340 | * 0...5 is safe, 6...9 overflows. | |
1341 | * - 0x1999999a x 10 = 0x100000004: always overflow. | |
1342 | */ | |
1343 | if (DUK_UNLIKELY(res >= 0x19999999UL)) { | |
1344 | if (res >= 0x1999999aUL) { | |
1345 | /* Always overflow. */ | |
1346 | goto parse_fail; | |
1347 | } | |
1348 | DUK_ASSERT(res == 0x19999999UL); | |
1349 | c -= DUK_ASC_0; | |
1350 | if (c >= 6) { | |
1351 | goto parse_fail; | |
1352 | } | |
1353 | res = 0xfffffffaUL + c; | |
1354 | DUK_ASSERT(res >= 0xfffffffaUL); | |
1355 | DUK_ASSERT_DISABLE(res <= 0xffffffffUL); /* range */ | |
1356 | } else { | |
1357 | res = res * 10U + (duk_uint32_t) (c - DUK_ASC_0); | |
7c673cae | 1358 | } |
7c673cae FG |
1359 | } else { |
1360 | goto parse_fail; | |
1361 | } | |
1362 | } | |
1363 | ||
1364 | *out_idx = res; | |
1365 | return 1; | |
1366 | ||
1367 | parse_fail: | |
1368 | *out_idx = DUK_HSTRING_NO_ARRAY_INDEX; | |
1369 | return 0; | |
1370 | } | |
1371 | ||
1372 | /* Called by duk_hstring.h macros */ | |
1373 | DUK_INTERNAL duk_uarridx_t duk_js_to_arrayindex_string_helper(duk_hstring *h) { | |
1374 | duk_uarridx_t res; | |
1375 | duk_small_int_t rc; | |
1376 | ||
1377 | if (!DUK_HSTRING_HAS_ARRIDX(h)) { | |
1378 | return DUK_HSTRING_NO_ARRAY_INDEX; | |
1379 | } | |
1380 | ||
1381 | rc = duk_js_to_arrayindex_raw_string(DUK_HSTRING_GET_DATA(h), | |
1382 | DUK_HSTRING_GET_BYTELEN(h), | |
1383 | &res); | |
1384 | DUK_UNREF(rc); | |
1385 | DUK_ASSERT(rc != 0); | |
1386 | return res; | |
1387 | } |