/src/hermes/lib/VM/JSLib/JSLibInternal.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 "JSLibInternal.h" |
9 | | |
10 | | #include "hermes/Regex/Executor.h" |
11 | | #include "hermes/Regex/Regex.h" |
12 | | #include "hermes/Regex/RegexTraits.h" |
13 | | #include "hermes/VM/PropertyAccessor.h" |
14 | | #include "hermes/VM/Runtime.h" |
15 | | #include "hermes/VM/StringBuilder.h" |
16 | | #include "hermes/VM/StringPrimitive.h" |
17 | | #include "hermes/VM/StringView.h" |
18 | | #pragma GCC diagnostic push |
19 | | |
20 | | #ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32 |
21 | | #pragma GCC diagnostic ignored "-Wshorten-64-to-32" |
22 | | #endif |
23 | | namespace hermes { |
24 | | namespace vm { |
25 | | |
26 | | Handle<NativeConstructor> defineSystemConstructor( |
27 | | Runtime &runtime, |
28 | | SymbolID name, |
29 | | NativeFunctionPtr nativeFunctionPtr, |
30 | | Handle<JSObject> prototypeObjectHandle, |
31 | | Handle<JSObject> constructorProtoObjectHandle, |
32 | | unsigned paramCount, |
33 | | NativeConstructor::CreatorFunction *creator, |
34 | 6.24k | CellKind targetKind) { |
35 | 6.24k | auto constructor = runtime.makeHandle(NativeConstructor::create( |
36 | 6.24k | runtime, |
37 | 6.24k | constructorProtoObjectHandle, |
38 | 6.24k | nullptr, |
39 | 6.24k | nativeFunctionPtr, |
40 | 6.24k | paramCount, |
41 | 6.24k | creator, |
42 | 6.24k | targetKind)); |
43 | | |
44 | 6.24k | auto st = Callable::defineNameLengthAndPrototype( |
45 | 6.24k | constructor, |
46 | 6.24k | runtime, |
47 | 6.24k | name, |
48 | 6.24k | paramCount, |
49 | 6.24k | prototypeObjectHandle, |
50 | 6.24k | Callable::WritablePrototype::No, |
51 | 6.24k | false); |
52 | 6.24k | (void)st; |
53 | 6.24k | assert( |
54 | 6.24k | st != ExecutionStatus::EXCEPTION && "defineLengthAndPrototype() failed"); |
55 | | |
56 | | // Define the global. |
57 | 6.24k | DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); |
58 | | |
59 | 6.24k | auto res = JSObject::defineOwnProperty( |
60 | 6.24k | runtime.getGlobal(), runtime, name, dpf, constructor); |
61 | 6.24k | assert( |
62 | 6.24k | res != ExecutionStatus::EXCEPTION && *res && |
63 | 6.24k | "defineOwnProperty() failed"); |
64 | 6.24k | (void)res; |
65 | | |
66 | 6.24k | return constructor; |
67 | 6.24k | } |
68 | | |
69 | | CallResult<HermesValue> defineMethod( |
70 | | Runtime &runtime, |
71 | | Handle<JSObject> objectHandle, |
72 | | SymbolID propertyName, |
73 | | SymbolID methodName, |
74 | | void *context, |
75 | | NativeFunctionPtr nativeFunctionPtr, |
76 | | unsigned paramCount, |
77 | 57.6k | DefinePropertyFlags dpf) { |
78 | 57.6k | GCScope gcScope{runtime}; |
79 | | |
80 | 57.6k | auto method = NativeFunction::create( |
81 | 57.6k | runtime, |
82 | 57.6k | Handle<JSObject>::vmcast(&runtime.functionPrototype), |
83 | 57.6k | context, |
84 | 57.6k | nativeFunctionPtr, |
85 | 57.6k | methodName, |
86 | 57.6k | paramCount, |
87 | 57.6k | Runtime::makeNullHandle<JSObject>()); |
88 | | |
89 | 57.6k | auto res = JSObject::defineOwnProperty( |
90 | 57.6k | objectHandle, runtime, propertyName, dpf, method); |
91 | 57.6k | (void)res; |
92 | 57.6k | assert( |
93 | 57.6k | res != ExecutionStatus::EXCEPTION && *res && |
94 | 57.6k | "defineOwnProperty() failed"); |
95 | | |
96 | 57.6k | return method.getHermesValue(); |
97 | 57.6k | } |
98 | | |
99 | | Handle<NativeConstructor> defineSystemConstructor( |
100 | | Runtime &runtime, |
101 | | SymbolID name, |
102 | | NativeFunctionPtr nativeFunctionPtr, |
103 | | Handle<JSObject> prototypeObjectHandle, |
104 | | unsigned paramCount, |
105 | | NativeConstructor::CreatorFunction *creator, |
106 | 3.04k | CellKind targetKind) { |
107 | 3.04k | return defineSystemConstructor( |
108 | 3.04k | runtime, |
109 | 3.04k | name, |
110 | 3.04k | nativeFunctionPtr, |
111 | 3.04k | prototypeObjectHandle, |
112 | 3.04k | Handle<JSObject>::vmcast(&runtime.functionPrototype), |
113 | 3.04k | paramCount, |
114 | 3.04k | creator, |
115 | 3.04k | targetKind); |
116 | 3.04k | } |
117 | | |
118 | | void defineMethod( |
119 | | Runtime &runtime, |
120 | | Handle<JSObject> objectHandle, |
121 | | SymbolID name, |
122 | | void *context, |
123 | | NativeFunctionPtr nativeFunctionPtr, |
124 | 53.9k | unsigned paramCount) { |
125 | 53.9k | DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); |
126 | 53.9k | (void)defineMethod( |
127 | 53.9k | runtime, objectHandle, name, context, nativeFunctionPtr, paramCount, dpf); |
128 | 53.9k | } |
129 | | |
130 | | void defineAccessor( |
131 | | Runtime &runtime, |
132 | | Handle<JSObject> objectHandle, |
133 | | SymbolID propertyName, |
134 | | SymbolID methodName, |
135 | | void *context, |
136 | | NativeFunctionPtr getterFunc, |
137 | | NativeFunctionPtr setterFunc, |
138 | | bool enumerable, |
139 | 7.04k | bool configurable) { |
140 | 7.04k | assert( |
141 | 7.04k | (getterFunc || setterFunc) && |
142 | 7.04k | "at least a getter or a setter must be specified"); |
143 | 7.04k | ExecutionStatus status{}; |
144 | 7.04k | (void)status; |
145 | | |
146 | 7.04k | GCScope gcScope{runtime}; |
147 | | |
148 | 7.04k | StringView nameView = |
149 | 7.04k | runtime.getIdentifierTable().getStringView(runtime, methodName); |
150 | 7.04k | assert(nameView.isASCII() && "Only ASCII accessors are supported"); |
151 | | |
152 | 7.04k | MutableHandle<NativeFunction> getter{runtime}; |
153 | 7.04k | if (getterFunc) { |
154 | | // Set the name by prepending "get ". |
155 | 7.04k | llvh::SmallString<32> getterName{"get "}; |
156 | 7.04k | llvh::raw_svector_ostream os{getterName}; |
157 | 7.04k | os << nameView; |
158 | | |
159 | 7.04k | auto strRes = runtime.ignoreAllocationFailure( |
160 | 7.04k | StringPrimitive::create(runtime, getterName)); |
161 | 7.04k | SymbolID getterFuncName = |
162 | 7.04k | runtime |
163 | 7.04k | .ignoreAllocationFailure( |
164 | 7.04k | runtime.getIdentifierTable().getSymbolHandleFromPrimitive( |
165 | 7.04k | runtime, |
166 | 7.04k | createPseudoHandle(vmcast<StringPrimitive>(strRes)))) |
167 | 7.04k | .get(); |
168 | | |
169 | 7.04k | auto funcRes = NativeFunction::create( |
170 | 7.04k | runtime, |
171 | 7.04k | Handle<JSObject>::vmcast(&runtime.functionPrototype), |
172 | 7.04k | context, |
173 | 7.04k | getterFunc, |
174 | 7.04k | getterFuncName, |
175 | 7.04k | 0, |
176 | 7.04k | Runtime::makeNullHandle<JSObject>()); |
177 | 7.04k | getter = funcRes.get(); |
178 | 7.04k | } |
179 | | |
180 | 7.04k | MutableHandle<NativeFunction> setter{runtime}; |
181 | 7.04k | if (setterFunc) { |
182 | | // Set the name by prepending "set ". |
183 | 160 | llvh::SmallString<32> setterName{"set "}; |
184 | 160 | llvh::raw_svector_ostream os{setterName}; |
185 | 160 | os << nameView; |
186 | | |
187 | 160 | auto strRes = runtime.ignoreAllocationFailure( |
188 | 160 | StringPrimitive::create(runtime, setterName)); |
189 | 160 | SymbolID setterFuncName = |
190 | 160 | runtime |
191 | 160 | .ignoreAllocationFailure( |
192 | 160 | runtime.getIdentifierTable().getSymbolHandleFromPrimitive( |
193 | 160 | runtime, |
194 | 160 | createPseudoHandle(vmcast<StringPrimitive>(strRes)))) |
195 | 160 | .get(); |
196 | | |
197 | 160 | auto funcRes = NativeFunction::create( |
198 | 160 | runtime, |
199 | 160 | Handle<JSObject>::vmcast(&runtime.functionPrototype), |
200 | 160 | context, |
201 | 160 | setterFunc, |
202 | 160 | setterFuncName, |
203 | 160 | 1, |
204 | 160 | Runtime::makeNullHandle<JSObject>()); |
205 | 160 | setter = funcRes.get(); |
206 | 160 | } |
207 | | |
208 | 7.04k | auto accessor = runtime.makeHandle<PropertyAccessor>( |
209 | 7.04k | PropertyAccessor::create(runtime, getter, setter)); |
210 | | |
211 | 7.04k | DefinePropertyFlags dpf{}; |
212 | 7.04k | dpf.setEnumerable = 1; |
213 | 7.04k | dpf.setConfigurable = 1; |
214 | 7.04k | dpf.setGetter = 1; |
215 | 7.04k | dpf.setSetter = 1; |
216 | 7.04k | dpf.enumerable = enumerable; |
217 | 7.04k | dpf.configurable = configurable; |
218 | | |
219 | 7.04k | auto res = JSObject::defineOwnProperty( |
220 | 7.04k | objectHandle, runtime, propertyName, dpf, accessor); |
221 | 7.04k | (void)res; |
222 | 7.04k | assert( |
223 | 7.04k | res != ExecutionStatus::EXCEPTION && *res && |
224 | 7.04k | "defineOwnProperty() failed"); |
225 | 7.04k | } |
226 | | |
227 | | void defineProperty( |
228 | | Runtime &runtime, |
229 | | Handle<JSObject> objectHandle, |
230 | | SymbolID name, |
231 | | Handle<> value, |
232 | 13.6k | DefinePropertyFlags dpf) { |
233 | 13.6k | auto res = JSObject::defineOwnProperty( |
234 | 13.6k | objectHandle, runtime, name, dpf, value, PropOpFlags()); |
235 | 13.6k | (void)res; |
236 | 13.6k | assert( |
237 | 13.6k | res != ExecutionStatus::EXCEPTION && *res && |
238 | 13.6k | "defineOwnProperty() failed"); |
239 | 13.6k | } |
240 | | |
241 | | void defineProperty( |
242 | | Runtime &runtime, |
243 | | Handle<JSObject> objectHandle, |
244 | | SymbolID name, |
245 | 4.48k | Handle<> value) { |
246 | 4.48k | DefinePropertyFlags dpf = DefinePropertyFlags::getNewNonEnumerableFlags(); |
247 | | |
248 | 4.48k | return defineProperty(runtime, objectHandle, name, value, dpf); |
249 | 4.48k | } |
250 | | |
251 | | ExecutionStatus iteratorCloseAndRethrow( |
252 | | Runtime &runtime, |
253 | 0 | Handle<JSObject> iterator) { |
254 | 0 | auto completion = runtime.makeHandle(runtime.getThrownValue()); |
255 | 0 | if (isUncatchableError(completion.getHermesValue())) { |
256 | | // If an uncatchable exception was raised, do not swallow it, but instead |
257 | | // propagate it. |
258 | 0 | return ExecutionStatus::EXCEPTION; |
259 | 0 | } |
260 | 0 | runtime.clearThrownValue(); |
261 | 0 | auto status = iteratorClose(runtime, iterator, completion); |
262 | 0 | (void)status; |
263 | 0 | assert( |
264 | 0 | status == ExecutionStatus::EXCEPTION && "exception swallowed mistakenly"); |
265 | 0 | return ExecutionStatus::EXCEPTION; |
266 | 0 | } |
267 | | |
268 | 1 | static std::vector<uint8_t> getReturnThisRegexBytecode() { |
269 | 1 | const char16_t *returnThisRE = uR"X(^\s*return[ \t]+this\s*;?\s*$)X"; |
270 | 1 | return regex::Regex<regex::UTF16RegexTraits>(returnThisRE).compile(); |
271 | 1 | } |
272 | | |
273 | 86 | static bool isReturnThis(Handle<StringPrimitive> str, Runtime &runtime) { |
274 | | // Fast check for minimal version. |
275 | 86 | { |
276 | 86 | auto minimal = runtime.getPredefinedString(Predefined::returnThis); |
277 | 86 | if (str->equals(minimal)) { |
278 | 0 | return true; |
279 | 0 | } |
280 | 86 | } |
281 | | // Regex match for variants. |
282 | 86 | auto input = StringPrimitive::createStringView(runtime, str); |
283 | 86 | static auto bytecode = getReturnThisRegexBytecode(); |
284 | 86 | auto result = regex::MatchRuntimeResult::NoMatch; |
285 | 86 | if (input.isASCII()) { |
286 | 0 | const char *begin = input.castToCharPtr(); |
287 | 0 | result = regex::searchWithBytecode( |
288 | 0 | bytecode, |
289 | 0 | begin, |
290 | 0 | 0, |
291 | 0 | input.length(), |
292 | 0 | nullptr, |
293 | 0 | regex::constants::matchDefault | regex::constants::matchInputAllAscii, |
294 | 0 | runtime.getOverflowGuardForRegex()); |
295 | 86 | } else { |
296 | 86 | const char16_t *begin = input.castToChar16Ptr(); |
297 | 86 | result = regex::searchWithBytecode( |
298 | 86 | bytecode, |
299 | 86 | begin, |
300 | 86 | 0, |
301 | 86 | input.length(), |
302 | 86 | nullptr, |
303 | 86 | regex::constants::matchDefault, |
304 | 86 | runtime.getOverflowGuardForRegex()); |
305 | 86 | } |
306 | 86 | return result == regex::MatchRuntimeResult::Match; |
307 | 86 | } |
308 | | |
309 | | CallResult<HermesValue> createDynamicFunction( |
310 | | Runtime &runtime, |
311 | | NativeArgs args, |
312 | 87 | DynamicFunctionKind kind) { |
313 | 87 | GCScope gcScope(runtime); |
314 | | |
315 | | // Number of arguments supplied to Function(). |
316 | 87 | uint32_t argCount = args.getArgCount(); |
317 | | |
318 | | // Number of parameters in the resultant function. |
319 | 87 | uint32_t paramCount = argCount > 0 ? argCount - 1 : 0; |
320 | | |
321 | | // List of the parameter strings for the resultant function.. |
322 | 87 | auto arrRes = JSArray::create(runtime, paramCount, paramCount); |
323 | 87 | if (LLVM_UNLIKELY(arrRes == ExecutionStatus::EXCEPTION)) { |
324 | 0 | return ExecutionStatus::EXCEPTION; |
325 | 0 | } |
326 | 87 | auto params = *arrRes; |
327 | | |
328 | | // Body of the resultant function. |
329 | 87 | MutableHandle<StringPrimitive> body{runtime}; |
330 | | |
331 | | // String length of the function in its entirety. |
332 | | // Account for commas in the argument list initially. |
333 | | // If at least two arguments to the function (3 in total), there's a comma. |
334 | 87 | SafeUInt32 size{paramCount > 0 ? paramCount - 1 : 0}; |
335 | | |
336 | | // es2020 19.2.1.1.1 Runtime Semantics: CreateDynamicFunction: If |
337 | | // NewTarget is given, such as with Reflect.construct, use |
338 | | // NewParent.prototype as the parent. The prototype on NewParent |
339 | | // has already been looked up to use as the parent of 'this', so |
340 | | // instead of looking it up again, just use this's parent. If |
341 | | // NewTarget isn't given, fall back to a default. |
342 | 87 | MutableHandle<JSObject> fallbackProto{runtime}; |
343 | 87 | switch (kind) { |
344 | 87 | case DynamicFunctionKind::Normal: |
345 | 87 | fallbackProto = Handle<JSObject>::vmcast(&runtime.functionPrototype); |
346 | 87 | break; |
347 | 0 | case DynamicFunctionKind::Generator: |
348 | 0 | fallbackProto = |
349 | 0 | Handle<JSObject>::vmcast(&runtime.generatorFunctionPrototype); |
350 | 0 | break; |
351 | 0 | case DynamicFunctionKind::Async: |
352 | 0 | fallbackProto = Handle<JSObject>::vmcast(&runtime.asyncFunctionPrototype); |
353 | 0 | break; |
354 | 0 | default: |
355 | 0 | llvm_unreachable("unknown kind for CreateDynamicFunction."); |
356 | 87 | } |
357 | | |
358 | 87 | Handle<JSObject> parent = !args.getNewTarget().isUndefined() |
359 | 87 | ? runtime.makeHandle( |
360 | 0 | vmcast<JSObject>(args.getThisArg())->getParent(runtime)) |
361 | 87 | : fallbackProto; |
362 | | |
363 | 87 | if (argCount == 0) { |
364 | | // No arguments, just set body to be the empty string. |
365 | 0 | body = runtime.getPredefinedString(Predefined::emptyString); |
366 | 87 | } else { |
367 | | // If there's arguments, store the parameters and the provided body. |
368 | 87 | auto marker = gcScope.createMarker(); |
369 | 13.3k | for (uint32_t i = 0; i < paramCount; ++i) { |
370 | 13.2k | gcScope.flushToMarker(marker); |
371 | 13.2k | auto strRes = toString_RJS(runtime, args.getArgHandle(i)); |
372 | 13.2k | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
373 | 0 | return ExecutionStatus::EXCEPTION; |
374 | 0 | } |
375 | 13.2k | auto param = runtime.makeHandle(std::move(*strRes)); |
376 | 13.2k | JSArray::setElementAt(params, runtime, i, param); |
377 | 13.2k | size.add(param->getStringLength()); |
378 | 13.2k | } |
379 | | |
380 | | // Last parameter is the body. |
381 | 87 | auto strRes = toString_RJS(runtime, args.getArgHandle(paramCount)); |
382 | 87 | if (strRes == ExecutionStatus::EXCEPTION) { |
383 | 0 | return ExecutionStatus::EXCEPTION; |
384 | 0 | } |
385 | 87 | body = strRes->get(); |
386 | 87 | size.add(body->getStringLength()); |
387 | | |
388 | 87 | if (kind == DynamicFunctionKind::Normal && argCount == 1 && |
389 | 87 | isReturnThis(body, runtime)) { |
390 | | // If this raises an exception, we still return immediately. |
391 | 0 | return JSFunction::create( |
392 | 0 | runtime, |
393 | 0 | runtime.makeHandle(Domain::create(runtime)), |
394 | 0 | parent, |
395 | 0 | Handle<Environment>(runtime, nullptr), |
396 | 0 | runtime.getReturnThisCodeBlock()) |
397 | 0 | .getHermesValue(); |
398 | 0 | } |
399 | 87 | } |
400 | | |
401 | | // Constant parts of the function. |
402 | 87 | ASCIIRef functionHeader; |
403 | 87 | switch (kind) { |
404 | 87 | case DynamicFunctionKind::Normal: |
405 | 87 | functionHeader = createASCIIRef("(function ("); |
406 | 87 | break; |
407 | 0 | case DynamicFunctionKind::Generator: |
408 | 0 | functionHeader = createASCIIRef("(function*("); |
409 | 0 | break; |
410 | 0 | case DynamicFunctionKind::Async: |
411 | 0 | functionHeader = createASCIIRef("(async function ("); |
412 | 0 | break; |
413 | 0 | default: |
414 | 0 | llvm_unreachable("unknown kind for CreateDynamicFunction."); |
415 | 87 | }; |
416 | 87 | size.add(functionHeader.size()); |
417 | | |
418 | 87 | auto bodyHeader = createASCIIRef("){"); |
419 | 87 | size.add(bodyHeader.size()); |
420 | | |
421 | | // Note: add a \n before the closing '}' in case the |
422 | | // function body ends with a line comment. |
423 | 87 | auto functionFooter = createASCIIRef("\n})"); |
424 | 87 | size.add(functionFooter.size()); |
425 | | |
426 | 87 | auto separator = createASCIIRef(","); |
427 | | |
428 | 87 | auto builder = StringBuilder::createStringBuilder(runtime, size); |
429 | 87 | if (builder == ExecutionStatus::EXCEPTION) { |
430 | 0 | return ExecutionStatus::EXCEPTION; |
431 | 0 | } |
432 | 87 | builder->appendASCIIRef(functionHeader); |
433 | 87 | MutableHandle<StringPrimitive> element{runtime}; |
434 | 13.3k | for (uint32_t i = 0; i < paramCount; ++i) { |
435 | | // Copy params into str. |
436 | 13.2k | element = params->at(runtime, i).getString(runtime); |
437 | 13.2k | builder->appendStringPrim(element); |
438 | 13.2k | if (i < paramCount - 1) { |
439 | | // If there's more params left to put, need to add a comma. |
440 | | // We wouldn't have entered the loop if paramCount == 0, |
441 | | // so there's no overflow here. |
442 | 13.2k | builder->appendASCIIRef(separator); |
443 | 13.2k | } |
444 | 13.2k | } |
445 | 87 | builder->appendASCIIRef(bodyHeader); |
446 | 87 | builder->appendStringPrim(body); |
447 | 87 | builder->appendASCIIRef(functionFooter); |
448 | | |
449 | 87 | auto evalRes = |
450 | 87 | directEval(runtime, builder->getStringPrimitive(), {}, false, true); |
451 | 87 | if (evalRes == ExecutionStatus::EXCEPTION) { |
452 | 5 | return ExecutionStatus::EXCEPTION; |
453 | 5 | } |
454 | | |
455 | 82 | Handle<JSFunction> function = |
456 | 82 | runtime.makeHandle(vmcast<JSFunction>(evalRes.getValue())); |
457 | | |
458 | 82 | DefinePropertyFlags dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); |
459 | 82 | dpf.enumerable = 0; |
460 | 82 | dpf.writable = 0; |
461 | | |
462 | | // Define the `name` correctly. |
463 | 82 | if (JSObject::defineOwnProperty( |
464 | 82 | function, |
465 | 82 | runtime, |
466 | 82 | Predefined::getSymbolID(Predefined::name), |
467 | 82 | dpf, |
468 | 82 | runtime.makeHandle(runtime.getStringPrimFromSymbolID( |
469 | 82 | Predefined::getSymbolID(Predefined::anonymous)))) == |
470 | 82 | ExecutionStatus::EXCEPTION) { |
471 | 0 | return ExecutionStatus::EXCEPTION; |
472 | 0 | } |
473 | | |
474 | | // Set the parent. This could be done by threading the argument |
475 | | // through to Runtime::runBytecode where the object is actually |
476 | | // created, but this is the only place we need to do this so it |
477 | | // keeps the code simpler. |
478 | 82 | CallResult<bool> parentRes = JSObject::setParent(*function, runtime, *parent); |
479 | 82 | if (LLVM_UNLIKELY(parentRes == ExecutionStatus::EXCEPTION)) { |
480 | 0 | return ExecutionStatus::EXCEPTION; |
481 | 0 | } |
482 | 82 | assert( |
483 | 82 | *parentRes && "Setting prototype on new dynamic function returned false"); |
484 | | |
485 | 82 | return function.getHermesValue(); |
486 | 82 | } |
487 | | |
488 | | } // namespace vm |
489 | | } // namespace hermes |