blob: d83a845d7920b7f254f8b0467f0fb08e5d5b3bb8 [file] [log] [blame]
/*
* Copyright (C) 2021 Igalia S.L.
* Author: Caitlin Potter <caitp@igalia.com>
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "JSRemoteFunction.h"
#include "ExecutableBaseInlines.h"
#include "JSCInlines.h"
#include "ShadowRealmObject.h"
#include <wtf/Assertions.h>
namespace JSC {
const ClassInfo JSRemoteFunction::s_info = { "Function"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSRemoteFunction) };
JSRemoteFunction::JSRemoteFunction(VM& vm, NativeExecutable* executable, JSGlobalObject* globalObject, Structure* structure, JSObject* targetFunction)
: Base(vm, executable, globalObject, structure)
, m_targetFunction(vm, this, targetFunction)
, m_length(0.0)
{
}
static JSValue wrapValue(JSGlobalObject* globalObject, JSGlobalObject* targetGlobalObject, JSValue value)
{
VM& vm = globalObject->vm();
if (value.isPrimitive())
return value;
if (value.isCallable()) {
JSObject* targetFunction = static_cast<JSObject*>(value.asCell());
return JSRemoteFunction::tryCreate(targetGlobalObject, vm, targetFunction);
}
return JSValue();
}
static inline JSValue wrapArgument(JSGlobalObject* globalObject, JSGlobalObject* targetGlobalObject, JSValue value)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue result = wrapValue(globalObject, targetGlobalObject, value);
RETURN_IF_EXCEPTION(scope, { });
if (!result)
throwTypeError(globalObject, scope, "value passing between realms must be callable or primitive"_s);
RELEASE_AND_RETURN(scope, result);
}
static inline JSValue wrapReturnValue(JSGlobalObject* globalObject, JSGlobalObject* targetGlobalObject, JSValue value)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSValue result = wrapValue(globalObject, targetGlobalObject, value);
RETURN_IF_EXCEPTION(scope, { });
if (!result)
throwTypeError(globalObject, scope, "value passing between realms must be callable or primitive"_s);
RELEASE_AND_RETURN(scope, result);
}
JSC_DEFINE_HOST_FUNCTION(remoteFunctionCallForJSFunction, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSRemoteFunction* remoteFunction = jsCast<JSRemoteFunction*>(callFrame->jsCallee());
ASSERT(remoteFunction->isRemoteFunction());
JSFunction* targetFunction = jsCast<JSFunction*>(remoteFunction->targetFunction());
JSGlobalObject* targetGlobalObject = targetFunction->globalObject();
MarkedArgumentBuffer args;
for (unsigned i = 0; i < callFrame->argumentCount(); ++i) {
JSValue wrappedValue = wrapArgument(globalObject, targetGlobalObject, callFrame->uncheckedArgument(i));
RETURN_IF_EXCEPTION(scope, { });
args.append(wrappedValue);
}
if (UNLIKELY(args.hasOverflowed())) {
throwOutOfMemoryError(globalObject, scope);
return { };
}
ExecutableBase* executable = targetFunction->executable();
if (executable->hasJITCodeForCall()) {
// Force the executable to cache its arity entrypoint.
executable->entrypointFor(CodeForCall, MustCheckArity);
}
auto callData = JSC::getCallData(targetFunction);
ASSERT(callData.type != CallData::Type::None);
auto result = call(targetGlobalObject, targetFunction, callData, jsUndefined(), args);
RETURN_IF_EXCEPTION(scope, { });
RELEASE_AND_RETURN(scope, JSValue::encode(wrapReturnValue(globalObject, globalObject, result)));
}
JSC_DEFINE_HOST_FUNCTION(remoteFunctionCallGeneric, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSRemoteFunction* remoteFunction = jsCast<JSRemoteFunction*>(callFrame->jsCallee());
ASSERT(remoteFunction->isRemoteFunction());
JSObject* targetFunction = remoteFunction->targetFunction();
JSGlobalObject* targetGlobalObject = targetFunction->globalObject();
MarkedArgumentBuffer args;
for (unsigned i = 0; i < callFrame->argumentCount(); ++i) {
JSValue wrappedValue = wrapArgument(globalObject, targetGlobalObject, callFrame->uncheckedArgument(i));
RETURN_IF_EXCEPTION(scope, { });
args.append(wrappedValue);
}
if (UNLIKELY(args.hasOverflowed())) {
throwOutOfMemoryError(globalObject, scope);
return { };
}
auto callData = JSC::getCallData(targetFunction);
ASSERT(callData.type != CallData::Type::None);
auto result = call(targetGlobalObject, targetFunction, callData, jsUndefined(), args);
RETURN_IF_EXCEPTION(scope, { });
RELEASE_AND_RETURN(scope, JSValue::encode(wrapReturnValue(globalObject, targetGlobalObject, result)));
}
JSC_DEFINE_HOST_FUNCTION(isRemoteFunction, (JSGlobalObject*, CallFrame* callFrame))
{
ASSERT(callFrame->argumentCount() == 1);
JSValue value = callFrame->uncheckedArgument(0);
return JSValue::encode(jsBoolean(JSC::isRemoteFunction(value)));
}
JSC_DEFINE_HOST_FUNCTION(createRemoteFunction, (JSGlobalObject* globalObject, CallFrame* callFrame))
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
ASSERT(callFrame->argumentCount() == 2);
JSValue targetFunction = callFrame->uncheckedArgument(0);
ASSERT(targetFunction.isCallable());
JSObject* targetCallable = jsCast<JSObject*>(targetFunction.asCell());
JSGlobalObject* destinationGlobalObject = globalObject;
if (!callFrame->uncheckedArgument(1).isUndefinedOrNull()) {
if (auto shadowRealm = jsDynamicCast<ShadowRealmObject*>(callFrame->uncheckedArgument(1)))
destinationGlobalObject = shadowRealm->globalObject();
else
destinationGlobalObject = jsCast<JSGlobalObject*>(callFrame->uncheckedArgument(1));
}
RELEASE_AND_RETURN(scope, JSValue::encode(JSRemoteFunction::tryCreate(destinationGlobalObject, vm, targetCallable)));
}
inline Structure* getRemoteFunctionStructure(JSGlobalObject* globalObject)
{
// FIXME: implement globalObject-aware structure caching
return globalObject->remoteFunctionStructure();
}
JSRemoteFunction* JSRemoteFunction::tryCreate(JSGlobalObject* globalObject, VM& vm, JSObject* targetCallable)
{
ASSERT(targetCallable && targetCallable->isCallable());
if (auto remote = jsDynamicCast<JSRemoteFunction*>(targetCallable)) {
targetCallable = remote->targetFunction();
ASSERT(!JSC::isRemoteFunction(targetCallable));
}
bool isJSFunction = getJSFunction(targetCallable);
NativeExecutable* executable = vm.getRemoteFunction(isJSFunction);
Structure* structure = getRemoteFunctionStructure(globalObject);
JSRemoteFunction* function = new (NotNull, allocateCell<JSRemoteFunction>(vm)) JSRemoteFunction(vm, executable, globalObject, structure, targetCallable);
function->finishCreation(globalObject, vm);
return function;
}
// https://tc39.es/proposal-shadowrealm/#sec-copynameandlength
void JSRemoteFunction::copyNameAndLength(JSGlobalObject* globalObject)
{
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
PropertySlot slot(m_targetFunction.get(), PropertySlot::InternalMethodType::Get);
bool targetHasLength = m_targetFunction->getOwnPropertySlotInline(globalObject, vm.propertyNames->length, slot);
RETURN_IF_EXCEPTION(scope, void());
if (targetHasLength) {
JSValue targetLength = slot.getValue(globalObject, vm.propertyNames->length);
RETURN_IF_EXCEPTION(scope, void());
double targetLengthAsInt = targetLength.toIntegerOrInfinity(globalObject);
RETURN_IF_EXCEPTION(scope, void());
m_length = std::max(targetLengthAsInt, 0.0);
}
JSValue targetName = m_targetFunction->get(globalObject, vm.propertyNames->name);
RETURN_IF_EXCEPTION(scope, void());
if (targetName.isString())
m_nameMayBeNull.set(vm, this, asString(targetName));
}
void JSRemoteFunction::finishCreation(JSGlobalObject* globalObject, VM& vm)
{
Base::finishCreation(vm);
ASSERT(inherits(info()));
auto scope = DECLARE_THROW_SCOPE(vm);
copyNameAndLength(globalObject);
auto* exception = scope.exception();
if (UNLIKELY(exception && !vm.isTerminationException(exception))) {
scope.clearException();
throwTypeError(globalObject, scope, "wrapping returned function throws an error"_s);
}
}
template<typename Visitor>
void JSRemoteFunction::visitChildrenImpl(JSCell* cell, Visitor& visitor)
{
JSRemoteFunction* thisObject = jsCast<JSRemoteFunction*>(cell);
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
Base::visitChildren(thisObject, visitor);
visitor.append(thisObject->m_targetFunction);
visitor.append(thisObject->m_nameMayBeNull);
}
DEFINE_VISIT_CHILDREN(JSRemoteFunction);
} // namespace JSC