lib/VM/JSLib/Function.cpp (209 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
//===----------------------------------------------------------------------===//
/// \file
/// ES5.1 15.3 Initialize the Function constructor.
//===----------------------------------------------------------------------===//
#include "JSLibInternal.h"
#include "hermes/Regex/Executor.h"
#include "hermes/Regex/RegexTraits.h"
#include "hermes/VM/ArrayLike.h"
#include "hermes/VM/Callable.h"
#include "hermes/VM/Operations.h"
#include "hermes/VM/StringBuilder.h"
#include "hermes/VM/StringView.h"
namespace hermes {
namespace vm {
//===----------------------------------------------------------------------===//
/// Function.
Handle<JSObject> createFunctionConstructor(Runtime &runtime) {
auto functionPrototype = Handle<Callable>::vmcast(&runtime.functionPrototype);
auto cons = defineSystemConstructor<JSFunction>(
runtime,
Predefined::getSymbolID(Predefined::Function),
functionConstructor,
functionPrototype,
1,
CellKind::JSFunctionKind);
// Function.prototype.xxx() methods.
defineMethod(
runtime,
functionPrototype,
Predefined::getSymbolID(Predefined::toString),
nullptr,
functionPrototypeToString,
0);
defineMethod(
runtime,
functionPrototype,
Predefined::getSymbolID(Predefined::apply),
nullptr,
functionPrototypeApply,
2);
defineMethod(
runtime,
functionPrototype,
Predefined::getSymbolID(Predefined::call),
nullptr,
functionPrototypeCall,
1);
defineMethod(
runtime,
functionPrototype,
Predefined::getSymbolID(Predefined::bind),
nullptr,
functionPrototypeBind,
1);
DefinePropertyFlags dpf = DefinePropertyFlags::getDefaultNewPropertyFlags();
dpf.writable = 0;
dpf.enumerable = 0;
dpf.configurable = 0;
(void)defineMethod(
runtime,
functionPrototype,
Predefined::getSymbolID(Predefined::SymbolHasInstance),
Predefined::getSymbolID(Predefined::squareSymbolHasInstance),
nullptr,
functionPrototypeSymbolHasInstance,
1,
dpf);
return cons;
}
CallResult<HermesValue>
functionConstructor(void *, Runtime &runtime, NativeArgs args) {
return createDynamicFunction(runtime, args, DynamicFunctionKind::Normal);
}
CallResult<HermesValue>
functionPrototypeToString(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope{runtime};
auto func = args.dyncastThis<Callable>();
if (!func) {
return runtime.raiseTypeError(
"Can't call Function.prototype.toString() on non-callable");
}
/// Append the current function name to the \p strBuf.
auto appendFunctionName = [&func, &runtime](SmallU16String<64> &strBuf) {
// Extract the name.
auto propRes = JSObject::getNamed_RJS(
func, runtime, Predefined::getSymbolID(Predefined::name));
if (LLVM_UNLIKELY(propRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// Convert the name to string, unless it is undefined.
if (!(*propRes)->isUndefined()) {
auto strRes =
toString_RJS(runtime, runtime.makeHandle(std::move(*propRes)));
if (LLVM_UNLIKELY(strRes == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
strRes->get()->appendUTF16String(strBuf);
}
return ExecutionStatus::RETURNED;
};
// Deal with JSFunctions that has a source String ID. That implies this
// function need a non-default toString implementation.
if (auto jsFunc = dyn_vmcast<JSFunction>(*func)) {
if (auto sourceID = jsFunc->getCodeBlock()->getFunctionSourceID()) {
StringPrimitive *source =
jsFunc->getCodeBlock()
->getRuntimeModule()
->getLazyRootModule()
->getStringPrimFromStringIDMayAllocate(*sourceID);
// Empty source marks implementation-hidden function, fabricate a source
// code string that imitate a NativeFunction.
if (source->getStringLength() == 0) {
SmallU16String<64> strBuf{};
strBuf.append("function ");
if (LLVM_UNLIKELY(
appendFunctionName(strBuf) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
strBuf.append("() { [native code] }");
return StringPrimitive::create(runtime, strBuf);
} else {
// Otherwise, it's the preserved source code.
return HermesValue::encodeStringValue(source);
}
};
}
SmallU16String<64> strBuf{};
if (vmisa<JSAsyncFunction>(*func)) {
strBuf.append("async function ");
} else if (vmisa<JSGeneratorFunction>(*func)) {
strBuf.append("function *");
} else {
strBuf.append("function ");
}
if (LLVM_UNLIKELY(appendFunctionName(strBuf) == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
// Formal parameters and the rest of the body.
if (vmisa<NativeFunction>(*func)) {
// Use [native code] here because we want to work with tools like Babel
// which detect the string "[native code]" and use it to alter behavior
// during the class transform.
// Also print without synthesized formal parameters to avoid breaking
// heuristics that detect the string "() { [native code] }".
// \see https://github.com/facebook/hermes/issues/471
strBuf.append("() { [native code] }");
} else {
// Append the synthesized formal parameters.
strBuf.append('(');
// Extract ".length".
auto lengthProp = Callable::extractOwnLengthProperty_RJS(func, runtime);
if (lengthProp == ExecutionStatus::EXCEPTION)
return ExecutionStatus::EXCEPTION;
// The value of the property is not guaranteed to be meaningful, so clamp it
// to [0..65535] for sanity.
uint32_t paramCount =
(uint32_t)std::min(65535.0, std::max(0.0, *lengthProp));
for (uint32_t i = 0; i < paramCount; ++i) {
if (i != 0)
strBuf.append(", ");
char buf[16];
::snprintf(buf, sizeof(buf), "a%u", i);
strBuf.append(buf);
}
// Avoid using the [native code] string to prevent extra wrapping overhead
// in, e.g., Babel's class extension mechanism.
strBuf.append(") { [bytecode] }");
}
// Finally allocate a StringPrimitive.
return StringPrimitive::create(runtime, strBuf);
} // namespace vm
CallResult<HermesValue>
functionPrototypeApply(void *, Runtime &runtime, NativeArgs args) {
GCScope gcScope(runtime);
auto func = args.dyncastThis<Callable>();
if (LLVM_UNLIKELY(!func)) {
return runtime.raiseTypeError("Can't apply() to non-callable");
}
if (args.getArg(1).isNull() || args.getArg(1).isUndefined()) {
ScopedNativeCallFrame newFrame{runtime, 0, *func, false, args.getArg(0)};
if (LLVM_UNLIKELY(newFrame.overflowed()))
return runtime.raiseStackOverflow(
Runtime::StackOverflowKind::NativeStack);
return Callable::call(func, runtime).toCallResultHermesValue();
}
auto argObj = Handle<JSObject>::dyn_vmcast(args.getArgHandle(1));
if (LLVM_UNLIKELY(!argObj)) {
return runtime.raiseTypeError(
"Can't apply() with non-object arguments list");
}
return Callable::executeCall(
func,
runtime,
Runtime::getUndefinedValue(),
args.getArgHandle(0),
argObj)
.toCallResultHermesValue();
}
CallResult<HermesValue>
functionPrototypeCall(void *, Runtime &runtime, NativeArgs args) {
auto func = args.dyncastThis<Callable>();
if (LLVM_UNLIKELY(!func)) {
return runtime.raiseTypeError("Can't call() non-callable");
}
uint32_t argCount = args.getArgCount();
ScopedNativeCallFrame newFrame{
runtime, argCount ? argCount - 1 : 0, *func, false, args.getArg(0)};
if (LLVM_UNLIKELY(newFrame.overflowed()))
return runtime.raiseStackOverflow(Runtime::StackOverflowKind::NativeStack);
for (uint32_t i = 1; i < argCount; ++i) {
newFrame->getArgRef(i - 1) = args.getArg(i);
}
auto res = Callable::call(func, runtime);
if (LLVM_UNLIKELY(res == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
return res->getHermesValue();
}
CallResult<HermesValue>
functionPrototypeBind(void *, Runtime &runtime, NativeArgs args) {
auto target = Handle<Callable>::dyn_vmcast(args.getThisHandle());
if (!target) {
return runtime.raiseTypeError("Can't bind() a non-callable");
}
return BoundFunction::create(
runtime, target, args.getArgCount(), args.begin());
}
CallResult<HermesValue>
functionPrototypeSymbolHasInstance(void *, Runtime &runtime, NativeArgs args) {
/// 1. Let F be the this value.
auto F = args.getThisHandle();
/// 2. Return OrdinaryHasInstance(F, V).
auto result = ordinaryHasInstance(runtime, F, args.getArgHandle(0));
if (LLVM_UNLIKELY(result == ExecutionStatus::EXCEPTION)) {
return ExecutionStatus::EXCEPTION;
}
return HermesValue::encodeBoolValue(*result);
}
} // namespace vm
} // namespace hermes