/src/hermes/lib/VM/Operations.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) Meta Platforms, Inc. and affiliates. |
3 | | * |
4 | | * This source code is licensed under the MIT license found in the |
5 | | * LICENSE file in the root directory of this source tree. |
6 | | */ |
7 | | |
8 | | #include "hermes/VM/Operations.h" |
9 | | |
10 | | #include "hermes/Support/Conversions.h" |
11 | | #include "hermes/Support/OSCompat.h" |
12 | | #include "hermes/VM/BigIntPrimitive.h" |
13 | | #include "hermes/VM/Callable.h" |
14 | | #include "hermes/VM/Casting.h" |
15 | | #include "hermes/VM/JSArray.h" |
16 | | #include "hermes/VM/JSCallableProxy.h" |
17 | | #include "hermes/VM/JSError.h" |
18 | | #include "hermes/VM/JSObject.h" |
19 | | #include "hermes/VM/JSRegExp.h" |
20 | | #include "hermes/VM/PrimitiveBox.h" |
21 | | #include "hermes/VM/PropertyAccessor.h" |
22 | | #include "hermes/VM/Runtime.h" |
23 | | #include "hermes/VM/StringBuilder.h" |
24 | | #include "hermes/VM/StringPrimitive.h" |
25 | | #include "hermes/VM/StringView.h" |
26 | | |
27 | | #include "dtoa/dtoa.h" |
28 | | |
29 | | #include "llvh/ADT/SmallString.h" |
30 | | |
31 | | #include <cfloat> |
32 | | #include <cmath> |
33 | | |
34 | | namespace hermes { |
35 | | namespace vm { |
36 | | |
37 | | CallResult<Handle<SymbolID>> stringToSymbolID( |
38 | | Runtime &runtime, |
39 | 183k | PseudoHandle<StringPrimitive> strPrim) { |
40 | | // Unique the string. |
41 | 183k | return runtime.getIdentifierTable().getSymbolHandleFromPrimitive( |
42 | 183k | runtime, std::move(strPrim)); |
43 | 183k | } |
44 | | |
45 | | CallResult<Handle<SymbolID>> valueToSymbolID( |
46 | | Runtime &runtime, |
47 | 184k | Handle<> nameValHnd) { |
48 | 184k | if (nameValHnd->isSymbol()) { |
49 | 495 | return Handle<SymbolID>::vmcast(nameValHnd); |
50 | 495 | } |
51 | | // Convert the value to a string. |
52 | 183k | auto res = toString_RJS(runtime, nameValHnd); |
53 | 183k | if (res == ExecutionStatus::EXCEPTION) |
54 | 0 | return ExecutionStatus::EXCEPTION; |
55 | | |
56 | | // Unique the string. |
57 | 183k | return stringToSymbolID(runtime, std::move(*res)); |
58 | 183k | } |
59 | | |
60 | 1.26k | HermesValue typeOf(Runtime &runtime, Handle<> valueHandle) { |
61 | 1.26k | switch (valueHandle->getETag()) { |
62 | 0 | case HermesValue::ETag::Undefined: |
63 | 0 | return HermesValue::encodeStringValue( |
64 | 0 | runtime.getPredefinedString(Predefined::undefined)); |
65 | 0 | case HermesValue::ETag::Null: |
66 | 0 | return HermesValue::encodeStringValue( |
67 | 0 | runtime.getPredefinedString(Predefined::object)); |
68 | 0 | case HermesValue::ETag::Str1: |
69 | 0 | case HermesValue::ETag::Str2: |
70 | 0 | return HermesValue::encodeStringValue( |
71 | 0 | runtime.getPredefinedString(Predefined::string)); |
72 | 0 | case HermesValue::ETag::BigInt1: |
73 | 0 | case HermesValue::ETag::BigInt2: |
74 | 0 | return HermesValue::encodeStringValue( |
75 | 0 | runtime.getPredefinedString(Predefined::bigint)); |
76 | 0 | case HermesValue::ETag::Bool: |
77 | 0 | return HermesValue::encodeStringValue( |
78 | 0 | runtime.getPredefinedString(Predefined::boolean)); |
79 | 0 | case HermesValue::ETag::Symbol: |
80 | 0 | return HermesValue::encodeStringValue( |
81 | 0 | runtime.getPredefinedString(Predefined::symbol)); |
82 | 1.26k | case HermesValue::ETag::Object1: |
83 | 1.26k | case HermesValue::ETag::Object2: |
84 | 1.26k | if (vmisa<Callable>(*valueHandle)) |
85 | 630 | return HermesValue::encodeStringValue( |
86 | 630 | runtime.getPredefinedString(Predefined::function)); |
87 | 630 | return HermesValue::encodeStringValue( |
88 | 630 | runtime.getPredefinedString(Predefined::object)); |
89 | 0 | default: |
90 | 0 | assert(valueHandle->isNumber() && "Invalid type."); |
91 | 0 | return HermesValue::encodeStringValue( |
92 | 0 | runtime.getPredefinedString(Predefined::number)); |
93 | 1.26k | } |
94 | 1.26k | } |
95 | | |
96 | | OptValue<uint32_t> toArrayIndex( |
97 | | Runtime &runtime, |
98 | 0 | Handle<StringPrimitive> strPrim) { |
99 | 0 | auto view = StringPrimitive::createStringView(runtime, strPrim); |
100 | 0 | return toArrayIndex(view); |
101 | 0 | } |
102 | | |
103 | 231k | OptValue<uint32_t> toArrayIndex(StringView str) { |
104 | 231k | auto len = str.length(); |
105 | 231k | if (str.isASCII()) { |
106 | 231k | const char *ptr = str.castToCharPtr(); |
107 | 231k | return hermes::toArrayIndex(ptr, ptr + len); |
108 | 231k | } |
109 | 0 | const char16_t *ptr = str.castToChar16Ptr(); |
110 | 0 | return hermes::toArrayIndex(ptr, ptr + len); |
111 | 231k | } |
112 | | |
113 | 535 | bool isSameValue(HermesValue x, HermesValue y) { |
114 | 535 | if (x.getTag() != y.getTag()) { |
115 | | // If the tags are different, they must be different. |
116 | 0 | return false; |
117 | 0 | } |
118 | 535 | assert( |
119 | 535 | !x.isEmpty() && !x.isNativeValue() && |
120 | 535 | "Empty and Native Value cannot be compared"); |
121 | | |
122 | | // Strings require deep comparison. |
123 | 535 | if (x.isString()) { |
124 | | // For strings, we compare each character in sequence. |
125 | 10 | return x.getString()->equals(y.getString()); |
126 | 10 | } |
127 | | |
128 | | // Bigints also require deep comparison. |
129 | 525 | if (x.isBigInt()) { |
130 | | // For bigints, perform the numerical comparison. |
131 | 0 | return x.getBigInt()->compare(y.getBigInt()) == 0; |
132 | 0 | } |
133 | | |
134 | | // Otherwise they are identical if the raw bits are the same. |
135 | 525 | return x.getRaw() == y.getRaw(); |
136 | 525 | } |
137 | | |
138 | 0 | bool isSameValueZero(HermesValue x, HermesValue y) { |
139 | 0 | if (x.isNumber() && y.isNumber() && x.getNumber() == y.getNumber()) { |
140 | | // Takes care of +0 == -0. |
141 | 0 | return true; |
142 | 0 | } |
143 | 0 | return isSameValue(x, y); |
144 | 0 | } |
145 | | |
146 | 651 | bool isPrimitive(HermesValue val) { |
147 | 651 | assert(!val.isEmpty() && "empty value encountered"); |
148 | 0 | assert(!val.isNativeValue() && "native value encountered"); |
149 | 0 | return !val.isObject(); |
150 | 651 | } |
151 | | |
152 | | CallResult<HermesValue> ordinaryToPrimitive( |
153 | | Handle<JSObject> selfHandle, |
154 | | Runtime &runtime, |
155 | 412 | PreferredType preferredType) { |
156 | 412 | GCScope gcScope{runtime}; |
157 | 412 | assert( |
158 | 412 | preferredType != PreferredType::NONE && |
159 | 412 | "OrdinaryToPrimitive requires a type hint"); |
160 | | |
161 | 651 | for (int i = 0; i < 2; ++i) { |
162 | 651 | if (preferredType == PreferredType::STRING) { |
163 | 412 | auto propRes = JSObject::getNamed_RJS( |
164 | 412 | selfHandle, runtime, Predefined::getSymbolID(Predefined::toString)); |
165 | 412 | if (propRes == ExecutionStatus::EXCEPTION) |
166 | 0 | return ExecutionStatus::EXCEPTION; |
167 | 412 | if (auto funcHandle = Handle<Callable>::dyn_vmcast( |
168 | 412 | runtime.makeHandle(std::move(*propRes)))) { |
169 | 412 | auto callRes = |
170 | 412 | funcHandle->executeCall0(funcHandle, runtime, selfHandle); |
171 | 412 | if (callRes == ExecutionStatus::EXCEPTION) |
172 | 0 | return ExecutionStatus::EXCEPTION; |
173 | 412 | if (isPrimitive(callRes->get())) |
174 | 412 | return callRes.toCallResultHermesValue(); |
175 | 412 | } |
176 | | |
177 | | // This method failed. Try the other one. |
178 | 0 | preferredType = PreferredType::NUMBER; |
179 | 239 | } else { |
180 | 239 | auto propRes = JSObject::getNamed_RJS( |
181 | 239 | selfHandle, runtime, Predefined::getSymbolID(Predefined::valueOf)); |
182 | 239 | if (propRes == ExecutionStatus::EXCEPTION) |
183 | 0 | return ExecutionStatus::EXCEPTION; |
184 | 239 | if (auto funcHandle = Handle<Callable>::dyn_vmcast( |
185 | 239 | runtime.makeHandle(std::move(*propRes)))) { |
186 | 239 | auto callRes = |
187 | 239 | funcHandle->executeCall0(funcHandle, runtime, selfHandle); |
188 | 239 | if (callRes == ExecutionStatus::EXCEPTION) |
189 | 0 | return ExecutionStatus::EXCEPTION; |
190 | 239 | if (isPrimitive(callRes->get())) |
191 | 0 | return callRes.toCallResultHermesValue(); |
192 | 239 | } |
193 | | |
194 | | // This method failed. Try the other one. |
195 | 239 | preferredType = PreferredType::STRING; |
196 | 239 | } |
197 | 651 | } |
198 | | |
199 | | // Nothing succeeded, time to give up. |
200 | 0 | return runtime.raiseTypeError("Cannot determine default value of object"); |
201 | 412 | } |
202 | | |
203 | | /// ES5.1 9.1 |
204 | | CallResult<HermesValue> |
205 | 62.6k | toPrimitive_RJS(Runtime &runtime, Handle<> valueHandle, PreferredType hint) { |
206 | 62.6k | assert(!valueHandle->isEmpty() && "empty value is not allowed"); |
207 | 0 | assert(!valueHandle->isNativeValue() && "native value is not allowed"); |
208 | | |
209 | 62.6k | if (!valueHandle->isObject()) |
210 | 62.1k | return *valueHandle; |
211 | | |
212 | | // 4. Let exoticToPrim be GetMethod(input, @@toPrimitive). |
213 | 412 | auto exoticToPrim = getMethod( |
214 | 412 | runtime, |
215 | 412 | valueHandle, |
216 | 412 | runtime.makeHandle( |
217 | 412 | Predefined::getSymbolID(Predefined::SymbolToPrimitive))); |
218 | 412 | if (LLVM_UNLIKELY(exoticToPrim == ExecutionStatus::EXCEPTION)) { |
219 | 0 | return ExecutionStatus::EXCEPTION; |
220 | 0 | } |
221 | | // 6. If exoticToPrim is not undefined, then |
222 | 412 | if (vmisa<Callable>(exoticToPrim->getHermesValue())) { |
223 | 0 | auto callable = runtime.makeHandle<Callable>( |
224 | 0 | dyn_vmcast<Callable>(exoticToPrim->getHermesValue())); |
225 | 0 | CallResult<PseudoHandle<>> resultRes = Callable::executeCall1( |
226 | 0 | callable, |
227 | 0 | runtime, |
228 | 0 | valueHandle, |
229 | 0 | HermesValue::encodeStringValue(runtime.getPredefinedString( |
230 | 0 | hint == PreferredType::NONE ? Predefined::defaultStr |
231 | 0 | : hint == PreferredType::STRING ? Predefined::string |
232 | 0 | : Predefined::number))); |
233 | 0 | if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { |
234 | 0 | return ExecutionStatus::EXCEPTION; |
235 | 0 | } |
236 | 0 | PseudoHandle<> result = std::move(*resultRes); |
237 | 0 | if (!result->isObject()) { |
238 | 0 | return result.getHermesValue(); |
239 | 0 | } |
240 | 0 | return runtime.raiseTypeError( |
241 | 0 | "Symbol.toPrimitive function must return a primitive"); |
242 | 0 | } |
243 | | |
244 | | // 7. If hint is "default", let hint be "number". |
245 | | // 8. Return OrdinaryToPrimitive(input,hint). |
246 | 412 | return ordinaryToPrimitive( |
247 | 412 | Handle<JSObject>::vmcast(valueHandle), |
248 | 412 | runtime, |
249 | 412 | hint == PreferredType::NONE ? PreferredType::NUMBER : hint); |
250 | 412 | } |
251 | | |
252 | 1.16k | bool toBoolean(HermesValue value) { |
253 | 1.16k | switch (value.getETag()) { |
254 | 0 | #ifdef HERMES_SLOW_DEBUG |
255 | 0 | case HermesValue::ETag::Invalid: |
256 | 0 | llvm_unreachable("invalid value"); |
257 | 0 | #endif // HERMES_SLOW_DEBUG |
258 | 0 | case HermesValue::ETag::Empty: |
259 | 0 | llvm_unreachable("empty value"); |
260 | 0 | case HermesValue::ETag::Native1: |
261 | 0 | case HermesValue::ETag::Native2: |
262 | 0 | llvm_unreachable("native value"); |
263 | 0 | case HermesValue::ETag::Undefined: |
264 | 0 | case HermesValue::ETag::Null: |
265 | 0 | return false; |
266 | 1.16k | case HermesValue::ETag::Bool: |
267 | 1.16k | return value.getBool(); |
268 | 0 | case HermesValue::ETag::Symbol: |
269 | 0 | case HermesValue::ETag::Object1: |
270 | 0 | case HermesValue::ETag::Object2: |
271 | 0 | return true; |
272 | 0 | case HermesValue::ETag::BigInt1: |
273 | 0 | case HermesValue::ETag::BigInt2: |
274 | 0 | return value.getBigInt()->compare(0) != 0; |
275 | 0 | case HermesValue::ETag::Str1: |
276 | 0 | case HermesValue::ETag::Str2: |
277 | 0 | return value.getString()->getStringLength() != 0; |
278 | 0 | default: { |
279 | 0 | auto m = value.getNumber(); |
280 | 0 | return !(m == 0 || std::isnan(m)); |
281 | 0 | } |
282 | 1.16k | } |
283 | 1.16k | } |
284 | | |
285 | | /// ES5.1 9.8.1 |
286 | | static CallResult<PseudoHandle<StringPrimitive>> numberToString( |
287 | | Runtime &runtime, |
288 | | double m) LLVM_NO_SANITIZE("float-cast-overflow"); |
289 | | |
290 | | static CallResult<PseudoHandle<StringPrimitive>> numberToString( |
291 | | Runtime &runtime, |
292 | 228k | double m) { |
293 | 228k | char buf8[hermes::NUMBER_TO_STRING_BUF_SIZE]; |
294 | | |
295 | | // Optimization: Fast-case for positive integers < 2^31 |
296 | 228k | int32_t n = static_cast<int32_t>(m); |
297 | 228k | if (m == static_cast<double>(n) && n > 0) { |
298 | | // Write base 10 digits in reverse from end of buf8. |
299 | 194k | char *p = buf8 + sizeof(buf8); |
300 | 738k | do { |
301 | 738k | *--p = '0' + (n % 10); |
302 | 738k | n /= 10; |
303 | 738k | } while (n); |
304 | 194k | size_t len = buf8 + sizeof(buf8) - p; |
305 | | // Temporarily stop the propagation of removing. |
306 | 194k | auto result = StringPrimitive::create(runtime, ASCIIRef(p, len)); |
307 | 194k | if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { |
308 | 0 | return ExecutionStatus::EXCEPTION; |
309 | 0 | } |
310 | 194k | return createPseudoHandle(vmcast<StringPrimitive>(*result)); |
311 | 194k | } |
312 | | |
313 | 34.0k | auto getPredefined = [&runtime](Predefined::Str predefinedID) { |
314 | 22.0k | return createPseudoHandle(runtime.getPredefinedString(predefinedID)); |
315 | 22.0k | }; |
316 | | |
317 | 34.0k | if (std::isnan(m)) |
318 | 4 | return getPredefined(Predefined::NaN); |
319 | 34.0k | if (m == 0) |
320 | 22.0k | return getPredefined(Predefined::zero); |
321 | 11.9k | if (m == std::numeric_limits<double>::infinity()) |
322 | 0 | return getPredefined(Predefined::Infinity); |
323 | 11.9k | if (m == -std::numeric_limits<double>::infinity()) |
324 | 0 | return getPredefined(Predefined::NegativeInfinity); |
325 | | |
326 | | // After special cases, run the generic routine to convert. |
327 | 11.9k | size_t len = hermes::numberToString(m, buf8, sizeof(buf8)); |
328 | | |
329 | 11.9k | auto result = StringPrimitive::create(runtime, ASCIIRef(buf8, len)); |
330 | 11.9k | if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { |
331 | 0 | return ExecutionStatus::EXCEPTION; |
332 | 0 | } |
333 | 11.9k | return createPseudoHandle(vmcast<StringPrimitive>(*result)); |
334 | 11.9k | } |
335 | | |
336 | | CallResult<PseudoHandle<StringPrimitive>> toString_RJS( |
337 | | Runtime &runtime, |
338 | 391k | Handle<> valueHandle) { |
339 | 391k | HermesValue value = valueHandle.get(); |
340 | 391k | StringPrimitive *result; |
341 | 391k | switch (value.getETag()) { |
342 | 0 | #ifdef HERMES_SLOW_DEBUG |
343 | 0 | case HermesValue::ETag::Invalid: |
344 | 0 | llvm_unreachable("invalid value"); |
345 | 0 | #endif // HERMES_SLOW_DEBUG |
346 | 0 | case HermesValue::ETag::Empty: |
347 | 0 | llvm_unreachable("empty value"); |
348 | 0 | case HermesValue::ETag::Native1: |
349 | 0 | case HermesValue::ETag::Native2: |
350 | 0 | llvm_unreachable("native value"); |
351 | 0 | case HermesValue::ETag::BigInt1: |
352 | 0 | case HermesValue::ETag::BigInt2: { |
353 | 0 | const uint8_t kDefaultRadix = 10; |
354 | 0 | auto res = BigIntPrimitive::toString( |
355 | 0 | runtime, Handle<BigIntPrimitive>::vmcast(valueHandle), kDefaultRadix); |
356 | 0 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
357 | 0 | return ExecutionStatus::EXCEPTION; |
358 | 0 | } |
359 | 0 | result = res->getString(); |
360 | 0 | break; |
361 | 0 | } |
362 | 162k | case HermesValue::ETag::Str1: |
363 | 162k | case HermesValue::ETag::Str2: |
364 | 162k | result = vmcast<StringPrimitive>(value); |
365 | 162k | break; |
366 | 202 | case HermesValue::ETag::Undefined: |
367 | 202 | result = runtime.getPredefinedString(Predefined::undefined); |
368 | 202 | break; |
369 | 0 | case HermesValue::ETag::Null: |
370 | 0 | result = runtime.getPredefinedString(Predefined::null); |
371 | 0 | break; |
372 | 0 | case HermesValue::ETag::Bool: |
373 | 0 | result = value.getBool() |
374 | 0 | ? runtime.getPredefinedString(Predefined::trueStr) |
375 | 0 | : runtime.getPredefinedString(Predefined::falseStr); |
376 | 0 | break; |
377 | 173 | case HermesValue::ETag::Object1: |
378 | 173 | case HermesValue::ETag::Object2: { |
379 | 173 | auto res = toPrimitive_RJS(runtime, valueHandle, PreferredType::STRING); |
380 | 173 | if (res == ExecutionStatus::EXCEPTION) { |
381 | 0 | return ExecutionStatus::EXCEPTION; |
382 | 0 | } |
383 | 173 | return toString_RJS(runtime, runtime.makeHandle(res.getValue())); |
384 | 173 | } |
385 | 0 | case HermesValue::ETag::Symbol: |
386 | 0 | return runtime.raiseTypeError("Cannot convert Symbol to string"); |
387 | 228k | default: |
388 | 228k | return numberToString(runtime, value.getNumber()); |
389 | 391k | } |
390 | | |
391 | 162k | return createPseudoHandle(result); |
392 | 391k | } |
393 | | |
394 | 0 | double parseIntWithRadix(const StringView str, int radix) { |
395 | 0 | auto res = |
396 | 0 | hermes::parseIntWithRadix</* AllowNumericSeparator */ false>(str, radix); |
397 | 0 | return res ? res.getValue() : std::numeric_limits<double>::quiet_NaN(); |
398 | 0 | } |
399 | | |
400 | | /// ES5.1 9.3.1 |
401 | | static inline double stringToNumber( |
402 | | Runtime &runtime, |
403 | 88 | Handle<StringPrimitive> strPrim) { |
404 | 88 | auto &idTable = runtime.getIdentifierTable(); |
405 | | |
406 | | // Fast check for special values (no extraneous whitespace). |
407 | 88 | if (runtime.symbolEqualsToStringPrim( |
408 | 88 | Predefined::getSymbolID(Predefined::Infinity), *strPrim)) { |
409 | 0 | return std::numeric_limits<double>::infinity(); |
410 | 0 | } |
411 | 88 | if (runtime.symbolEqualsToStringPrim( |
412 | 88 | Predefined::getSymbolID(Predefined::PositiveInfinity), *strPrim)) { |
413 | 0 | return std::numeric_limits<double>::infinity(); |
414 | 0 | } |
415 | 88 | if (runtime.symbolEqualsToStringPrim( |
416 | 88 | Predefined::getSymbolID(Predefined::NegativeInfinity), *strPrim)) { |
417 | 0 | } |
418 | 88 | if (runtime.symbolEqualsToStringPrim( |
419 | 88 | Predefined::getSymbolID(Predefined::NaN), *strPrim)) { |
420 | 0 | return std::numeric_limits<double>::quiet_NaN(); |
421 | 0 | } |
422 | | |
423 | | // Trim string to the interval [begin, end). |
424 | 88 | auto orig = StringPrimitive::createStringView(runtime, strPrim); |
425 | 88 | auto begin = orig.begin(); |
426 | 88 | auto end = orig.end(); |
427 | | |
428 | | // Move begin and end to ignore whitespace. |
429 | 88 | while (begin != end && |
430 | 88 | (isWhiteSpaceChar(*begin) || isLineTerminatorChar(*begin))) { |
431 | 0 | ++begin; |
432 | 0 | } |
433 | 88 | while (begin != end && |
434 | 88 | (isWhiteSpaceChar(*(end - 1)) || isLineTerminatorChar(*(end - 1)))) { |
435 | 0 | --end; |
436 | 0 | } |
437 | | // Early return for empty strings (strings only containing whitespace). |
438 | 88 | if (begin == end) { |
439 | 0 | return 0; |
440 | 0 | } |
441 | | |
442 | | // Trim the string. |
443 | 88 | StringView str16 = orig.slice(begin, end); |
444 | | |
445 | | // Slow check for special values. |
446 | | // This should only run if user created a string with extra whitespace, |
447 | | // since normal uses would get caught by the initial check. |
448 | 88 | if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( |
449 | 88 | runtime, Predefined::getSymbolID(Predefined::Infinity))))) { |
450 | 0 | return std::numeric_limits<double>::infinity(); |
451 | 0 | } |
452 | 88 | if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( |
453 | 88 | runtime, Predefined::getSymbolID(Predefined::PositiveInfinity))))) { |
454 | 0 | return std::numeric_limits<double>::infinity(); |
455 | 0 | } |
456 | 88 | if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( |
457 | 88 | runtime, Predefined::getSymbolID(Predefined::NegativeInfinity))))) { |
458 | 0 | return -std::numeric_limits<double>::infinity(); |
459 | 0 | } |
460 | 88 | if (LLVM_UNLIKELY(str16.equals(idTable.getStringView( |
461 | 88 | runtime, Predefined::getSymbolID(Predefined::NaN))))) { |
462 | 0 | return std::numeric_limits<double>::quiet_NaN(); |
463 | 0 | } |
464 | | |
465 | 88 | auto len = str16.length(); |
466 | | |
467 | | // Parse hex codes, since dtoa doesn't do it. |
468 | | // FIXME: May be inaccurate for some hex values. |
469 | | // We need to check other sources first. |
470 | 88 | if (len > 2) { |
471 | 83 | if (str16[0] == u'0' && letterToLower(str16[1]) == u'x') { |
472 | 0 | return parseIntWithRadix(str16.slice(2), 16); |
473 | 0 | } |
474 | 83 | if (str16[0] == u'0' && letterToLower(str16[1]) == u'o') { |
475 | 0 | return parseIntWithRadix(str16.slice(2), 8); |
476 | 0 | } |
477 | 83 | if (str16[0] == u'0' && letterToLower(str16[1]) == u'b') { |
478 | 0 | return parseIntWithRadix(str16.slice(2), 2); |
479 | 0 | } |
480 | 83 | } |
481 | | |
482 | | // Finally, copy 16 bit chars into 8 bit chars and call dtoa. |
483 | 88 | llvh::SmallVector<char, 32> str8(len + 1); |
484 | 88 | uint32_t i = 0; |
485 | 412 | for (auto c16 : str16) { |
486 | | // Check to ensure we only have valid number characters now. |
487 | 412 | if ((u'0' <= c16 && c16 <= u'9') || c16 == u'.' || |
488 | 412 | letterToLower(c16) == u'e' || c16 == u'+' || c16 == u'-') { |
489 | 406 | str8[i] = static_cast<char>(c16); |
490 | 406 | } else { |
491 | 6 | return std::numeric_limits<double>::quiet_NaN(); |
492 | 6 | } |
493 | 406 | ++i; |
494 | 406 | } |
495 | 82 | str8[len] = '\0'; |
496 | 82 | char *endPtr; |
497 | 82 | double result = ::hermes_g_strtod(str8.data(), &endPtr); |
498 | 82 | if (endPtr == str8.data() + len) { |
499 | 82 | return result; |
500 | 82 | } |
501 | | |
502 | | // If everything failed, return NaN. |
503 | 0 | return std::numeric_limits<double>::quiet_NaN(); |
504 | 82 | } |
505 | | |
506 | 688 | CallResult<HermesValue> toNumber_RJS(Runtime &runtime, Handle<> valueHandle) { |
507 | 688 | auto value = valueHandle.get(); |
508 | 688 | double result; |
509 | 688 | switch (value.getETag()) { |
510 | 0 | #ifdef HERMES_SLOW_DEBUG |
511 | 0 | case HermesValue::ETag::Invalid: |
512 | 0 | llvm_unreachable("invalid value"); |
513 | 0 | #endif // HERMES_SLOW_DEBUG |
514 | 0 | case HermesValue::ETag::Empty: |
515 | 0 | llvm_unreachable("empty value"); |
516 | 0 | case HermesValue::ETag::Native1: |
517 | 0 | case HermesValue::ETag::Native2: |
518 | 0 | llvm_unreachable("native value"); |
519 | 81 | case HermesValue::ETag::Object1: |
520 | 81 | case HermesValue::ETag::Object2: { |
521 | 81 | auto res = toPrimitive_RJS(runtime, valueHandle, PreferredType::NUMBER); |
522 | 81 | if (res == ExecutionStatus::EXCEPTION) { |
523 | 0 | return ExecutionStatus::EXCEPTION; |
524 | 0 | } |
525 | 81 | return toNumber_RJS(runtime, runtime.makeHandle(res.getValue())); |
526 | 81 | } |
527 | 88 | case HermesValue::ETag::Str1: |
528 | 88 | case HermesValue::ETag::Str2: |
529 | 88 | result = |
530 | 88 | stringToNumber(runtime, Handle<StringPrimitive>::vmcast(valueHandle)); |
531 | 88 | break; |
532 | 0 | case HermesValue::ETag::BigInt1: |
533 | 0 | case HermesValue::ETag::BigInt2: |
534 | 0 | return runtime.raiseTypeError("Cannot convert BigInt to number"); |
535 | 0 | case HermesValue::ETag::Undefined: |
536 | 0 | result = std::numeric_limits<double>::quiet_NaN(); |
537 | 0 | break; |
538 | 0 | case HermesValue::ETag::Null: |
539 | 0 | result = +0.0; |
540 | 0 | break; |
541 | 0 | case HermesValue::ETag::Bool: |
542 | 0 | result = value.getBool(); |
543 | 0 | break; |
544 | 0 | case HermesValue::ETag::Symbol: |
545 | 0 | return runtime.raiseTypeError("Cannot convert Symbol to number"); |
546 | 519 | default: |
547 | | // Already have a number, just return it. |
548 | 519 | return value; |
549 | 688 | } |
550 | 88 | return HermesValue::encodeUntrustedNumberValue(result); |
551 | 688 | } |
552 | | |
553 | 3 | CallResult<HermesValue> toNumeric_RJS(Runtime &runtime, Handle<> valueHandle) { |
554 | 3 | GCScopeMarkerRAII marker{runtime}; |
555 | 3 | auto primValue = toPrimitive_RJS(runtime, valueHandle, PreferredType::NUMBER); |
556 | | |
557 | 3 | if (LLVM_UNLIKELY(primValue == ExecutionStatus::EXCEPTION)) { |
558 | 0 | return ExecutionStatus::EXCEPTION; |
559 | 0 | } |
560 | | |
561 | 3 | if (primValue->isBigInt()) { |
562 | 0 | return *primValue; |
563 | 0 | } |
564 | | |
565 | 3 | return toNumber_RJS(runtime, runtime.makeHandle(*primValue)); |
566 | 3 | } |
567 | | |
568 | 0 | CallResult<HermesValue> toLength(Runtime &runtime, Handle<> valueHandle) { |
569 | 0 | constexpr double maxLength = 9007199254740991.0; // 2**53 - 1 |
570 | 0 | auto res = toIntegerOrInfinity(runtime, valueHandle); |
571 | 0 | if (res == ExecutionStatus::EXCEPTION) { |
572 | 0 | return ExecutionStatus::EXCEPTION; |
573 | 0 | } |
574 | 0 | auto len = res->getNumber(); |
575 | 0 | if (len <= 0) { |
576 | 0 | len = 0; |
577 | 0 | } else if (len > maxLength) { |
578 | 0 | len = maxLength; |
579 | 0 | } |
580 | 0 | return HermesValue::encodeUntrustedNumberValue(len); |
581 | 0 | } |
582 | | |
583 | 335 | CallResult<uint64_t> toLengthU64(Runtime &runtime, Handle<> valueHandle) { |
584 | 335 | constexpr double highestIntegralDouble = |
585 | 335 | ((uint64_t)1 << std::numeric_limits<double>::digits) - 1; |
586 | 335 | auto res = toIntegerOrInfinity(runtime, valueHandle); |
587 | 335 | if (res == ExecutionStatus::EXCEPTION) { |
588 | 0 | return ExecutionStatus::EXCEPTION; |
589 | 0 | } |
590 | 335 | auto len = res->getNumber(); |
591 | 335 | if (len <= 0) { |
592 | 79 | len = 0; |
593 | 256 | } else if (len > highestIntegralDouble) { |
594 | 0 | len = highestIntegralDouble; |
595 | 0 | } |
596 | 335 | return len; |
597 | 335 | } |
598 | | |
599 | 0 | CallResult<HermesValue> toIndex(Runtime &runtime, Handle<> valueHandle) { |
600 | 0 | auto value = (valueHandle->isUndefined()) |
601 | 0 | ? runtime.makeHandle(HermesValue::encodeUntrustedNumberValue(0)) |
602 | 0 | : valueHandle; |
603 | 0 | auto res = toIntegerOrInfinity(runtime, value); |
604 | 0 | if (res == ExecutionStatus::EXCEPTION) { |
605 | 0 | return ExecutionStatus::EXCEPTION; |
606 | 0 | } |
607 | 0 | auto integerIndex = res->getNumber(); |
608 | 0 | if (integerIndex < 0) { |
609 | 0 | return runtime.raiseRangeError("A negative value cannot be an index"); |
610 | 0 | } |
611 | 0 | auto integerIndexHandle = |
612 | 0 | runtime.makeHandle(HermesValue::encodeUntrustedNumberValue(integerIndex)); |
613 | 0 | res = toLength(runtime, integerIndexHandle); |
614 | 0 | if (res == ExecutionStatus::EXCEPTION) { |
615 | 0 | return ExecutionStatus::EXCEPTION; |
616 | 0 | } |
617 | 0 | auto index = res.getValue(); |
618 | 0 | if (index.getNumber() != integerIndex) { |
619 | 0 | return runtime.raiseRangeError( |
620 | 0 | "The value given for the index must be between 0 and 2 ^ 53 - 1"); |
621 | 0 | } |
622 | 0 | return res; |
623 | 0 | } |
624 | | |
625 | | CallResult<HermesValue> toIntegerOrInfinity( |
626 | | Runtime &runtime, |
627 | 521 | Handle<> valueHandle) { |
628 | 521 | auto res = toNumber_RJS(runtime, valueHandle); |
629 | 521 | if (res == ExecutionStatus::EXCEPTION) { |
630 | 0 | return ExecutionStatus::EXCEPTION; |
631 | 0 | } |
632 | 521 | double num = res->getNumber(); |
633 | | |
634 | 521 | double result; |
635 | 521 | if (std::isnan(num)) { |
636 | 0 | result = 0; |
637 | 521 | } else { |
638 | 521 | result = std::trunc(num); |
639 | 521 | } |
640 | | |
641 | 521 | return HermesValue::encodeTrustedNumberValue(result); |
642 | 521 | } |
643 | | |
644 | | /// Conversion of HermesValues to integers. |
645 | | template <typename T> |
646 | | static inline CallResult<HermesValue> toInt( |
647 | | Runtime &runtime, |
648 | 79 | Handle<> valueHandle) { |
649 | 79 | auto res = toNumber_RJS(runtime, valueHandle); |
650 | 79 | if (res == ExecutionStatus::EXCEPTION) { |
651 | 0 | return ExecutionStatus::EXCEPTION; |
652 | 0 | } |
653 | 79 | double num = res->getNumber(); |
654 | 79 | T result = static_cast<T>(hermes::truncateToInt32(num)); |
655 | 79 | return HermesValue::encodeUntrustedNumberValue(result); |
656 | 79 | } Unexecuted instantiation: Operations.cpp:hermes::vm::CallResult<hermes::vm::HermesValue, (hermes::vm::detail::CallResultSpecialize)2> hermes::vm::toInt<signed char>(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>) Unexecuted instantiation: Operations.cpp:hermes::vm::CallResult<hermes::vm::HermesValue, (hermes::vm::detail::CallResultSpecialize)2> hermes::vm::toInt<short>(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>) Unexecuted instantiation: Operations.cpp:hermes::vm::CallResult<hermes::vm::HermesValue, (hermes::vm::detail::CallResultSpecialize)2> hermes::vm::toInt<int>(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>) Unexecuted instantiation: Operations.cpp:hermes::vm::CallResult<hermes::vm::HermesValue, (hermes::vm::detail::CallResultSpecialize)2> hermes::vm::toInt<unsigned char>(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>) Unexecuted instantiation: Operations.cpp:hermes::vm::CallResult<hermes::vm::HermesValue, (hermes::vm::detail::CallResultSpecialize)2> hermes::vm::toInt<unsigned short>(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>) Operations.cpp:hermes::vm::CallResult<hermes::vm::HermesValue, (hermes::vm::detail::CallResultSpecialize)2> hermes::vm::toInt<unsigned int>(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>) Line | Count | Source | 648 | 79 | Handle<> valueHandle) { | 649 | 79 | auto res = toNumber_RJS(runtime, valueHandle); | 650 | 79 | if (res == ExecutionStatus::EXCEPTION) { | 651 | 0 | return ExecutionStatus::EXCEPTION; | 652 | 0 | } | 653 | 79 | double num = res->getNumber(); | 654 | 79 | T result = static_cast<T>(hermes::truncateToInt32(num)); | 655 | 79 | return HermesValue::encodeUntrustedNumberValue(result); | 656 | 79 | } |
|
657 | | |
658 | 0 | CallResult<HermesValue> toInt8(Runtime &runtime, Handle<> valueHandle) { |
659 | 0 | return toInt<int8_t>(runtime, valueHandle); |
660 | 0 | } |
661 | | |
662 | 0 | CallResult<HermesValue> toInt16(Runtime &runtime, Handle<> valueHandle) { |
663 | 0 | return toInt<int16_t>(runtime, valueHandle); |
664 | 0 | } |
665 | | |
666 | 0 | CallResult<HermesValue> toInt32_RJS(Runtime &runtime, Handle<> valueHandle) { |
667 | 0 | return toInt<int32_t>(runtime, valueHandle); |
668 | 0 | } |
669 | | |
670 | 0 | CallResult<HermesValue> toUInt8(Runtime &runtime, Handle<> valueHandle) { |
671 | 0 | return toInt<uint8_t>(runtime, valueHandle); |
672 | 0 | } |
673 | | |
674 | 0 | uint8_t toUInt8Clamp(double number) { |
675 | | // 3. If number is NaN, return +0. |
676 | | // 4. If number <= 0, return +0. |
677 | | // Not < so that NaN coerces to 0. |
678 | | // NOTE: this check correctly rounds numbers less than 0.5 |
679 | 0 | if (!(number >= 0.5)) { |
680 | 0 | return 0; |
681 | 0 | } |
682 | | |
683 | | // 5. If number >= 255, return 255. |
684 | 0 | if (number > 255) { |
685 | 0 | return 255; |
686 | 0 | } |
687 | | |
688 | | // The next steps are the equivalent of the spec's round-to-even requirement. |
689 | | // Round up and then do the even/odd check. |
690 | 0 | double toTruncate = number + 0.5; |
691 | 0 | uint8_t x = static_cast<uint8_t>(toTruncate); |
692 | | |
693 | | // If it was a tie (i.e. it ended in 0.5) then |
694 | 0 | if (x == toTruncate) { |
695 | | // number ended in 0.5 and was rounded up, reduce by 1 if odd, |
696 | | // else leave the same. |
697 | | // That is the same as unsetting the least significant bit. |
698 | 0 | return (x & ~1); |
699 | 0 | } else { |
700 | | // number did not end in 0.5, don't need to check the parity. |
701 | 0 | return x; |
702 | 0 | } |
703 | 0 | } |
704 | | |
705 | 0 | CallResult<HermesValue> toUInt8Clamp(Runtime &runtime, Handle<> valueHandle) { |
706 | | // 1. Let number be toNumber_RJS(argument) |
707 | 0 | auto res = toNumber_RJS(runtime, valueHandle); |
708 | 0 | if (res == ExecutionStatus::EXCEPTION) { |
709 | | // 2. ReturnIfAbrupt(number) |
710 | 0 | return ExecutionStatus::EXCEPTION; |
711 | 0 | } |
712 | 0 | return HermesValue::encodeUntrustedNumberValue( |
713 | 0 | toUInt8Clamp(res->getNumber())); |
714 | 0 | } |
715 | | |
716 | 0 | CallResult<HermesValue> toUInt16(Runtime &runtime, Handle<> valueHandle) { |
717 | 0 | return toInt<uint16_t>(runtime, valueHandle); |
718 | 0 | } |
719 | | |
720 | 79 | CallResult<HermesValue> toUInt32_RJS(Runtime &runtime, Handle<> valueHandle) { |
721 | 79 | return toInt<uint32_t>(runtime, valueHandle); |
722 | 79 | } |
723 | | |
724 | | CallResult<Handle<JSObject>> getPrimitivePrototype( |
725 | | Runtime &runtime, |
726 | 165 | Handle<> base) { |
727 | 165 | switch (base->getETag()) { |
728 | 0 | #ifdef HERMES_SLOW_DEBUG |
729 | 0 | case HermesValue::ETag::Invalid: |
730 | 0 | llvm_unreachable("invalid value"); |
731 | 0 | #endif // HERMES_SLOW_DEBUG |
732 | 0 | case HermesValue::ETag::Empty: |
733 | 0 | llvm_unreachable("empty value"); |
734 | 0 | case HermesValue::ETag::Native1: |
735 | 0 | case HermesValue::ETag::Native2: |
736 | 0 | llvm_unreachable("native value"); |
737 | 0 | case HermesValue::ETag::Object1: |
738 | 0 | case HermesValue::ETag::Object2: |
739 | 0 | llvm_unreachable("object value"); |
740 | 3 | case HermesValue::ETag::Undefined: |
741 | 3 | return runtime.raiseTypeError("Cannot convert undefined value to object"); |
742 | 0 | case HermesValue::ETag::Null: |
743 | 0 | return runtime.raiseTypeError("Cannot convert null value to object"); |
744 | 162 | case HermesValue::ETag::Str1: |
745 | 162 | case HermesValue::ETag::Str2: |
746 | 162 | return Handle<JSObject>::vmcast(&runtime.stringPrototype); |
747 | 0 | case HermesValue::ETag::BigInt1: |
748 | 0 | case HermesValue::ETag::BigInt2: |
749 | 0 | return Handle<JSObject>::vmcast(&runtime.bigintPrototype); |
750 | 0 | case HermesValue::ETag::Bool: |
751 | 0 | return Handle<JSObject>::vmcast(&runtime.booleanPrototype); |
752 | 0 | case HermesValue::ETag::Symbol: |
753 | 0 | return Handle<JSObject>::vmcast(&runtime.symbolPrototype); |
754 | 0 | default: |
755 | 0 | assert(base->isNumber() && "Unknown tag in getPrimitivePrototype."); |
756 | 0 | return Handle<JSObject>::vmcast(&runtime.numberPrototype); |
757 | 165 | } |
758 | 165 | } |
759 | | |
760 | 1.40k | CallResult<HermesValue> toObject(Runtime &runtime, Handle<> valueHandle) { |
761 | 1.40k | auto value = valueHandle.get(); |
762 | 1.40k | switch (value.getETag()) { |
763 | 0 | #ifdef HERMES_SLOW_DEBUG |
764 | 0 | case HermesValue::ETag::Invalid: |
765 | 0 | llvm_unreachable("invalid value"); |
766 | 0 | #endif // HERMES_SLOW_DEBUG |
767 | 0 | case HermesValue::ETag::Empty: |
768 | 0 | llvm_unreachable("empty value"); |
769 | 0 | case HermesValue::ETag::Native1: |
770 | 0 | case HermesValue::ETag::Native2: |
771 | 0 | llvm_unreachable("native value"); |
772 | 0 | case HermesValue::ETag::Undefined: |
773 | 0 | return runtime.raiseTypeError("Cannot convert undefined value to object"); |
774 | 0 | case HermesValue::ETag::Null: |
775 | 0 | return runtime.raiseTypeError("Cannot convert null value to object"); |
776 | 1.40k | case HermesValue::ETag::Object1: |
777 | 1.40k | case HermesValue::ETag::Object2: |
778 | 1.40k | return value; |
779 | 0 | case HermesValue::ETag::Bool: |
780 | 0 | return JSBoolean::create( |
781 | 0 | runtime, |
782 | 0 | value.getBool(), |
783 | 0 | Handle<JSObject>::vmcast(&runtime.booleanPrototype)) |
784 | 0 | .getHermesValue(); |
785 | 0 | case HermesValue::ETag::BigInt1: |
786 | 0 | case HermesValue::ETag::BigInt2: |
787 | 0 | return JSBigInt::create( |
788 | 0 | runtime, |
789 | 0 | Handle<BigIntPrimitive>::vmcast(valueHandle), |
790 | 0 | Handle<JSObject>::vmcast(&runtime.bigintPrototype)) |
791 | 0 | .getHermesValue(); |
792 | 0 | case HermesValue::ETag::Str1: |
793 | 0 | case HermesValue::ETag::Str2: { |
794 | 0 | auto res = JSString::create( |
795 | 0 | runtime, |
796 | 0 | Handle<StringPrimitive>::vmcast(valueHandle), |
797 | 0 | Handle<JSObject>::vmcast(&runtime.stringPrototype)); |
798 | 0 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
799 | 0 | return ExecutionStatus::EXCEPTION; |
800 | 0 | } |
801 | 0 | return res->getHermesValue(); |
802 | 0 | } |
803 | 0 | case HermesValue::ETag::Symbol: |
804 | 0 | return JSSymbol::create( |
805 | 0 | runtime, |
806 | 0 | *Handle<SymbolID>::vmcast(valueHandle), |
807 | 0 | Handle<JSObject>::vmcast(&runtime.symbolPrototype)) |
808 | 0 | .getHermesValue(); |
809 | 3 | default: |
810 | 3 | assert(valueHandle->isNumber() && "Unknown tag in toObject."); |
811 | 0 | return JSNumber::create( |
812 | 3 | runtime, |
813 | 3 | value.getNumber(), |
814 | 3 | Handle<JSObject>::vmcast(&runtime.numberPrototype)) |
815 | 3 | .getHermesValue(); |
816 | 1.40k | } |
817 | 1.40k | } |
818 | | |
819 | | ExecutionStatus amendPropAccessErrorMsgWithPropName( |
820 | | Runtime &runtime, |
821 | | Handle<> valueHandle, |
822 | | llvh::StringRef operationStr, |
823 | 3 | SymbolID id) { |
824 | 3 | if (!valueHandle->isNull() && !valueHandle->isUndefined()) { |
825 | | // If value is not null/undefined, fall back to the original exception. |
826 | 0 | return ExecutionStatus::EXCEPTION; |
827 | 0 | } |
828 | 3 | assert(!runtime.getThrownValue().isEmpty() && "Error must have been thrown"); |
829 | | // Clear the error first because we will re-throw. |
830 | 0 | runtime.clearThrownValue(); |
831 | | |
832 | | // Construct an error message that contains the property name. |
833 | 3 | llvh::StringRef valueStr = valueHandle->isNull() ? "null" : "undefined"; |
834 | 3 | return runtime.raiseTypeError( |
835 | 3 | TwineChar16("Cannot ") + operationStr + " property '" + |
836 | 3 | runtime.getIdentifierTable().getStringView(runtime, id) + "' of " + |
837 | 3 | valueStr); |
838 | 3 | } |
839 | | |
840 | | /// Implement a BigInt vs. String comparison operation using a user-provided |
841 | | /// \p comparator. Note that \p leftHandle is a Handle<BigIntPrimitive> to |
842 | | /// ensure the caller is putting the BigInt in the lhs (and adjusting \p |
843 | | /// comparator appropriately). |
844 | | /// \return false if StringToBigInt( \p rightHandle ) is undefined, otherwise |
845 | | /// returns \p comparator ( \p leftHandle <=> \p righHandle ). |
846 | | static CallResult<bool> compareBigIntAndString( |
847 | | Runtime &runtime, |
848 | | Handle<BigIntPrimitive> leftHandle, |
849 | | Handle<> rightHandle, |
850 | 0 | bool (*comparator)(int)) { |
851 | 0 | assert(rightHandle->isString() && "rhs should be string"); |
852 | | |
853 | 0 | auto bigintRight = stringToBigInt(runtime, rightHandle); |
854 | 0 | if (LLVM_UNLIKELY(bigintRight == ExecutionStatus::EXCEPTION)) { |
855 | 0 | return ExecutionStatus::EXCEPTION; |
856 | 0 | } |
857 | 0 | if (bigintRight->isUndefined()) { // Non-compliance: should be undefined. |
858 | 0 | return false; |
859 | 0 | } |
860 | 0 | assert(bigintRight->isBigInt() && "stringToBigInt resulted in non-bigint"); |
861 | 0 | return comparator(leftHandle->compare(bigintRight->getBigInt())); |
862 | 0 | } |
863 | | |
864 | | /// Implement a BigInt vs. Number comparison operation using a user-provided |
865 | | /// \p comparator. Note that \p leftHandle is a Handle<BigIntPrimitive> to |
866 | | /// ensure the caller is putting the BigInt in the lhs (and adjusting \p |
867 | | /// comparator appropriately). |
868 | | /// \return false if \p right is NaN, otherwise returns |
869 | | /// \p comparator ( \p leftHandle <=> \p righHandle ). |
870 | | static CallResult<bool> compareBigIntAndNumber( |
871 | | Runtime &runtime, |
872 | | Handle<BigIntPrimitive> leftHandle, |
873 | | double right, |
874 | 0 | bool (*comparator)(int)) { |
875 | 0 | switch (std::fpclassify(right)) { |
876 | 0 | case FP_NAN: |
877 | | // BigInt comparison to NaN is always false. |
878 | 0 | return false; |
879 | 0 | case FP_INFINITE: |
880 | | // If rhs is +infinite, it is greater than lhs; otherwise, it is less than |
881 | | // rhs. |
882 | 0 | return comparator(right > 0 ? -1 : 1); |
883 | 0 | default: |
884 | 0 | break; |
885 | 0 | } |
886 | | |
887 | | // Split the rhs into integral and fractional parts. |
888 | 0 | double integralPart; |
889 | 0 | const double fractionalPart = std::modf(right, &integralPart); |
890 | | |
891 | | // Now use the rhs' integral part to create a new BigInt, which is compared to |
892 | | // lhs. |
893 | 0 | auto rightHandle = BigIntPrimitive::fromDouble(runtime, integralPart); |
894 | 0 | if (LLVM_UNLIKELY(rightHandle == ExecutionStatus::EXCEPTION)) { |
895 | 0 | return ExecutionStatus::EXCEPTION; |
896 | 0 | } |
897 | | |
898 | | // If rhs' integral part is different than lhs, then use the integral parts' |
899 | | // comparison to decide the result. |
900 | 0 | if (int comparisonResult = leftHandle->compare(rightHandle->getBigInt())) { |
901 | 0 | return comparator(comparisonResult); |
902 | 0 | } |
903 | | |
904 | | // Lhs' and rhs' integral parts are equal, thus resort the rhs' fractional |
905 | | // part. |
906 | 0 | if (fractionalPart != 0) { |
907 | | // If rhs is negative, then it is smaller than lhs; otherwise, it is |
908 | | // greater. |
909 | 0 | return comparator(right < 0 ? 1 : -1); |
910 | 0 | } |
911 | | |
912 | | // Lhs' and rhs' integral parts are equal, and rhs does not have a fractional |
913 | | // part (it is zero), thus they are equal. |
914 | 0 | return comparator(0); |
915 | 0 | } |
916 | | |
917 | | /// Implement a comparison operator. First both operands a converted to |
918 | | /// primitives. If they both end up being strings, a lexicographical comparison |
919 | | /// is performed. Otherwise both operands are converted to numbers and the |
920 | | /// values are compared. |
921 | | /// \param oper is the comparison operator to use when comparing numbers. |
922 | | #define IMPLEMENT_COMPARISON_OP(name, oper) \ |
923 | | CallResult<bool> name( \ |
924 | 0 | Runtime &runtime, Handle<> leftHandle, Handle<> rightHandle) { \ |
925 | 0 | auto resLeft = \ |
926 | 0 | toPrimitive_RJS(runtime, leftHandle, PreferredType::NUMBER); \ |
927 | 0 | if (resLeft == ExecutionStatus::EXCEPTION) \ |
928 | 0 | return ExecutionStatus::EXCEPTION; \ |
929 | 0 | MutableHandle<> left(runtime, resLeft.getValue()); \ |
930 | 0 | \ |
931 | 0 | auto resRight = \ |
932 | 0 | toPrimitive_RJS(runtime, rightHandle, PreferredType::NUMBER); \ |
933 | 0 | if (resRight == ExecutionStatus::EXCEPTION) \ |
934 | 0 | return ExecutionStatus::EXCEPTION; \ |
935 | 0 | MutableHandle<> right(runtime, resRight.getValue()); \ |
936 | 0 | \ |
937 | 0 | /* If both are strings, we must do a string comparison.*/ \ |
938 | 0 | if (left->isString() && right->isString()) { \ |
939 | 0 | return left->getString()->compare(right->getString()) oper 0; \ |
940 | 0 | } \ |
941 | 0 | \ |
942 | 0 | if (left->isBigInt() && right->isString()) { \ |
943 | 0 | return compareBigIntAndString( \ |
944 | 0 | runtime, \ |
945 | 0 | Handle<BigIntPrimitive>::vmcast(left), \ |
946 | 0 | right, \ |
947 | 0 | [](int result) { return result oper 0; }); \ Unexecuted instantiation: Operations.cpp:hermes::vm::lessOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_0::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_4::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::lessEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_8::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_12::operator()(int) const |
948 | 0 | } \ |
949 | 0 | \ |
950 | 0 | if (left->isString() && right->isBigInt()) { \ |
951 | 0 | return compareBigIntAndString( \ |
952 | 0 | runtime, \ |
953 | 0 | Handle<BigIntPrimitive>::vmcast(right), \ |
954 | 0 | left, \ |
955 | 0 | [](int result) { return -result oper 0; }); \ Unexecuted instantiation: Operations.cpp:hermes::vm::lessOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_1::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_5::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::lessEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_9::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_13::operator()(int) const |
956 | 0 | } \ |
957 | 0 | \ |
958 | 0 | /* Convert both to a number and compare the numbers. */ \ |
959 | 0 | resLeft = toNumeric_RJS(runtime, left); \ |
960 | 0 | if (resLeft == ExecutionStatus::EXCEPTION) \ |
961 | 0 | return ExecutionStatus::EXCEPTION; \ |
962 | 0 | left = resLeft.getValue(); \ |
963 | 0 | resRight = toNumeric_RJS(runtime, right); \ |
964 | 0 | if (resRight == ExecutionStatus::EXCEPTION) \ |
965 | 0 | return ExecutionStatus::EXCEPTION; \ |
966 | 0 | right = resRight.getValue(); \ |
967 | 0 | \ |
968 | 0 | if (left->isNumber() && right->isNumber()) { \ |
969 | 0 | return left->getNumber() oper right->getNumber(); \ |
970 | 0 | } else if (left->isBigInt() && right->isBigInt()) { \ |
971 | 0 | return left->getBigInt()->compare(right->getBigInt()) oper 0; \ |
972 | 0 | } \ |
973 | 0 | \ |
974 | 0 | if (left->isBigInt() && right->isNumber()) { \ |
975 | 0 | return compareBigIntAndNumber( \ |
976 | 0 | runtime, \ |
977 | 0 | Handle<BigIntPrimitive>::vmcast(left), \ |
978 | 0 | right->getNumber(), \ |
979 | 0 | [](int result) { return result oper 0; }); \ Unexecuted instantiation: Operations.cpp:hermes::vm::lessOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_2::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_6::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::lessEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_10::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_14::operator()(int) const |
980 | 0 | } \ |
981 | 0 | assert( \ |
982 | 0 | left->isNumber() && right->isBigInt() && \ |
983 | 0 | "expecting one number and one bigint"); \ |
984 | 0 | return compareBigIntAndNumber( \ |
985 | 0 | runtime, \ |
986 | 0 | Handle<BigIntPrimitive>::vmcast(right), \ |
987 | 0 | left->getNumber(), \ |
988 | 0 | [](int result) { return -result oper 0; }); \ Unexecuted instantiation: Operations.cpp:hermes::vm::lessOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_3::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_7::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::lessEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_11::operator()(int) const Unexecuted instantiation: Operations.cpp:hermes::vm::greaterEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>)::$_15::operator()(int) const |
989 | 0 | } Unexecuted instantiation: hermes::vm::lessOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>) Unexecuted instantiation: hermes::vm::greaterOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>) Unexecuted instantiation: hermes::vm::lessEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>) Unexecuted instantiation: hermes::vm::greaterEqualOp_RJS(hermes::vm::Runtime&, hermes::vm::Handle<hermes::vm::HermesValue>, hermes::vm::Handle<hermes::vm::HermesValue>) |
990 | | |
991 | | IMPLEMENT_COMPARISON_OP(lessOp_RJS, <); |
992 | | IMPLEMENT_COMPARISON_OP(greaterOp_RJS, >); |
993 | | IMPLEMENT_COMPARISON_OP(lessEqualOp_RJS, <=); |
994 | | IMPLEMENT_COMPARISON_OP(greaterEqualOp_RJS, >=); |
995 | | |
996 | | /// ES11 7.2.15 Abstract Equality Comparison |
997 | | CallResult<bool> |
998 | 630 | abstractEqualityTest_RJS(Runtime &runtime, Handle<> xHandle, Handle<> yHandle) { |
999 | 630 | MutableHandle<> x{runtime, xHandle.get()}; |
1000 | 630 | MutableHandle<> y{runtime, yHandle.get()}; |
1001 | | |
1002 | 630 | while (true) { |
1003 | | // Combine tags for use in the switch statement. Use NativeValueTag as a |
1004 | | // placeholder for numbers. |
1005 | 630 | assert( |
1006 | 630 | !x->isNativeValue() && !x->isEmpty() && "invalid value for comparison"); |
1007 | 0 | assert( |
1008 | 630 | !y->isNativeValue() && !y->isEmpty() && "invalid value for comparison"); |
1009 | | |
1010 | | // The following macros are used to generate the switch cases using |
1011 | | // HermesValue::combineETags; an S in the name means it is a single ETag |
1012 | | // (e.g., ETag::Bool), while M means it is a multi ETag (e.g., ETag::Object1 |
1013 | | // and ETag::Object2). |
1014 | 0 | #define CASE_S_S(typeA, typeB) \ |
1015 | 0 | case HermesValue::combineETags( \ |
1016 | 0 | HermesValue::ETag::typeA, HermesValue::ETag::typeB): |
1017 | | |
1018 | 0 | #define CASE_S_M(typeA, typeB) \ |
1019 | 0 | CASE_S_S(typeA, typeB##1) \ |
1020 | 0 | CASE_S_S(typeA, typeB##2) |
1021 | | |
1022 | 0 | #define CASE_M_S(typeA, typeB) \ |
1023 | 0 | CASE_S_S(typeA##1, typeB) \ |
1024 | 0 | CASE_S_S(typeA##2, typeB) |
1025 | | |
1026 | 0 | #define CASE_M_M(typeA, typeB) \ |
1027 | 0 | CASE_M_S(typeA, typeB##1) \ |
1028 | 0 | CASE_M_S(typeA, typeB##2) |
1029 | | |
1030 | | // NUMBER_TAG is a "virtual" ETag member that is used to tag numbers (which |
1031 | | // don't have a tag assigned to them). It reuses ETag::Native1 there will |
1032 | | // never be any native values in this part of the code. |
1033 | 0 | #define NUMBER_TAG Native1 |
1034 | | |
1035 | | // Tag numbers as with the "virtual" ETag member NUMBER_TAG, and use default |
1036 | | // tag values for everything else. |
1037 | 0 | HermesValue::ETag xType = |
1038 | 630 | x->isNumber() ? HermesValue::ETag::NUMBER_TAG : x->getETag(); |
1039 | 630 | HermesValue::ETag yType = |
1040 | 630 | y->isNumber() ? HermesValue::ETag::NUMBER_TAG : y->getETag(); |
1041 | | |
1042 | 630 | switch (HermesValue::combineETags(xType, yType)) { |
1043 | | // 1. If Type(x) is the same as Type(y), then |
1044 | | // a. Return the result of performing Strict Equality Comparison x === y. |
1045 | 0 | CASE_S_S(Undefined, Undefined) |
1046 | 0 | CASE_S_S(Null, Null) { |
1047 | 0 | return true; |
1048 | 0 | } |
1049 | 0 | CASE_S_S(NUMBER_TAG, NUMBER_TAG) { |
1050 | 0 | return x->getNumber() == y->getNumber(); |
1051 | 0 | } |
1052 | 0 | CASE_M_M(Str, Str) { |
1053 | 0 | return x->getString()->equals(y->getString()); |
1054 | 0 | } |
1055 | 0 | CASE_M_M(BigInt, BigInt) { |
1056 | 0 | return x->getBigInt()->compare(y->getBigInt()) == 0; |
1057 | 0 | } |
1058 | 0 | CASE_S_S(Bool, Bool) |
1059 | 0 | CASE_S_S(Symbol, Symbol) |
1060 | 0 | CASE_M_M(Object, Object) { |
1061 | 0 | return x->getRaw() == y->getRaw(); |
1062 | 0 | } |
1063 | | // 2. If x is null and y is undefined, return true. |
1064 | | // 3. If x is undefined and y is null, return true. |
1065 | 0 | CASE_S_S(Undefined, Null) |
1066 | 0 | CASE_S_S(Null, Undefined) { |
1067 | 0 | return true; |
1068 | 0 | } |
1069 | | // 4. If Type(x) is Number and Type(y) is String, return the result of the |
1070 | | // comparison x == ! ToNumber(y). |
1071 | 0 | CASE_S_M(NUMBER_TAG, Str) { |
1072 | 0 | return x->getNumber() == |
1073 | 0 | stringToNumber(runtime, Handle<StringPrimitive>::vmcast(y)); |
1074 | 0 | } |
1075 | | // 5. If Type(x) is String and Type(y) is Number, return the result of the |
1076 | | // comparison ! ToNumber(x) == y. |
1077 | 0 | CASE_M_S(Str, NUMBER_TAG) { |
1078 | 0 | return stringToNumber(runtime, Handle<StringPrimitive>::vmcast(x)) == |
1079 | 0 | y->getNumber(); |
1080 | 0 | } |
1081 | | // 6. If Type(x) is BigInt and Type(y) is String, then |
1082 | 0 | CASE_M_M(BigInt, Str) { |
1083 | | // a. Let n be ! StringToBigInt(y). |
1084 | 0 | auto n = stringToBigInt(runtime, y); |
1085 | 0 | if (LLVM_UNLIKELY(n == ExecutionStatus::EXCEPTION)) { |
1086 | 0 | return ExecutionStatus::EXCEPTION; |
1087 | 0 | } |
1088 | | // b. If n is NaN, return false. |
1089 | | // N.B.: this has been amended in ES2023 to read |
1090 | | // If n is undefined, return false. |
1091 | 0 | if (n->isUndefined()) { |
1092 | 0 | return false; |
1093 | 0 | } |
1094 | | // c. Return the result of the comparison x == n. |
1095 | 0 | y = n.getValue(); |
1096 | 0 | break; |
1097 | 0 | } |
1098 | | // 7. If Type(x) is String and Type(y) is BigInt, return the result of the |
1099 | | // comparison y == x. |
1100 | 0 | CASE_M_M(Str, BigInt) { |
1101 | 0 | std::swap(x, y); |
1102 | 0 | break; |
1103 | 0 | } |
1104 | | // 8. If Type(x) is Boolean, return the result of the comparison ! |
1105 | | // ToNumber(x) == y. |
1106 | 0 | CASE_S_S(Bool, NUMBER_TAG) { |
1107 | | // Do both conversions and check numerical equality. |
1108 | 0 | return static_cast<double>(x->getBool()) == y->getNumber(); |
1109 | 0 | } |
1110 | 0 | CASE_S_M(Bool, Str) { |
1111 | | // Do string parsing and check double equality. |
1112 | 0 | return static_cast<double>(x->getBool()) == |
1113 | 0 | stringToNumber(runtime, Handle<StringPrimitive>::vmcast(y)); |
1114 | 0 | } |
1115 | 0 | CASE_S_M(Bool, BigInt) { |
1116 | 0 | return y->getBigInt()->compare(static_cast<int32_t>(x->getBool())) == 0; |
1117 | 0 | } |
1118 | 0 | CASE_S_M(Bool, Object) { |
1119 | 0 | x = HermesValue::encodeUntrustedNumberValue(x->getBool()); |
1120 | 0 | break; |
1121 | 0 | } |
1122 | | // 9. If Type(y) is Boolean, return the result of the comparison x == ! |
1123 | | // ToNumber(y). |
1124 | 0 | CASE_S_S(NUMBER_TAG, Bool) { |
1125 | 0 | return x->getNumber() == static_cast<double>(y->getBool()); |
1126 | 0 | } |
1127 | 0 | CASE_M_S(Str, Bool) { |
1128 | 0 | return stringToNumber(runtime, Handle<StringPrimitive>::vmcast(x)) == |
1129 | 0 | static_cast<double>(y->getBool()); |
1130 | 0 | } |
1131 | 0 | CASE_M_S(BigInt, Bool) { |
1132 | 0 | return x->getBigInt()->compare(static_cast<int32_t>(y->getBool())) == 0; |
1133 | 0 | } |
1134 | 0 | CASE_M_S(Object, Bool) { |
1135 | 0 | y = HermesValue::encodeUntrustedNumberValue(y->getBool()); |
1136 | 0 | break; |
1137 | 0 | } |
1138 | | // 10. If Type(x) is either String, Number, BigInt, or Symbol and Type(y) |
1139 | | // is Object, return the result of the comparison x == ToPrimitive(y). |
1140 | 0 | CASE_M_M(Str, Object) |
1141 | 0 | CASE_M_M(BigInt, Object) |
1142 | 0 | CASE_S_M(Symbol, Object) |
1143 | 0 | CASE_S_M(NUMBER_TAG, Object) { |
1144 | 0 | auto status = toPrimitive_RJS(runtime, y, PreferredType::NONE); |
1145 | 0 | if (status == ExecutionStatus::EXCEPTION) { |
1146 | 0 | return ExecutionStatus::EXCEPTION; |
1147 | 0 | } |
1148 | 0 | y = status.getValue(); |
1149 | 0 | break; |
1150 | 0 | } |
1151 | | // 11. If Type(x) is Object and Type(y) is either String, Number, BigInt, |
1152 | | // or Symbol, return the result of the comparison ToPrimitive(x) == y. |
1153 | 0 | CASE_M_M(Object, Str) |
1154 | 0 | CASE_M_M(Object, BigInt) |
1155 | 0 | CASE_M_S(Object, Symbol) |
1156 | 0 | CASE_M_S(Object, NUMBER_TAG) { |
1157 | 0 | auto status = toPrimitive_RJS(runtime, x, PreferredType::NONE); |
1158 | 0 | if (status == ExecutionStatus::EXCEPTION) { |
1159 | 0 | return ExecutionStatus::EXCEPTION; |
1160 | 0 | } |
1161 | 0 | x = status.getValue(); |
1162 | 0 | break; |
1163 | 0 | } |
1164 | | // 12. If Type(x) is BigInt and Type(y) is Number, or if Type(x) is |
1165 | | // Number and Type(y) is BigInt, then a. If x or y are any of NaN, +∞, |
1166 | | // or -∞, return false. b. If the mathematical value of x is equal to |
1167 | | // the mathematical value of y, return true; otherwise return false. |
1168 | 0 | CASE_M_S(BigInt, NUMBER_TAG) { |
1169 | 0 | std::swap(x, y); |
1170 | 0 | [[fallthrough]]; |
1171 | 0 | } |
1172 | 0 | CASE_S_M(NUMBER_TAG, BigInt) { |
1173 | 0 | if (!isIntegralNumber(x->getNumber())) { |
1174 | 0 | return false; |
1175 | 0 | } |
1176 | | |
1177 | 0 | auto xAsBigInt = BigIntPrimitive::fromDouble(runtime, x->getNumber()); |
1178 | 0 | if (LLVM_UNLIKELY(xAsBigInt == ExecutionStatus::EXCEPTION)) { |
1179 | 0 | return ExecutionStatus::EXCEPTION; |
1180 | 0 | } |
1181 | 0 | return xAsBigInt->getBigInt()->compare(y->getBigInt()) == 0; |
1182 | 0 | } |
1183 | | |
1184 | | // 13. Return false. |
1185 | 630 | default: |
1186 | 630 | return false; |
1187 | 630 | } |
1188 | | |
1189 | 630 | #undef CASE_S_S |
1190 | 630 | #undef CASE_S_M |
1191 | 630 | #undef CASE_M_S |
1192 | 630 | #undef CASE_M_M |
1193 | 630 | #undef NUMBER_TAG |
1194 | 630 | } |
1195 | 630 | } |
1196 | | |
1197 | 1.89k | bool strictEqualityTest(HermesValue x, HermesValue y) { |
1198 | | // Numbers are special because they can have different tags and they don't |
1199 | | // obey bit-exact equality (because of NaN). |
1200 | 1.89k | if (x.isNumber()) |
1201 | 0 | return y.isNumber() && x.getNumber() == y.getNumber(); |
1202 | | // If they are not numbers and are bit exact, they must be the same. |
1203 | 1.89k | if (x.getRaw() == y.getRaw()) |
1204 | 1.89k | return true; |
1205 | | // All the rest of the cases need to have the same tags. |
1206 | 0 | if (x.getTag() != y.getTag()) |
1207 | 0 | return false; |
1208 | | // Strings need deep comparison. |
1209 | 0 | if (x.isString()) |
1210 | 0 | return x.getString()->equals(y.getString()); |
1211 | | |
1212 | | // The only remaining case is bigint, which also needs a deep comparison. |
1213 | 0 | return x.isBigInt() && x.getBigInt()->compare(y.getBigInt()) == 0; |
1214 | 0 | } |
1215 | | |
1216 | | CallResult<HermesValue> |
1217 | 31.1k | addOp_RJS(Runtime &runtime, Handle<> xHandle, Handle<> yHandle) { |
1218 | 31.1k | auto resX = toPrimitive_RJS(runtime, xHandle, PreferredType::NONE); |
1219 | 31.1k | if (resX == ExecutionStatus::EXCEPTION) { |
1220 | 0 | return ExecutionStatus::EXCEPTION; |
1221 | 0 | } |
1222 | 31.1k | auto xPrim = runtime.makeHandle(resX.getValue()); |
1223 | | |
1224 | 31.1k | auto resY = toPrimitive_RJS(runtime, yHandle, PreferredType::NONE); |
1225 | 31.1k | if (resY == ExecutionStatus::EXCEPTION) { |
1226 | 0 | return ExecutionStatus::EXCEPTION; |
1227 | 0 | } |
1228 | 31.1k | auto yPrim = runtime.makeHandle(resY.getValue()); |
1229 | | |
1230 | | // If one of the values is a string, concatenate as strings. |
1231 | 31.1k | if (xPrim->isString() || yPrim->isString()) { |
1232 | 31.1k | auto resX = toString_RJS(runtime, xPrim); |
1233 | 31.1k | if (resX == ExecutionStatus::EXCEPTION) { |
1234 | 0 | return ExecutionStatus::EXCEPTION; |
1235 | 0 | } |
1236 | 31.1k | auto xStr = runtime.makeHandle(std::move(*resX)); |
1237 | | |
1238 | 31.1k | auto resY = toString_RJS(runtime, yPrim); |
1239 | 31.1k | if (resY == ExecutionStatus::EXCEPTION) { |
1240 | 0 | return ExecutionStatus::EXCEPTION; |
1241 | 0 | } |
1242 | 31.1k | auto yStr = runtime.makeHandle(std::move(*resY)); |
1243 | | |
1244 | 31.1k | return StringPrimitive::concat(runtime, xStr, yStr); |
1245 | 31.1k | } |
1246 | | |
1247 | | // xPrim and yPrim are primitives; hence, they are already bigints, or they |
1248 | | // will never be bigints. |
1249 | 0 | if (LLVM_LIKELY(!xPrim->isBigInt())) { |
1250 | | // xPrim is not a bigint; thus this is Number + Number. |
1251 | 0 | auto res = toNumber_RJS(runtime, xPrim); |
1252 | 0 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
1253 | 0 | return ExecutionStatus::EXCEPTION; |
1254 | 0 | } |
1255 | 0 | const double xNum = res->getNumber(); |
1256 | | // N.B.: toNumber(yPrim) will raise an TypeError if yPrim is bigint, which |
1257 | | // is the correct exception to be raised when trying to perform |
1258 | | // Number + BigInt. This avoids the need to check if yPrim is a bigint. |
1259 | 0 | res = toNumber_RJS(runtime, yPrim); |
1260 | 0 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
1261 | 0 | return ExecutionStatus::EXCEPTION; |
1262 | 0 | } |
1263 | 0 | const double yNum = res->getNumber(); |
1264 | 0 | return HermesValue::encodeUntrustedNumberValue(xNum + yNum); |
1265 | 0 | } |
1266 | | |
1267 | | // yPrim is a primitive; therefore it is already a BigInt, or it will never be |
1268 | | // one. |
1269 | 0 | if (!yPrim->isBigInt()) { |
1270 | 0 | return runtime.raiseTypeErrorForValue( |
1271 | 0 | "Cannot convert ", yHandle, " to BigInt"); |
1272 | 0 | } |
1273 | | |
1274 | 0 | return BigIntPrimitive::add( |
1275 | 0 | runtime, |
1276 | 0 | runtime.makeHandle(xPrim->getBigInt()), |
1277 | 0 | runtime.makeHandle(yPrim->getBigInt())); |
1278 | 0 | } |
1279 | | |
1280 | | static const size_t MIN_RADIX = 2; |
1281 | | static const size_t MAX_RADIX = 36; |
1282 | | |
1283 | 0 | static inline char toRadixChar(unsigned x, unsigned radix) { |
1284 | 0 | const char chars[] = "0123456789abcdefghijklmnopqrstuvwxyz"; |
1285 | 0 | static_assert(sizeof(chars) - 1 == MAX_RADIX, "Invalid chars array"); |
1286 | 0 | assert( |
1287 | 0 | x < radix && x < std::strlen(chars) && |
1288 | 0 | "invalid number to radix conversion"); |
1289 | 0 | return chars[x]; |
1290 | 0 | } |
1291 | | |
1292 | | /// \return the exponent component of the double \p x. |
1293 | 0 | static inline int doubleExponent(double x) { |
1294 | 0 | int e; |
1295 | 0 | std::frexp(x, &e); |
1296 | 0 | return e; |
1297 | 0 | } |
1298 | | |
1299 | | Handle<StringPrimitive> |
1300 | 0 | numberToStringWithRadix(Runtime &runtime, double number, unsigned radix) { |
1301 | 0 | (void)MIN_RADIX; |
1302 | 0 | (void)MAX_RADIX; |
1303 | 0 | assert(MIN_RADIX <= radix && radix <= MAX_RADIX && "Invalid radix"); |
1304 | | // Two parts of the final result: integer part and fractional part. |
1305 | 0 | llvh::SmallString<64> result{}; |
1306 | | |
1307 | | // Used to store just the fractional part of the string (not including '.'). |
1308 | 0 | llvh::SmallString<32> fStr{}; |
1309 | | |
1310 | | // If negative, treat as if positive and add a '-' later. |
1311 | 0 | bool negative = false; |
1312 | 0 | if (number < 0) { |
1313 | 0 | negative = true; |
1314 | 0 | number = -number; |
1315 | 0 | } |
1316 | | |
1317 | | // Split number into integer and fractional parts. |
1318 | 0 | double iPart; |
1319 | 0 | double fPart = std::modf(number, &iPart); |
1320 | | |
1321 | | // If there's a fractional part, convert it and store in fStr. |
1322 | 0 | if (fPart != 0) { |
1323 | | // Distance to the next double value. |
1324 | 0 | double next = |
1325 | 0 | std::nextafter(number, std::numeric_limits<double>::infinity()); |
1326 | 0 | double minDenorm = |
1327 | 0 | std::nextafter(0.0, std::numeric_limits<double>::infinity()); |
1328 | | |
1329 | | // Precision of the input (half the distance to the next double). |
1330 | | // We only compute digits up to that precision. |
1331 | | // Ensure that delta > 0 by clamping it by the min denormalized positive |
1332 | | // double number. |
1333 | 0 | double delta = std::max(0.5 * (next - number), minDenorm); |
1334 | |
|
1335 | 0 | while (fPart > delta) { |
1336 | | // Multiply by radix to find the next digit. |
1337 | 0 | fPart *= radix; |
1338 | 0 | delta *= radix; |
1339 | | // Write the next digit. |
1340 | 0 | unsigned digit = static_cast<unsigned>(fPart); |
1341 | 0 | fStr.push_back(toRadixChar(digit, radix)); |
1342 | | // Remove current digit from fPart to prepare for next iteration. |
1343 | 0 | fPart -= digit; |
1344 | | // Round-to-even. |
1345 | 0 | if (fPart > 0.5 || (fPart == 0.5 && (digit & 1))) { |
1346 | | // Must round up, necessitating changing written digits. |
1347 | 0 | if (fPart + delta > 1) { |
1348 | | // Round because printing the next closest double would not give |
1349 | | // closer results than rounding. The distance between the next |
1350 | | // double and this one is large enough that at this point, we're |
1351 | | // doing worse than rounding up if we were to print out the next |
1352 | | // double precisely. |
1353 | 0 | while (true) { |
1354 | | // Rounding requires backtracking to fix everything up. |
1355 | 0 | if (fStr.size() == 0) { |
1356 | | // Rounding failed to stop in the fractional part, |
1357 | | // so carry over to the integral part. |
1358 | 0 | ++iPart; |
1359 | 0 | break; |
1360 | 0 | } |
1361 | | // Iterator to the last digit of the string. |
1362 | 0 | char &c = fStr.back(); |
1363 | 0 | unsigned digitForC = c <= '9' ? c - '0' : c - 'a' + 10; |
1364 | 0 | if (digitForC + 1 < radix) { |
1365 | | // Can increment this digit, and we're done. |
1366 | 0 | c = toRadixChar(digitForC + 1, radix); |
1367 | 0 | break; |
1368 | 0 | } |
1369 | | // We weren't able to increment, so this will be a trailing 0, |
1370 | | // which we don't want to keep around anyway. So, pop the last |
1371 | | // digit off the string and continue on. |
1372 | 0 | fStr.pop_back(); |
1373 | 0 | } |
1374 | | // Rounded off the number, done writing the fractional string. |
1375 | 0 | break; |
1376 | 0 | } |
1377 | 0 | } |
1378 | 0 | } |
1379 | 0 | } |
1380 | | |
1381 | | // Now, create the integer part. |
1382 | 0 | if (iPart == 0) { |
1383 | 0 | result.push_back('0'); |
1384 | 0 | } else { |
1385 | | // Write the number backwards, then reverse it. This simplifies the code. |
1386 | | |
1387 | | // Physical mantissa size. |
1388 | | // Hidden bit is not included because it's not after the decimal point in |
1389 | | // the binary scientific notation representation of a double. |
1390 | | // We use this to calculate whether we have the precision required to know |
1391 | | // what the next digit is going to be. |
1392 | 0 | constexpr const int MANTISSA_SIZE = DBL_MANT_DIG - 1; |
1393 | | |
1394 | | // Handle trailing zeros. |
1395 | 0 | while (doubleExponent(iPart / radix) > MANTISSA_SIZE) { |
1396 | | // (iPart / radix) doesn't have enough precision to be useful here, |
1397 | | // because its exponent is larger than the number of bits that the |
1398 | | // mantissa can encode. So, just put a trailing zero, divide by radix, |
1399 | | // and move on to the next digit. |
1400 | 0 | result.push_back('0'); |
1401 | 0 | iPart /= radix; |
1402 | 0 | } |
1403 | | |
1404 | | // Print the rest of the string when we know we have enough precision to |
1405 | | // do so. |
1406 | 0 | while (iPart > 0) { |
1407 | | // Cast digit to int because we know 2 <= digit <= 36. |
1408 | 0 | int digit = static_cast<int>(std::fmod(iPart, radix)); |
1409 | 0 | result.push_back(toRadixChar(digit, radix)); |
1410 | 0 | iPart = (iPart - digit) / radix; |
1411 | 0 | } |
1412 | | |
1413 | | // Int string was generated in reverse, so flip it. |
1414 | 0 | std::reverse(result.begin(), result.end()); |
1415 | 0 | } |
1416 | | |
1417 | | // Concatenate the fractional string on if it exists. |
1418 | 0 | if (!fStr.empty()) { |
1419 | 0 | result += '.'; |
1420 | 0 | result += fStr; |
1421 | 0 | } |
1422 | | |
1423 | | // Account for negative numbers. |
1424 | 0 | if (negative) { |
1425 | 0 | result.insert(result.begin(), '-'); |
1426 | 0 | } |
1427 | |
|
1428 | 0 | return runtime.makeHandle<StringPrimitive>(runtime.ignoreAllocationFailure( |
1429 | 0 | StringPrimitive::create(runtime, result))); |
1430 | 0 | } |
1431 | | |
1432 | | CallResult<PseudoHandle<>> |
1433 | 495 | getMethod(Runtime &runtime, Handle<> O, Handle<> key) { |
1434 | 495 | GCScopeMarkerRAII gcScope{runtime}; |
1435 | 495 | auto objRes = toObject(runtime, O); |
1436 | 495 | if (LLVM_UNLIKELY(objRes == ExecutionStatus::EXCEPTION)) { |
1437 | 0 | return ExecutionStatus::EXCEPTION; |
1438 | 0 | } |
1439 | 495 | auto obj = runtime.makeHandle<JSObject>(*objRes); |
1440 | 495 | auto funcRes = JSObject::getComputed_RJS(obj, runtime, key); |
1441 | 495 | if (LLVM_UNLIKELY(funcRes == ExecutionStatus::EXCEPTION)) { |
1442 | 0 | return ExecutionStatus::EXCEPTION; |
1443 | 0 | } |
1444 | 495 | if ((*funcRes)->isUndefined() || (*funcRes)->isNull()) { |
1445 | 495 | return PseudoHandle<>::create(HermesValue::encodeUndefinedValue()); |
1446 | 495 | } |
1447 | 0 | if (!vmisa<Callable>(funcRes->get())) { |
1448 | 0 | return runtime.raiseTypeError("Could not get callable method from object"); |
1449 | 0 | } |
1450 | 0 | return funcRes; |
1451 | 0 | } |
1452 | | |
1453 | | CallResult<IteratorRecord> getIterator( |
1454 | | Runtime &runtime, |
1455 | | Handle<> obj, |
1456 | 2 | llvh::Optional<Handle<Callable>> methodOpt) { |
1457 | 2 | MutableHandle<Callable> method{runtime}; |
1458 | 2 | if (LLVM_LIKELY(!methodOpt.hasValue())) { |
1459 | 2 | auto methodRes = getMethod( |
1460 | 2 | runtime, |
1461 | 2 | obj, |
1462 | 2 | runtime.makeHandle( |
1463 | 2 | Predefined::getSymbolID(Predefined::SymbolIterator))); |
1464 | 2 | if (LLVM_UNLIKELY(methodRes == ExecutionStatus::EXCEPTION)) { |
1465 | 0 | return ExecutionStatus::EXCEPTION; |
1466 | 0 | } |
1467 | 2 | if (!vmisa<Callable>(methodRes->getHermesValue())) { |
1468 | 2 | return runtime.raiseTypeError("iterator method is not callable"); |
1469 | 2 | } |
1470 | 0 | method = vmcast<Callable>(methodRes->getHermesValue()); |
1471 | 0 | } else { |
1472 | 0 | method = **methodOpt; |
1473 | 0 | } |
1474 | 0 | auto iteratorRes = Callable::executeCall0(method, runtime, obj); |
1475 | 0 | if (LLVM_UNLIKELY(iteratorRes == ExecutionStatus::EXCEPTION)) { |
1476 | 0 | return ExecutionStatus::EXCEPTION; |
1477 | 0 | } |
1478 | 0 | if (LLVM_UNLIKELY(!(*iteratorRes)->isObject())) { |
1479 | 0 | return runtime.raiseTypeError("iterator is not an object"); |
1480 | 0 | } |
1481 | 0 | auto iterator = runtime.makeHandle<JSObject>(std::move(*iteratorRes)); |
1482 | |
|
1483 | 0 | CallResult<PseudoHandle<>> nextMethodRes = JSObject::getNamed_RJS( |
1484 | 0 | iterator, runtime, Predefined::getSymbolID(Predefined::next)); |
1485 | 0 | if (LLVM_UNLIKELY(nextMethodRes == ExecutionStatus::EXCEPTION)) { |
1486 | 0 | return ExecutionStatus::EXCEPTION; |
1487 | 0 | } |
1488 | | |
1489 | | // We perform this check prior to returning, because every function in the JS |
1490 | | // library which gets an iterator immediately calls the 'next' function. |
1491 | 0 | if (!vmisa<Callable>(nextMethodRes->get())) { |
1492 | 0 | return runtime.raiseTypeError("'next' method on iterator must be callable"); |
1493 | 0 | } |
1494 | | |
1495 | 0 | auto nextMethod = |
1496 | 0 | Handle<Callable>::vmcast(runtime.makeHandle(std::move(*nextMethodRes))); |
1497 | |
|
1498 | 0 | return IteratorRecord{iterator, nextMethod}; |
1499 | 0 | } |
1500 | | |
1501 | | CallResult<PseudoHandle<JSObject>> iteratorNext( |
1502 | | Runtime &runtime, |
1503 | | const IteratorRecord &iteratorRecord, |
1504 | 0 | llvh::Optional<Handle<>> value) { |
1505 | 0 | GCScopeMarkerRAII marker{runtime}; |
1506 | 0 | auto resultRes = value |
1507 | 0 | ? Callable::executeCall1( |
1508 | 0 | iteratorRecord.nextMethod, |
1509 | 0 | runtime, |
1510 | 0 | iteratorRecord.iterator, |
1511 | 0 | value->getHermesValue()) |
1512 | 0 | : Callable::executeCall0( |
1513 | 0 | iteratorRecord.nextMethod, runtime, iteratorRecord.iterator); |
1514 | 0 | if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { |
1515 | 0 | return ExecutionStatus::EXCEPTION; |
1516 | 0 | } |
1517 | 0 | if (LLVM_UNLIKELY(!(*resultRes)->isObject())) { |
1518 | 0 | return runtime.raiseTypeError("iterator.next() did not return an object"); |
1519 | 0 | } |
1520 | 0 | return PseudoHandle<JSObject>::vmcast(std::move(*resultRes)); |
1521 | 0 | } |
1522 | | |
1523 | | CallResult<PseudoHandle<HermesValue>> iteratorValue( |
1524 | | Runtime &runtime, |
1525 | 0 | Handle<JSObject> iterResult) { |
1526 | 0 | return JSObject::getNamed_RJS( |
1527 | 0 | iterResult, runtime, Predefined::getSymbolID(Predefined::value)); |
1528 | 0 | } |
1529 | | |
1530 | | CallResult<Handle<JSObject>> iteratorStep( |
1531 | | Runtime &runtime, |
1532 | 0 | const IteratorRecord &iteratorRecord) { |
1533 | 0 | auto resultRes = iteratorNext(runtime, iteratorRecord); |
1534 | 0 | if (LLVM_UNLIKELY(resultRes == ExecutionStatus::EXCEPTION)) { |
1535 | 0 | return ExecutionStatus::EXCEPTION; |
1536 | 0 | } |
1537 | 0 | Handle<JSObject> result = runtime.makeHandle(std::move(*resultRes)); |
1538 | 0 | auto completeRes = JSObject::getNamed_RJS( |
1539 | 0 | result, runtime, Predefined::getSymbolID(Predefined::done)); |
1540 | 0 | if (LLVM_UNLIKELY(completeRes == ExecutionStatus::EXCEPTION)) { |
1541 | 0 | return ExecutionStatus::EXCEPTION; |
1542 | 0 | } |
1543 | 0 | if (toBoolean(completeRes->get())) { |
1544 | 0 | return Runtime::makeNullHandle<JSObject>(); |
1545 | 0 | } |
1546 | 0 | return result; |
1547 | 0 | } |
1548 | | |
1549 | | ExecutionStatus iteratorClose( |
1550 | | Runtime &runtime, |
1551 | | Handle<JSObject> iterator, |
1552 | 0 | Handle<> completion) { |
1553 | 0 | ExecutionStatus completionStatus = completion->isEmpty() |
1554 | 0 | ? ExecutionStatus::RETURNED |
1555 | 0 | : ExecutionStatus::EXCEPTION; |
1556 | | |
1557 | | // 4. Let innerResult be GetMethod(iterator, "return"). |
1558 | | // Do this lazily: innerResult is only actually used if GetMethod returns |
1559 | | // a callable which, when called, doesn't throw. Defer storing to innerResult |
1560 | | // until that point. |
1561 | 0 | auto returnRes = getMethod( |
1562 | 0 | runtime, |
1563 | 0 | iterator, |
1564 | 0 | runtime.makeHandle(Predefined::getSymbolID(Predefined::returnStr))); |
1565 | |
|
1566 | 0 | MutableHandle<> innerResult{runtime}; |
1567 | 0 | if (LLVM_LIKELY(returnRes != ExecutionStatus::EXCEPTION)) { |
1568 | 0 | if (!vmisa<Callable>(returnRes->getHermesValue())) { |
1569 | 0 | runtime.setThrownValue(*completion); |
1570 | 0 | return completionStatus; |
1571 | 0 | } |
1572 | 0 | Handle<Callable> returnFn = |
1573 | 0 | runtime.makeHandle(vmcast<Callable>(returnRes->getHermesValue())); |
1574 | 0 | auto innerResultRes = Callable::executeCall0(returnFn, runtime, iterator); |
1575 | 0 | if (LLVM_UNLIKELY(innerResultRes == ExecutionStatus::EXCEPTION)) { |
1576 | 0 | if (isUncatchableError(runtime.getThrownValue())) { |
1577 | | // If the call to return threw an uncatchable exception, that overrides |
1578 | | // the completion, since the point of an uncatchable exception is to |
1579 | | // prevent more JS from executing. |
1580 | 0 | return ExecutionStatus::EXCEPTION; |
1581 | 0 | } |
1582 | | // If the error is catchable, suppress it temporarily below in lieu |
1583 | | // of the returnRes exception by writing to innerResultException. |
1584 | | // Spec text overwrites the value in `innerResult`. |
1585 | 0 | } else { |
1586 | 0 | innerResult = std::move(*innerResultRes); |
1587 | 0 | } |
1588 | 0 | } |
1589 | | // Runtime::thrownValue now contains the innerResult's exception if it |
1590 | | // was thrown. |
1591 | | // GetMethod error here is deliberately suppressed (no "?" in the spec). |
1592 | 0 | if (completionStatus == ExecutionStatus::EXCEPTION) { |
1593 | | // 6. If completion.[[Type]] is throw, return Completion(completion). |
1594 | | // Note: Overrides the innerResult exception. |
1595 | 0 | runtime.setThrownValue(*completion); |
1596 | 0 | return ExecutionStatus::EXCEPTION; |
1597 | 0 | } |
1598 | 0 | if (LLVM_UNLIKELY(!runtime.getThrownValue().isEmpty())) { |
1599 | | // 7. If innerResult.[[Type]] is throw, return Completion(innerResult). |
1600 | | // Note: innerResult exception is still in Runtime::thrownValue, |
1601 | | // so there is no need to set it again. |
1602 | 0 | return ExecutionStatus::EXCEPTION; |
1603 | 0 | } |
1604 | 0 | if (!innerResult->isObject()) { |
1605 | | // 8. If Type(innerResult.[[Value]]) is not Object, |
1606 | | // throw a TypeError exception. |
1607 | 0 | return runtime.raiseTypeError("iterator.return() did not return an object"); |
1608 | 0 | } |
1609 | 0 | return ExecutionStatus::RETURNED; |
1610 | 0 | } |
1611 | | |
1612 | | CallResult<Handle<JSArray>> iterableToArray( |
1613 | | Runtime &runtime, |
1614 | 0 | Handle<HermesValue> items) { |
1615 | | // IterableToList: 2a. Let iteratorRecord be ? GetIterator(items, sync). |
1616 | 0 | CallResult<IteratorRecord> iteratorRecordRes = getIterator(runtime, items); |
1617 | 0 | if (LLVM_UNLIKELY(iteratorRecordRes == ExecutionStatus::EXCEPTION)) { |
1618 | 0 | return ExecutionStatus::EXCEPTION; |
1619 | 0 | } |
1620 | 0 | IteratorRecord iteratorRecord = *iteratorRecordRes; |
1621 | | |
1622 | | // CreateArrayFromList: 1. Let array be ! ArrayCreate(0). |
1623 | 0 | auto arrRes = JSArray::create(runtime, 0, 0); |
1624 | 0 | assert(arrRes != ExecutionStatus::EXCEPTION && "could not create array"); |
1625 | 0 | Handle<JSArray> array = *arrRes; |
1626 | | // CreateArrayFromList: 2. Let n be 0. |
1627 | 0 | size_t n = 0; |
1628 | |
|
1629 | 0 | GCScopeMarkerRAII marker{runtime}; |
1630 | 0 | for (;; marker.flush()) { |
1631 | | // IterableToList: 5.a. Set next to ? IteratorStep(iteratorRecord). |
1632 | 0 | CallResult<Handle<JSObject>> nextRes = |
1633 | 0 | iteratorStep(runtime, iteratorRecord); |
1634 | 0 | if (LLVM_UNLIKELY(nextRes == ExecutionStatus::EXCEPTION)) { |
1635 | 0 | return ExecutionStatus::EXCEPTION; |
1636 | 0 | } |
1637 | 0 | if (!*nextRes) { |
1638 | 0 | break; |
1639 | 0 | } |
1640 | | // 5.b.i. Let nextValue be ? IteratorValue(next). |
1641 | 0 | CallResult<PseudoHandle<HermesValue>> nextValueRes = |
1642 | 0 | iteratorValue(runtime, *nextRes); |
1643 | 0 | if (LLVM_UNLIKELY(nextValueRes == ExecutionStatus::EXCEPTION)) { |
1644 | 0 | return ExecutionStatus::EXCEPTION; |
1645 | 0 | } |
1646 | | // CreateArrayFromList: 3.a Perform ! CreateDataPropertyOrThrow(array, ! |
1647 | | // ToString(𝔽(n)), e). |
1648 | 0 | JSArray::setElementAt( |
1649 | 0 | array, runtime, n, runtime.makeHandle(std::move(*nextValueRes))); |
1650 | | // CreateArrayFromList: 3.b Set n to n + 1. |
1651 | 0 | n++; |
1652 | 0 | } |
1653 | 0 | if (LLVM_UNLIKELY( |
1654 | 0 | JSArray::setLengthProperty(array, runtime, n) == |
1655 | 0 | ExecutionStatus::EXCEPTION)) { |
1656 | 0 | return ExecutionStatus::EXCEPTION; |
1657 | 0 | } |
1658 | | // 4. Return array. |
1659 | 0 | return array; |
1660 | 0 | } |
1661 | | |
1662 | 0 | bool isUncatchableError(HermesValue value) { |
1663 | 0 | if (auto *jsError = dyn_vmcast<JSError>(value)) { |
1664 | 0 | return !jsError->catchable(); |
1665 | 0 | } |
1666 | 0 | return false; |
1667 | 0 | } |
1668 | | |
1669 | | Handle<JSObject> |
1670 | 0 | createIterResultObject(Runtime &runtime, Handle<> value, bool done) { |
1671 | 0 | auto objHandle = runtime.makeHandle(JSObject::create(runtime)); |
1672 | 0 | auto status = JSObject::defineOwnProperty( |
1673 | 0 | objHandle, |
1674 | 0 | runtime, |
1675 | 0 | Predefined::getSymbolID(Predefined::value), |
1676 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
1677 | 0 | value); |
1678 | 0 | (void)status; |
1679 | 0 | assert( |
1680 | 0 | status != ExecutionStatus::EXCEPTION && *status && |
1681 | 0 | "put own value property cannot fail"); |
1682 | 0 | status = JSObject::defineOwnProperty( |
1683 | 0 | objHandle, |
1684 | 0 | runtime, |
1685 | 0 | Predefined::getSymbolID(Predefined::done), |
1686 | 0 | DefinePropertyFlags::getDefaultNewPropertyFlags(), |
1687 | 0 | Runtime::getBoolValue(done)); |
1688 | 0 | assert( |
1689 | 0 | status != ExecutionStatus::EXCEPTION && *status && |
1690 | 0 | "put own value property cannot fail"); |
1691 | 0 | return objHandle; |
1692 | 0 | } |
1693 | | |
1694 | | CallResult<Handle<Callable>> speciesConstructor( |
1695 | | Handle<JSObject> O, |
1696 | | Runtime &runtime, |
1697 | 0 | Handle<Callable> defaultConstructor) { |
1698 | | // construct from the "constructor" property in self if that is defined, else |
1699 | | // use the default one. |
1700 | 0 | auto res = JSObject::getNamed_RJS( |
1701 | 0 | O, runtime, Predefined::getSymbolID(Predefined::constructor)); |
1702 | 0 | if (res == ExecutionStatus::EXCEPTION) { |
1703 | 0 | return ExecutionStatus::EXCEPTION; |
1704 | 0 | } |
1705 | 0 | PseudoHandle<> cons = std::move(*res); |
1706 | 0 | if (cons->isUndefined()) { |
1707 | 0 | return defaultConstructor; |
1708 | 0 | } |
1709 | 0 | if (!cons->isObject()) { |
1710 | 0 | return runtime.raiseTypeError( |
1711 | 0 | "Constructor must be an object if it is not undefined"); |
1712 | 0 | } |
1713 | | // There is no @@species (no Symbols yet), so we'll assume that there was |
1714 | | // no other constructor specified. |
1715 | 0 | return defaultConstructor; |
1716 | 0 | } |
1717 | | |
1718 | 0 | CallResult<bool> isConstructor(Runtime &runtime, HermesValue value) { |
1719 | 0 | return isConstructor(runtime, dyn_vmcast<Callable>(value)); |
1720 | 0 | } |
1721 | | |
1722 | 0 | CallResult<bool> isConstructor(Runtime &runtime, Callable *callable) { |
1723 | | // This is not a complete definition, since ES6 and later define member |
1724 | | // functions of objects to not be constructors; however, Hermes does not have |
1725 | | // ES6 classes implemented yet, so we cannot check for that case. |
1726 | 0 | if (!callable) { |
1727 | 0 | return false; |
1728 | 0 | } |
1729 | | |
1730 | | // We traverse the BoundFunction target chain to find the eventual target. |
1731 | 0 | while (BoundFunction *b = dyn_vmcast<BoundFunction>(callable)) { |
1732 | 0 | callable = b->getTarget(runtime); |
1733 | 0 | } |
1734 | | |
1735 | | // If it is a bytecode function, check the flags. |
1736 | 0 | if (auto *func = dyn_vmcast<JSFunction>(callable)) { |
1737 | 0 | auto *cb = func->getCodeBlock(runtime); |
1738 | | // Even though it doesn't make sense logically, we need to compile the |
1739 | | // function in order to access it flags. |
1740 | 0 | if (LLVM_UNLIKELY(cb->lazyCompile(runtime) == ExecutionStatus::EXCEPTION)) { |
1741 | 0 | return ExecutionStatus::EXCEPTION; |
1742 | 0 | } |
1743 | 0 | return !func->getCodeBlock(runtime)->getHeaderFlags().isCallProhibited( |
1744 | 0 | true); |
1745 | 0 | } |
1746 | | |
1747 | | // We check for NativeFunction since those are defined to not be |
1748 | | // constructible, with the exception of NativeConstructor. |
1749 | 0 | if (!vmisa<NativeFunction>(callable) || vmisa<NativeConstructor>(callable)) { |
1750 | 0 | return true; |
1751 | 0 | } |
1752 | | |
1753 | | // JSCallableProxy is a NativeFunction, but may or may not be a |
1754 | | // constructor, so we ask it. |
1755 | 0 | if (auto *cproxy = dyn_vmcast<JSCallableProxy>(callable)) { |
1756 | 0 | return cproxy->isConstructor(runtime); |
1757 | 0 | } |
1758 | | |
1759 | 0 | return false; |
1760 | 0 | } |
1761 | | |
1762 | | CallResult<bool> |
1763 | 0 | ordinaryHasInstance(Runtime &runtime, Handle<> constructor, Handle<> object) { |
1764 | | // 1. If IsCallable(C) is false, return false. |
1765 | 0 | if (!vmisa<Callable>(*constructor)) { |
1766 | 0 | return false; |
1767 | 0 | } |
1768 | | |
1769 | 0 | Callable *ctor = vmcast<Callable>(*constructor); |
1770 | |
|
1771 | 0 | BoundFunction *bound; |
1772 | | // 2. If C has a [[BoundTargetFunction]] internal slot, then |
1773 | 0 | while (LLVM_UNLIKELY(bound = dyn_vmcast<BoundFunction>(ctor))) { |
1774 | | // 2a. Let BC be the value of C’s [[BoundTargetFunction]] internal slot. |
1775 | | // 2b. Return InstanceofOperator(O,BC) (see 12.9.4). |
1776 | | // Note that we can do this with the loop instead, |
1777 | | // because bound->getTarget() must be a Callable, and Callables cannot |
1778 | | // redefine @@hasInstance (non-configurable). |
1779 | | // Callables call this function directly from their @@hasInstance |
1780 | | // function. |
1781 | 0 | ctor = bound->getTarget(runtime); |
1782 | 0 | } |
1783 | | |
1784 | | // At this point 'ctor' is the actual function with a prototype. |
1785 | 0 | assert(ctor != nullptr && "ctor must not be null"); |
1786 | | |
1787 | | // 3. If Type(O) is not Object, return false. |
1788 | 0 | if (LLVM_UNLIKELY(!object->isObject())) { |
1789 | 0 | return false; |
1790 | 0 | } |
1791 | | |
1792 | | // 4. Let P be Get(C, "prototype"). |
1793 | 0 | auto propRes = JSObject::getNamed_RJS( |
1794 | 0 | runtime.makeHandle(ctor), |
1795 | 0 | runtime, |
1796 | 0 | Predefined::getSymbolID(Predefined::prototype)); |
1797 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
1798 | 0 | return ExecutionStatus::EXCEPTION; |
1799 | 0 | } |
1800 | | |
1801 | | // 5. If Type(P) is not Object, throw a TypeError exception. |
1802 | 0 | Handle<JSObject> ctorPrototype = runtime.makeHandle( |
1803 | 0 | PseudoHandle<JSObject>::dyn_vmcast(std::move(*propRes))); |
1804 | 0 | if (LLVM_UNLIKELY(!ctorPrototype)) { |
1805 | 0 | return runtime.raiseTypeError( |
1806 | 0 | "function's '.prototype' is not an object in 'instanceof'"); |
1807 | 0 | } |
1808 | | |
1809 | | // 6.1.7.3 Invariants of the Essential Internal Methods notes that |
1810 | | // detection of infinite prototype chains is not enforceable as an |
1811 | | // invariant if exotic objects exist in the chain. Most of the |
1812 | | // time, ScopedNativeDepthTracker will detect this. Here, we need to |
1813 | | // check that we're not repeating forever. Since ordinary object |
1814 | | // chains are verified at the time the parent is set, we count Proxy |
1815 | | // objects. Thus, any length chain of ordinary objects is ok. |
1816 | 0 | constexpr unsigned int kMaxProxyCount = 1024; |
1817 | 0 | unsigned int proxyCount = 0; |
1818 | 0 | MutableHandle<JSObject> head{runtime, vmcast<JSObject>(object.get())}; |
1819 | 0 | GCScopeMarkerRAII gcScope{runtime}; |
1820 | | // 6. Repeat |
1821 | 0 | while (true) { |
1822 | | // 6a. Let O be O.[[GetPrototypeOf]](). |
1823 | 0 | CallResult<PseudoHandle<JSObject>> parentRes = |
1824 | 0 | JSObject::getPrototypeOf(head, runtime); |
1825 | 0 | if (LLVM_UNLIKELY(parentRes == ExecutionStatus::EXCEPTION)) { |
1826 | 0 | return ExecutionStatus::EXCEPTION; |
1827 | 0 | } |
1828 | | // 6b. If O is null, return false. |
1829 | 0 | if (!*parentRes) { |
1830 | 0 | return false; |
1831 | 0 | } |
1832 | | // 6c. If SameValue(P, O) is true, return true. |
1833 | 0 | if (parentRes->get() == ctorPrototype.get()) { |
1834 | 0 | return true; |
1835 | 0 | } |
1836 | 0 | if (head->isProxyObject()) { |
1837 | 0 | ++proxyCount; |
1838 | 0 | if (proxyCount > kMaxProxyCount) { |
1839 | 0 | return runtime.raiseRangeError( |
1840 | 0 | "Maximum prototype chain length exceeded"); |
1841 | 0 | } |
1842 | 0 | } |
1843 | 0 | head = parentRes->get(); |
1844 | 0 | gcScope.flush(); |
1845 | 0 | } |
1846 | 0 | } |
1847 | | |
1848 | | CallResult<bool> instanceOfOperator_RJS( |
1849 | | Runtime &runtime, |
1850 | | Handle<> object, |
1851 | 0 | Handle<> constructor) { |
1852 | | // 1. If Type(C) is not Object, throw a TypeError exception. |
1853 | 0 | if (LLVM_UNLIKELY(!constructor->isObject())) { |
1854 | 0 | return runtime.raiseTypeError( |
1855 | 0 | "right operand of 'instanceof' is not an object"); |
1856 | 0 | } |
1857 | | |
1858 | | // Fast path: Function.prototype[Symbol.hasInstance] is non-configurable |
1859 | | // and non-writable (ES6.0 19.2.3.6), so we directly run its behavior here. |
1860 | | // Simply call through to ordinaryHasInstance. |
1861 | 0 | if (vmisa<JSFunction>(*constructor)) { |
1862 | 0 | return ordinaryHasInstance(runtime, constructor, object); |
1863 | 0 | } |
1864 | | |
1865 | | // 2. Let instOfHandler be GetMethod(C,@@hasInstance). |
1866 | 0 | CallResult<PseudoHandle<>> instOfHandlerRes = JSObject::getNamed_RJS( |
1867 | 0 | Handle<JSObject>::vmcast(constructor), |
1868 | 0 | runtime, |
1869 | 0 | Predefined::getSymbolID(Predefined::SymbolHasInstance)); |
1870 | 0 | if (LLVM_UNLIKELY(instOfHandlerRes == ExecutionStatus::EXCEPTION)) { |
1871 | 0 | return ExecutionStatus::EXCEPTION; |
1872 | 0 | } |
1873 | 0 | auto instOfHandler = runtime.makeHandle(std::move(*instOfHandlerRes)); |
1874 | | |
1875 | | // 4. If instOfHandler is not undefined, then |
1876 | 0 | if (!instOfHandler->isUndefined()) { |
1877 | | // 5. Return ToBoolean(Call(instOfHandler, C, «O»)). |
1878 | 0 | if (!vmisa<Callable>(*instOfHandler)) { |
1879 | 0 | return runtime.raiseTypeError("instanceof handler must be callable"); |
1880 | 0 | } |
1881 | 0 | auto callRes = Callable::executeCall1( |
1882 | 0 | Handle<Callable>::vmcast(instOfHandler), runtime, constructor, *object); |
1883 | 0 | if (LLVM_UNLIKELY(callRes == ExecutionStatus::EXCEPTION)) { |
1884 | 0 | return ExecutionStatus::EXCEPTION; |
1885 | 0 | } |
1886 | 0 | return toBoolean(callRes->get()); |
1887 | 0 | } |
1888 | | |
1889 | | // 6. If IsCallable(C) is false, throw a TypeError exception. |
1890 | 0 | if (!vmisa<Callable>(*constructor)) { |
1891 | 0 | return runtime.raiseTypeError( |
1892 | 0 | "right operand of 'instanceof' is not callable"); |
1893 | 0 | } |
1894 | | |
1895 | | // 7. Return OrdinaryHasInstance(C, O). |
1896 | 0 | return ordinaryHasInstance(runtime, constructor, object); |
1897 | 0 | } |
1898 | | |
1899 | | /// ES6.0 7.2.8 |
1900 | | /// Returns true if the object is a JSRegExp or has a Symbol.match property that |
1901 | | /// evaluates to true. |
1902 | 76 | CallResult<bool> isRegExp(Runtime &runtime, Handle<> arg) { |
1903 | | // 1. If Type(argument) is not Object, return false. |
1904 | 76 | if (!arg->isObject()) { |
1905 | 0 | return false; |
1906 | 0 | } |
1907 | 76 | Handle<JSObject> obj = Handle<JSObject>::vmcast(arg); |
1908 | | // 2. Let isRegExp be Get(argument, @@match). |
1909 | 76 | auto propRes = JSObject::getNamed_RJS( |
1910 | 76 | obj, runtime, Predefined::getSymbolID(Predefined::SymbolMatch)); |
1911 | | // 3. ReturnIfAbrupt(isRegExp). |
1912 | 76 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
1913 | 0 | return ExecutionStatus::EXCEPTION; |
1914 | 0 | } |
1915 | | // 4. If isRegExp is not undefined, return ToBoolean(isRegExp). |
1916 | 76 | if (!(*propRes)->isUndefined()) { |
1917 | 0 | return toBoolean(propRes->get()); |
1918 | 0 | } |
1919 | | // 5. If argument has a [[RegExpMatcher]] internal slot, return true. |
1920 | | // 6. Return false. |
1921 | 76 | return vmisa<JSRegExp>(arg.get()); |
1922 | 76 | } |
1923 | | |
1924 | | CallResult<Handle<StringPrimitive>> symbolDescriptiveString( |
1925 | | Runtime &runtime, |
1926 | 0 | Handle<SymbolID> sym) { |
1927 | | // 1. Assert: Type(sym) is Symbol. |
1928 | | // 2. Let desc be sym's [[Description]] value. |
1929 | | // 3. If desc is undefined, set desc to the empty string. |
1930 | | // 4. Assert: Type(desc) is String. |
1931 | 0 | auto desc = runtime.makeHandle<StringPrimitive>( |
1932 | 0 | runtime.getStringPrimFromSymbolID(*sym)); |
1933 | 0 | SafeUInt32 descLen(desc->getStringLength()); |
1934 | 0 | descLen.add(8); |
1935 | | |
1936 | | // 5. Return the string-concatenation of "Symbol(", desc, and ")". |
1937 | 0 | auto builder = StringBuilder::createStringBuilder(runtime, descLen); |
1938 | 0 | if (LLVM_UNLIKELY(builder == ExecutionStatus::EXCEPTION)) { |
1939 | 0 | return ExecutionStatus::EXCEPTION; |
1940 | 0 | } |
1941 | 0 | builder->appendASCIIRef({"Symbol(", 7}); |
1942 | 0 | builder->appendStringPrim(desc); |
1943 | 0 | builder->appendCharacter(')'); |
1944 | |
|
1945 | 0 | return builder->getStringPrimitive(); |
1946 | 0 | } |
1947 | | |
1948 | 0 | CallResult<bool> isArray(Runtime &runtime, JSObject *obj) { |
1949 | 0 | if (!obj) { |
1950 | 0 | return false; |
1951 | 0 | } |
1952 | 0 | while (true) { |
1953 | 0 | if (vmisa<JSArray>(obj)) { |
1954 | 0 | return true; |
1955 | 0 | } |
1956 | 0 | if (LLVM_LIKELY(!obj->isProxyObject())) { |
1957 | 0 | return false; |
1958 | 0 | } |
1959 | 0 | if (JSProxy::isRevoked(obj, runtime)) { |
1960 | 0 | return runtime.raiseTypeError("Proxy has been revoked"); |
1961 | 0 | } |
1962 | 0 | obj = JSProxy::getTarget(obj, runtime).get(); |
1963 | 0 | assert(obj && "target of non-revoked Proxy is null"); |
1964 | 0 | } |
1965 | 0 | } |
1966 | | |
1967 | 0 | CallResult<bool> isConcatSpreadable(Runtime &runtime, Handle<> value) { |
1968 | 0 | auto O = Handle<JSObject>::dyn_vmcast(value); |
1969 | 0 | if (!O) { |
1970 | 0 | return false; |
1971 | 0 | } |
1972 | | |
1973 | 0 | CallResult<PseudoHandle<>> spreadable = JSObject::getNamed_RJS( |
1974 | 0 | O, |
1975 | 0 | runtime, |
1976 | 0 | Predefined::getSymbolID(Predefined::SymbolIsConcatSpreadable)); |
1977 | 0 | if (LLVM_UNLIKELY(spreadable == ExecutionStatus::EXCEPTION)) { |
1978 | 0 | return ExecutionStatus::EXCEPTION; |
1979 | 0 | } |
1980 | | |
1981 | 0 | if (!(*spreadable)->isUndefined()) { |
1982 | 0 | return toBoolean(spreadable->get()); |
1983 | 0 | } |
1984 | | |
1985 | 0 | return isArray(runtime, *O); |
1986 | 0 | } |
1987 | | |
1988 | | ExecutionStatus toPropertyDescriptor( |
1989 | | Handle<> obj, |
1990 | | Runtime &runtime, |
1991 | | DefinePropertyFlags &flags, |
1992 | 0 | MutableHandle<> &valueOrAccessor) { |
1993 | 0 | GCScopeMarkerRAII gcMarker{runtime}; |
1994 | | |
1995 | | // Verify that the attributes argument is also an object. |
1996 | 0 | auto attributes = Handle<JSObject>::dyn_vmcast(obj); |
1997 | 0 | if (!attributes) { |
1998 | 0 | return runtime.raiseTypeError( |
1999 | 0 | "Object.defineProperty() Attributes argument is not an object"); |
2000 | 0 | } |
2001 | | |
2002 | 0 | NamedPropertyDescriptor desc; |
2003 | | |
2004 | | // Get enumerable property of the attributes. |
2005 | 0 | if (JSObject::getNamedDescriptorPredefined( |
2006 | 0 | attributes, runtime, Predefined::enumerable, desc)) { |
2007 | 0 | auto propRes = JSObject::getNamed_RJS( |
2008 | 0 | attributes, |
2009 | 0 | runtime, |
2010 | 0 | Predefined::getSymbolID(Predefined::enumerable), |
2011 | 0 | PropOpFlags().plusThrowOnError()); |
2012 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
2013 | 0 | return ExecutionStatus::EXCEPTION; |
2014 | 0 | } |
2015 | 0 | flags.enumerable = toBoolean(propRes->get()); |
2016 | 0 | flags.setEnumerable = true; |
2017 | 0 | } |
2018 | | |
2019 | | // Get configurable property of the attributes. |
2020 | 0 | if (JSObject::getNamedDescriptorPredefined( |
2021 | 0 | attributes, runtime, Predefined::configurable, desc)) { |
2022 | 0 | auto propRes = JSObject::getNamed_RJS( |
2023 | 0 | attributes, |
2024 | 0 | runtime, |
2025 | 0 | Predefined::getSymbolID(Predefined::configurable), |
2026 | 0 | PropOpFlags().plusThrowOnError()); |
2027 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
2028 | 0 | return ExecutionStatus::EXCEPTION; |
2029 | 0 | } |
2030 | 0 | flags.configurable = toBoolean(propRes->get()); |
2031 | 0 | flags.setConfigurable = true; |
2032 | 0 | } |
2033 | | |
2034 | | // Get value property of the attributes. |
2035 | 0 | if (JSObject::getNamedDescriptorPredefined( |
2036 | 0 | attributes, runtime, Predefined::value, desc)) { |
2037 | 0 | auto propRes = JSObject::getNamed_RJS( |
2038 | 0 | attributes, |
2039 | 0 | runtime, |
2040 | 0 | Predefined::getSymbolID(Predefined::value), |
2041 | 0 | PropOpFlags().plusThrowOnError()); |
2042 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
2043 | 0 | return ExecutionStatus::EXCEPTION; |
2044 | 0 | } |
2045 | 0 | valueOrAccessor = std::move(*propRes); |
2046 | 0 | flags.setValue = true; |
2047 | 0 | } |
2048 | | |
2049 | | // Get writable property of the attributes. |
2050 | 0 | if (JSObject::getNamedDescriptorPredefined( |
2051 | 0 | attributes, runtime, Predefined::writable, desc)) { |
2052 | 0 | auto propRes = JSObject::getNamed_RJS( |
2053 | 0 | attributes, |
2054 | 0 | runtime, |
2055 | 0 | Predefined::getSymbolID(Predefined::writable), |
2056 | 0 | PropOpFlags().plusThrowOnError()); |
2057 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
2058 | 0 | return ExecutionStatus::EXCEPTION; |
2059 | 0 | } |
2060 | 0 | flags.writable = toBoolean(propRes->get()); |
2061 | 0 | flags.setWritable = true; |
2062 | 0 | } |
2063 | | |
2064 | | // Get getter property of the attributes. |
2065 | 0 | MutableHandle<Callable> getterPtr{runtime}; |
2066 | 0 | if (JSObject::getNamedDescriptorPredefined( |
2067 | 0 | attributes, runtime, Predefined::get, desc)) { |
2068 | 0 | auto propRes = JSObject::getNamed_RJS( |
2069 | 0 | attributes, |
2070 | 0 | runtime, |
2071 | 0 | Predefined::getSymbolID(Predefined::get), |
2072 | 0 | PropOpFlags().plusThrowOnError()); |
2073 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
2074 | 0 | return ExecutionStatus::EXCEPTION; |
2075 | 0 | } |
2076 | 0 | flags.setGetter = true; |
2077 | 0 | PseudoHandle<> getter = std::move(*propRes); |
2078 | 0 | if (LLVM_LIKELY(!getter->isUndefined())) { |
2079 | 0 | getterPtr = dyn_vmcast<Callable>(getter.get()); |
2080 | 0 | if (LLVM_UNLIKELY(!getterPtr)) { |
2081 | 0 | return runtime.raiseTypeError( |
2082 | 0 | "Invalid property descriptor. Getter must be a function."); |
2083 | 0 | } |
2084 | 0 | } |
2085 | 0 | } |
2086 | | |
2087 | | // Get setter property of the attributes. |
2088 | 0 | MutableHandle<Callable> setterPtr{runtime}; |
2089 | 0 | if (JSObject::getNamedDescriptorPredefined( |
2090 | 0 | attributes, runtime, Predefined::set, desc)) { |
2091 | 0 | auto propRes = JSObject::getNamed_RJS( |
2092 | 0 | attributes, |
2093 | 0 | runtime, |
2094 | 0 | Predefined::getSymbolID(Predefined::set), |
2095 | 0 | PropOpFlags().plusThrowOnError()); |
2096 | 0 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
2097 | 0 | return ExecutionStatus::EXCEPTION; |
2098 | 0 | } |
2099 | 0 | flags.setSetter = true; |
2100 | 0 | PseudoHandle<> setter = std::move(*propRes); |
2101 | 0 | if (LLVM_LIKELY(!setter->isUndefined())) { |
2102 | 0 | setterPtr = PseudoHandle<Callable>::dyn_vmcast(std::move(setter)); |
2103 | 0 | if (LLVM_UNLIKELY(!setterPtr)) { |
2104 | 0 | return runtime.raiseTypeError( |
2105 | 0 | "Invalid property descriptor. Setter must be a function."); |
2106 | 0 | } |
2107 | 0 | } |
2108 | 0 | } |
2109 | | |
2110 | | // Construct property accessor if getter/setter is set. |
2111 | 0 | if (flags.setSetter || flags.setGetter) { |
2112 | 0 | if (flags.setValue) { |
2113 | 0 | return runtime.raiseTypeError( |
2114 | 0 | "Invalid property descriptor. Can't set both accessor and value."); |
2115 | 0 | } |
2116 | 0 | if (flags.setWritable) { |
2117 | 0 | return runtime.raiseTypeError( |
2118 | 0 | "Invalid property descriptor. Can't set both accessor and writable."); |
2119 | 0 | } |
2120 | 0 | valueOrAccessor = PropertyAccessor::create(runtime, getterPtr, setterPtr); |
2121 | 0 | } |
2122 | | |
2123 | 0 | return ExecutionStatus::RETURNED; |
2124 | 0 | } |
2125 | | |
2126 | | CallResult<HermesValue> objectFromPropertyDescriptor( |
2127 | | Runtime &runtime, |
2128 | | ComputedPropertyDescriptor desc, |
2129 | 0 | Handle<> valueOrAccessor) { |
2130 | 0 | Handle<JSObject> obj = runtime.makeHandle(JSObject::create(runtime)); |
2131 | |
|
2132 | 0 | DefinePropertyFlags dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); |
2133 | |
|
2134 | 0 | if (!desc.flags.accessor) { |
2135 | | // Data Descriptor |
2136 | 0 | auto result = JSObject::defineOwnProperty( |
2137 | 0 | obj, |
2138 | 0 | runtime, |
2139 | 0 | Predefined::getSymbolID(Predefined::value), |
2140 | 0 | dpf, |
2141 | 0 | valueOrAccessor, |
2142 | 0 | PropOpFlags().plusThrowOnError()); |
2143 | 0 | assert( |
2144 | 0 | result != ExecutionStatus::EXCEPTION && |
2145 | 0 | "defineOwnProperty() failed on a new object"); |
2146 | 0 | if (result == ExecutionStatus::EXCEPTION) { |
2147 | 0 | return ExecutionStatus::EXCEPTION; |
2148 | 0 | } |
2149 | | |
2150 | 0 | result = JSObject::defineOwnProperty( |
2151 | 0 | obj, |
2152 | 0 | runtime, |
2153 | 0 | Predefined::getSymbolID(Predefined::writable), |
2154 | 0 | dpf, |
2155 | 0 | Runtime::getBoolValue(desc.flags.writable), |
2156 | 0 | PropOpFlags().plusThrowOnError()); |
2157 | 0 | assert( |
2158 | 0 | result != ExecutionStatus::EXCEPTION && |
2159 | 0 | "defineOwnProperty() failed on a new object"); |
2160 | 0 | if (result == ExecutionStatus::EXCEPTION) { |
2161 | 0 | return ExecutionStatus::EXCEPTION; |
2162 | 0 | } |
2163 | 0 | } else { |
2164 | | // Accessor |
2165 | 0 | auto *accessor = vmcast<PropertyAccessor>(valueOrAccessor.get()); |
2166 | |
|
2167 | 0 | auto getter = runtime.makeHandle( |
2168 | 0 | accessor->getter ? HermesValue::encodeObjectValue( |
2169 | 0 | accessor->getter.getNonNull(runtime)) |
2170 | 0 | : HermesValue::encodeUndefinedValue()); |
2171 | |
|
2172 | 0 | auto setter = runtime.makeHandle( |
2173 | 0 | accessor->setter ? HermesValue::encodeObjectValue( |
2174 | 0 | accessor->setter.getNonNull(runtime)) |
2175 | 0 | : HermesValue::encodeUndefinedValue()); |
2176 | |
|
2177 | 0 | auto result = JSObject::defineOwnProperty( |
2178 | 0 | obj, |
2179 | 0 | runtime, |
2180 | 0 | Predefined::getSymbolID(Predefined::get), |
2181 | 0 | dpf, |
2182 | 0 | getter, |
2183 | 0 | PropOpFlags().plusThrowOnError()); |
2184 | 0 | assert( |
2185 | 0 | result != ExecutionStatus::EXCEPTION && |
2186 | 0 | "defineOwnProperty() failed on a new object"); |
2187 | 0 | if (result == ExecutionStatus::EXCEPTION) { |
2188 | 0 | return ExecutionStatus::EXCEPTION; |
2189 | 0 | } |
2190 | | |
2191 | 0 | result = JSObject::defineOwnProperty( |
2192 | 0 | obj, |
2193 | 0 | runtime, |
2194 | 0 | Predefined::getSymbolID(Predefined::set), |
2195 | 0 | dpf, |
2196 | 0 | setter, |
2197 | 0 | PropOpFlags().plusThrowOnError()); |
2198 | 0 | assert( |
2199 | 0 | result != ExecutionStatus::EXCEPTION && |
2200 | 0 | "defineOwnProperty() failed on a new object"); |
2201 | 0 | if (result == ExecutionStatus::EXCEPTION) { |
2202 | 0 | return ExecutionStatus::EXCEPTION; |
2203 | 0 | } |
2204 | 0 | } |
2205 | | |
2206 | 0 | auto result = JSObject::defineOwnProperty( |
2207 | 0 | obj, |
2208 | 0 | runtime, |
2209 | 0 | Predefined::getSymbolID(Predefined::enumerable), |
2210 | 0 | dpf, |
2211 | 0 | Runtime::getBoolValue(desc.flags.enumerable), |
2212 | 0 | PropOpFlags().plusThrowOnError()); |
2213 | 0 | assert( |
2214 | 0 | result != ExecutionStatus::EXCEPTION && |
2215 | 0 | "defineOwnProperty() failed on a new object"); |
2216 | 0 | if (result == ExecutionStatus::EXCEPTION) { |
2217 | 0 | return ExecutionStatus::EXCEPTION; |
2218 | 0 | } |
2219 | | |
2220 | 0 | result = JSObject::defineOwnProperty( |
2221 | 0 | obj, |
2222 | 0 | runtime, |
2223 | 0 | Predefined::getSymbolID(Predefined::configurable), |
2224 | 0 | dpf, |
2225 | 0 | Runtime::getBoolValue(desc.flags.configurable), |
2226 | 0 | PropOpFlags().plusThrowOnError()); |
2227 | 0 | assert( |
2228 | 0 | result != ExecutionStatus::EXCEPTION && |
2229 | 0 | "defineOwnProperty() failed on a new object"); |
2230 | 0 | if (result == ExecutionStatus::EXCEPTION) { |
2231 | 0 | return ExecutionStatus::EXCEPTION; |
2232 | 0 | } |
2233 | | |
2234 | 0 | return obj.getHermesValue(); |
2235 | 0 | } |
2236 | | |
2237 | 0 | CallResult<HermesValue> numberToBigInt(Runtime &runtime, double number) { |
2238 | 0 | if (!isIntegralNumber(number)) { |
2239 | 0 | return runtime.raiseRangeError("number is not integral"); |
2240 | 0 | } |
2241 | | |
2242 | 0 | return BigIntPrimitive::fromDouble(runtime, number); |
2243 | 0 | } |
2244 | | |
2245 | 0 | bool isIntegralNumber(double number) { |
2246 | | // 1. if Type(argument) is not Number, return false |
2247 | | // it is a number |
2248 | | |
2249 | | // 2. if argument is NaN, +inf, -inf, return false |
2250 | 0 | if (std::isnan(number) || number == std::numeric_limits<double>::infinity() || |
2251 | 0 | number == -std::numeric_limits<double>::infinity()) { |
2252 | 0 | return false; |
2253 | 0 | } |
2254 | | |
2255 | | // 3. if floor(abs(R(argument))) != abs(R(argument)) return false |
2256 | 0 | if (std::floor(std::abs(number)) != std::abs(number)) { |
2257 | 0 | return false; |
2258 | 0 | } |
2259 | | |
2260 | | // 4. return true |
2261 | 0 | return true; |
2262 | 0 | } |
2263 | | |
2264 | 0 | CallResult<HermesValue> toBigInt_RJS(Runtime &runtime, Handle<> value) { |
2265 | 0 | auto prim = toPrimitive_RJS(runtime, value, PreferredType::NUMBER); |
2266 | 0 | if (LLVM_UNLIKELY(prim == ExecutionStatus::EXCEPTION)) { |
2267 | 0 | return ExecutionStatus::EXCEPTION; |
2268 | 0 | } |
2269 | | |
2270 | 0 | switch (prim->getETag()) { |
2271 | 0 | default: |
2272 | 0 | break; |
2273 | 0 | case HermesValue::ETag::Undefined: |
2274 | 0 | return runtime.raiseTypeError("invalid argument to BigInt()"); |
2275 | 0 | case HermesValue::ETag::Null: |
2276 | 0 | return runtime.raiseTypeError("invalid argument to BigInt()"); |
2277 | 0 | case HermesValue::ETag::Bool: |
2278 | 0 | return BigIntPrimitive::fromSigned(runtime, prim->getBool() ? 1 : 0); |
2279 | 0 | case HermesValue::ETag::BigInt1: |
2280 | 0 | case HermesValue::ETag::BigInt2: |
2281 | 0 | return *prim; |
2282 | 0 | case HermesValue::ETag::Str1: |
2283 | 0 | case HermesValue::ETag::Str2: { |
2284 | 0 | auto n = stringToBigInt(runtime, runtime.makeHandle(*prim)); |
2285 | 0 | if (LLVM_UNLIKELY(n == ExecutionStatus::EXCEPTION)) { |
2286 | 0 | return ExecutionStatus::EXCEPTION; |
2287 | 0 | } |
2288 | 0 | if (n->isUndefined()) { |
2289 | 0 | return runtime.raiseSyntaxError("can't convert string to bigint"); |
2290 | 0 | } |
2291 | 0 | return *n; |
2292 | 0 | } |
2293 | 0 | case HermesValue::ETag::Symbol: |
2294 | 0 | return runtime.raiseTypeError("invalid argument to BigInt()"); |
2295 | 0 | } |
2296 | | |
2297 | 0 | return runtime.raiseTypeError("invalid argument to BigInt()"); |
2298 | 0 | } |
2299 | | |
2300 | 0 | CallResult<HermesValue> stringToBigInt(Runtime &runtime, Handle<> value) { |
2301 | 0 | if (value->isString()) { |
2302 | 0 | auto str = value->getString(); |
2303 | |
|
2304 | 0 | std::string outError; |
2305 | 0 | auto parsedBigInt = str->isASCII() |
2306 | 0 | ? bigint::ParsedBigInt::parsedBigIntFromStringIntegerLiteral( |
2307 | 0 | str->getStringRef<char>(), &outError) |
2308 | 0 | : bigint::ParsedBigInt::parsedBigIntFromStringIntegerLiteral( |
2309 | 0 | str->getStringRef<char16_t>(), &outError); |
2310 | 0 | if (!parsedBigInt) { |
2311 | 0 | return HermesValue::encodeUndefinedValue(); |
2312 | 0 | } |
2313 | | |
2314 | 0 | return BigIntPrimitive::fromBytes(runtime, parsedBigInt->getBytes()); |
2315 | 0 | } |
2316 | | |
2317 | 0 | return runtime.raiseTypeError("Invalid argument to stringToBigInt"); |
2318 | 0 | } |
2319 | | |
2320 | 0 | CallResult<HermesValue> thisBigIntValue(Runtime &runtime, Handle<> value) { |
2321 | 0 | if (value->isBigInt()) |
2322 | 0 | return *value; |
2323 | 0 | if (auto *jsBigInt = dyn_vmcast<JSBigInt>(*value)) |
2324 | 0 | return HermesValue::encodeBigIntValue( |
2325 | 0 | JSBigInt::getPrimitiveBigInt(jsBigInt, runtime)); |
2326 | 0 | return runtime.raiseTypeError("value is not a bigint"); |
2327 | 0 | } |
2328 | | |
2329 | 0 | bool hasRestrictedGlobalProperty(Runtime &runtime, SymbolID N) { |
2330 | 0 | Handle<JSObject> globalObject = runtime.getGlobal(); |
2331 | | |
2332 | | // 1. Let ObjRec be envRec.[[ObjectRecord]]. |
2333 | | // 2. Let globalObject be ObjRec.[[BindingObject]]. |
2334 | | |
2335 | | // 3. Let existingProp be ? globalObject.[[GetOwnProperty]](N). |
2336 | 0 | NamedPropertyDescriptor desc; |
2337 | 0 | JSObject *existingProp = |
2338 | 0 | JSObject::getNamedDescriptorUnsafe(globalObject, runtime, N, desc); |
2339 | | |
2340 | | // 4. If existingProp is undefined, return false. |
2341 | 0 | if (!existingProp) { |
2342 | 0 | return false; |
2343 | 0 | } |
2344 | | |
2345 | | // 5. If existingProp.[[Configurable]] is true, return false. |
2346 | | // 6. Return true. |
2347 | 0 | return !desc.flags.configurable; |
2348 | 0 | } |
2349 | | } // namespace vm |
2350 | | } // namespace hermes |