/src/hermes/lib/VM/JSLib/Function.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 | | //===----------------------------------------------------------------------===// |
9 | | /// \file |
10 | | /// ES5.1 15.3 Initialize the Function constructor. |
11 | | //===----------------------------------------------------------------------===// |
12 | | #include "JSLibInternal.h" |
13 | | |
14 | | #include "hermes/Regex/Executor.h" |
15 | | #include "hermes/Regex/RegexTraits.h" |
16 | | #include "hermes/VM/ArrayLike.h" |
17 | | #include "hermes/VM/Callable.h" |
18 | | #include "hermes/VM/Operations.h" |
19 | | #include "hermes/VM/StringBuilder.h" |
20 | | #include "hermes/VM/StringView.h" |
21 | | |
22 | | namespace hermes { |
23 | | namespace vm { |
24 | | |
25 | | //===----------------------------------------------------------------------===// |
26 | | /// Function. |
27 | | |
28 | 93 | Handle<JSObject> createFunctionConstructor(Runtime &runtime) { |
29 | 93 | auto functionPrototype = Handle<Callable>::vmcast(&runtime.functionPrototype); |
30 | | |
31 | 93 | auto cons = defineSystemConstructor<JSFunction>( |
32 | 93 | runtime, |
33 | 93 | Predefined::getSymbolID(Predefined::Function), |
34 | 93 | functionConstructor, |
35 | 93 | functionPrototype, |
36 | 93 | 1, |
37 | 93 | CellKind::JSFunctionKind); |
38 | | |
39 | | // Function.prototype.xxx() methods. |
40 | 93 | defineMethod( |
41 | 93 | runtime, |
42 | 93 | functionPrototype, |
43 | 93 | Predefined::getSymbolID(Predefined::toString), |
44 | 93 | nullptr, |
45 | 93 | functionPrototypeToString, |
46 | 93 | 0); |
47 | 93 | defineMethod( |
48 | 93 | runtime, |
49 | 93 | functionPrototype, |
50 | 93 | Predefined::getSymbolID(Predefined::apply), |
51 | 93 | nullptr, |
52 | 93 | functionPrototypeApply, |
53 | 93 | 2); |
54 | 93 | defineMethod( |
55 | 93 | runtime, |
56 | 93 | functionPrototype, |
57 | 93 | Predefined::getSymbolID(Predefined::call), |
58 | 93 | nullptr, |
59 | 93 | functionPrototypeCall, |
60 | 93 | 1); |
61 | 93 | defineMethod( |
62 | 93 | runtime, |
63 | 93 | functionPrototype, |
64 | 93 | Predefined::getSymbolID(Predefined::bind), |
65 | 93 | nullptr, |
66 | 93 | functionPrototypeBind, |
67 | 93 | 1); |
68 | | |
69 | 93 | DefinePropertyFlags dpf = DefinePropertyFlags::getDefaultNewPropertyFlags(); |
70 | 93 | dpf.writable = 0; |
71 | 93 | dpf.enumerable = 0; |
72 | 93 | dpf.configurable = 0; |
73 | 93 | (void)defineMethod( |
74 | 93 | runtime, |
75 | 93 | functionPrototype, |
76 | 93 | Predefined::getSymbolID(Predefined::SymbolHasInstance), |
77 | 93 | Predefined::getSymbolID(Predefined::squareSymbolHasInstance), |
78 | 93 | nullptr, |
79 | 93 | functionPrototypeSymbolHasInstance, |
80 | 93 | 1, |
81 | 93 | dpf); |
82 | | |
83 | 93 | return cons; |
84 | 93 | } |
85 | | |
86 | | CallResult<HermesValue> |
87 | 10 | functionConstructor(void *, Runtime &runtime, NativeArgs args) { |
88 | 10 | return createDynamicFunction(runtime, args, DynamicFunctionKind::Normal); |
89 | 10 | } |
90 | | |
91 | | CallResult<HermesValue> |
92 | 7 | functionPrototypeToString(void *, Runtime &runtime, NativeArgs args) { |
93 | 7 | GCScope gcScope{runtime}; |
94 | | |
95 | 7 | auto func = args.dyncastThis<Callable>(); |
96 | 7 | if (!func) { |
97 | 0 | return runtime.raiseTypeError( |
98 | 0 | "Can't call Function.prototype.toString() on non-callable"); |
99 | 0 | } |
100 | | |
101 | | /// Append the current function name to the \p strBuf. |
102 | 7 | auto appendFunctionName = [&func, &runtime](SmallU16String<64> &strBuf) { |
103 | | // Extract the name. |
104 | 7 | auto propRes = JSObject::getNamed_RJS( |
105 | 7 | func, runtime, Predefined::getSymbolID(Predefined::name)); |
106 | 7 | if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) { |
107 | 0 | return ExecutionStatus::EXCEPTION; |
108 | 0 | } |
109 | | |
110 | | // Convert the name to string, unless it is undefined. |
111 | 7 | if (!(*propRes)->isUndefined()) { |
112 | 7 | auto strRes = |
113 | 7 | toString_RJS(runtime, runtime.makeHandle(std::move(*propRes))); |
114 | 7 | if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) { |
115 | 0 | return ExecutionStatus::EXCEPTION; |
116 | 0 | } |
117 | 7 | strRes->get()->appendUTF16String(strBuf); |
118 | 7 | } |
119 | 7 | return ExecutionStatus::RETURNED; |
120 | 7 | }; |
121 | | |
122 | | // Deal with JSFunctions that has a source String ID. That implies this |
123 | | // function need a non-default toString implementation. |
124 | 7 | if (auto jsFunc = dyn_vmcast<JSFunction>(*func)) { |
125 | 7 | if (auto sourceID = jsFunc->getCodeBlock(runtime)->getFunctionSourceID()) { |
126 | 0 | StringPrimitive *source = |
127 | 0 | jsFunc->getCodeBlock(runtime) |
128 | 0 | ->getRuntimeModule() |
129 | 0 | ->getLazyRootModule() |
130 | 0 | ->getStringPrimFromStringIDMayAllocate(*sourceID); |
131 | | // Empty source marks implementation-hidden function, fabricate a source |
132 | | // code string that imitate a NativeFunction. |
133 | 0 | if (source->getStringLength() == 0) { |
134 | 0 | SmallU16String<64> strBuf{}; |
135 | 0 | strBuf.append("function "); |
136 | 0 | if (LLVM_UNLIKELY( |
137 | 0 | appendFunctionName(strBuf) == ExecutionStatus::EXCEPTION)) { |
138 | 0 | return ExecutionStatus::EXCEPTION; |
139 | 0 | } |
140 | 0 | strBuf.append("() { [native code] }"); |
141 | 0 | return StringPrimitive::create(runtime, strBuf); |
142 | 0 | } else { |
143 | | // Otherwise, it's the preserved source code. |
144 | 0 | return HermesValue::encodeStringValue(source); |
145 | 0 | } |
146 | 7 | }; |
147 | 7 | } |
148 | | |
149 | 7 | SmallU16String<64> strBuf{}; |
150 | 7 | if (vmisa<JSAsyncFunction>(*func)) { |
151 | 0 | strBuf.append("async function "); |
152 | 7 | } else if (vmisa<JSGeneratorFunction>(*func)) { |
153 | 1 | strBuf.append("function *"); |
154 | 6 | } else { |
155 | 6 | strBuf.append("function "); |
156 | 6 | } |
157 | | |
158 | 7 | if (LLVM_UNLIKELY(appendFunctionName(strBuf) == ExecutionStatus::EXCEPTION)) { |
159 | 0 | return ExecutionStatus::EXCEPTION; |
160 | 0 | } |
161 | | |
162 | | // Formal parameters and the rest of the body. |
163 | 7 | if (vmisa<NativeFunction>(*func)) { |
164 | | // Use [native code] here because we want to work with tools like Babel |
165 | | // which detect the string "[native code]" and use it to alter behavior |
166 | | // during the class transform. |
167 | | // Also print without synthesized formal parameters to avoid breaking |
168 | | // heuristics that detect the string "() { [native code] }". |
169 | | // \see https://github.com/facebook/hermes/issues/471 |
170 | 0 | strBuf.append("() { [native code] }"); |
171 | 7 | } else { |
172 | | // Append the synthesized formal parameters. |
173 | 7 | strBuf.append('('); |
174 | | |
175 | | // Extract ".length". |
176 | 7 | auto lengthProp = Callable::extractOwnLengthProperty_RJS(func, runtime); |
177 | 7 | if (lengthProp == ExecutionStatus::EXCEPTION) |
178 | 0 | return ExecutionStatus::EXCEPTION; |
179 | | |
180 | | // The value of the property is not guaranteed to be meaningful, so clamp it |
181 | | // to [0..65535] for sanity. |
182 | 7 | uint32_t paramCount = |
183 | 7 | (uint32_t)std::min(65535.0, std::max(0.0, *lengthProp)); |
184 | | |
185 | 7 | for (uint32_t i = 0; i < paramCount; ++i) { |
186 | 0 | if (i != 0) |
187 | 0 | strBuf.append(", "); |
188 | 0 | char buf[16]; |
189 | 0 | ::snprintf(buf, sizeof(buf), "a%u", i); |
190 | 0 | strBuf.append(buf); |
191 | 0 | } |
192 | | |
193 | | // Avoid using the [native code] string to prevent extra wrapping overhead |
194 | | // in, e.g., Babel's class extension mechanism. |
195 | 7 | strBuf.append(") { [bytecode] }"); |
196 | 7 | } |
197 | | |
198 | | // Finally allocate a StringPrimitive. |
199 | 7 | return StringPrimitive::create(runtime, strBuf); |
200 | 7 | } // namespace vm |
201 | | |
202 | | CallResult<HermesValue> |
203 | 0 | functionPrototypeApply(void *, Runtime &runtime, NativeArgs args) { |
204 | 0 | GCScope gcScope(runtime); |
205 | 0 | auto func = args.dyncastThis<Callable>(); |
206 | 0 | if (LLVM_UNLIKELY(!func)) { |
207 | 0 | return runtime.raiseTypeError("Can't apply() to non-callable"); |
208 | 0 | } |
209 | | |
210 | 0 | if (args.getArg(1).isNull() || args.getArg(1).isUndefined()) { |
211 | 0 | ScopedNativeCallFrame newFrame{runtime, 0, *func, false, args.getArg(0)}; |
212 | 0 | if (LLVM_UNLIKELY(newFrame.overflowed())) |
213 | 0 | return runtime.raiseStackOverflow( |
214 | 0 | Runtime::StackOverflowKind::NativeStack); |
215 | 0 | return Callable::call(func, runtime).toCallResultHermesValue(); |
216 | 0 | } |
217 | | |
218 | 0 | auto argObj = Handle<JSObject>::dyn_vmcast(args.getArgHandle(1)); |
219 | 0 | if (LLVM_UNLIKELY(!argObj)) { |
220 | 0 | return runtime.raiseTypeError( |
221 | 0 | "Can't apply() with non-object arguments list"); |
222 | 0 | } |
223 | | |
224 | 0 | return Callable::executeCall( |
225 | 0 | func, |
226 | 0 | runtime, |
227 | 0 | Runtime::getUndefinedValue(), |
228 | 0 | args.getArgHandle(0), |
229 | 0 | argObj) |
230 | 0 | .toCallResultHermesValue(); |
231 | 0 | } |
232 | | |
233 | | CallResult<HermesValue> |
234 | 0 | functionPrototypeCall(void *, Runtime &runtime, NativeArgs args) { |
235 | 0 | auto func = args.dyncastThis<Callable>(); |
236 | 0 | if (LLVM_UNLIKELY(!func)) { |
237 | 0 | return runtime.raiseTypeError("Can't call() non-callable"); |
238 | 0 | } |
239 | | |
240 | 0 | uint32_t argCount = args.getArgCount(); |
241 | 0 | ScopedNativeCallFrame newFrame{ |
242 | 0 | runtime, argCount ? argCount - 1 : 0, *func, false, args.getArg(0)}; |
243 | 0 | if (LLVM_UNLIKELY(newFrame.overflowed())) |
244 | 0 | return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack); |
245 | 0 | for (uint32_t i = 1; i < argCount; ++i) { |
246 | 0 | newFrame->getArgRef(i - 1) = args.getArg(i); |
247 | 0 | } |
248 | 0 | auto res = Callable::call(func, runtime); |
249 | 0 | if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) { |
250 | 0 | return ExecutionStatus::EXCEPTION; |
251 | 0 | } |
252 | 0 | return res->getHermesValue(); |
253 | 0 | } |
254 | | |
255 | | CallResult<HermesValue> |
256 | 93 | functionPrototypeBind(void *, Runtime &runtime, NativeArgs args) { |
257 | 93 | auto target = Handle<Callable>::dyn_vmcast(args.getThisHandle()); |
258 | 93 | if (!target) { |
259 | 0 | return runtime.raiseTypeError("Can't bind() a non-callable"); |
260 | 0 | } |
261 | | |
262 | 93 | return BoundFunction::create( |
263 | 93 | runtime, target, args.getArgCount(), args.begin()); |
264 | 93 | } |
265 | | |
266 | | CallResult<HermesValue> |
267 | 0 | functionPrototypeSymbolHasInstance(void *, Runtime &runtime, NativeArgs args) { |
268 | | /// 1. Let F be the this value. |
269 | 0 | auto F = args.getThisHandle(); |
270 | | /// 2. Return OrdinaryHasInstance(F, V). |
271 | 0 | auto result = ordinaryHasInstance(runtime, F, args.getArgHandle(0)); |
272 | 0 | if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) { |
273 | 0 | return ExecutionStatus::EXCEPTION; |
274 | 0 | } |
275 | 0 | return HermesValue::encodeBoolValue(*result); |
276 | 0 | } |
277 | | |
278 | | } // namespace vm |
279 | | } // namespace hermes |