/* * Copyright (C) 2013-2023 Apple Inc. All rights reserved. * * 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. ``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 * 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. */ #pragma once #include "ExecutableBaseInlines.h" #include "FunctionExecutable.h" #include "JSBoundFunction.h" #include "JSFunction.h" #include "JSRemoteFunction.h" #include "NativeExecutable.h" #include "WebAssemblyFunction.h" #include #include #if ENABLE(AOT) #include #endif namespace JSC { inline Structure* JSFunction::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { ASSERT(globalObject); return Structure::create(vm, globalObject, prototype, TypeInfo(JSFunctionType, StructureFlags), info()); } inline Structure* JSStrictFunction::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { ASSERT(globalObject); return Structure::create(vm, globalObject, prototype, TypeInfo(JSFunctionType, StructureFlags), info()); } inline Structure* JSSloppyFunction::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { ASSERT(globalObject); return Structure::create(vm, globalObject, prototype, TypeInfo(JSFunctionType, StructureFlags), info()); } inline Structure* JSArrowFunction::createStructure(VM& vm, JSGlobalObject* globalObject, JSValue prototype) { ASSERT(globalObject); return Structure::create(vm, globalObject, prototype, TypeInfo(JSFunctionType, StructureFlags), info()); } inline JSFunction* JSFunction::createWithInvalidatedReallocationWatchpoint(VM& vm, JSGlobalObject* globalObject, FunctionExecutable* executable, JSScope* scope) { return createWithInvalidatedReallocationWatchpoint(vm, globalObject, executable, scope, selectStructureForNewFuncExp(globalObject, executable)); } inline JSFunction* JSFunction::createWithInvalidatedReallocationWatchpoint(VM& vm, JSGlobalObject*, FunctionExecutable* executable, JSScope* scope, Structure* structure) { ASSERT(executable->singleton().hasBeenInvalidated()); return createImpl(vm, executable, scope, structure); } inline JSFunction::JSFunction(VM& vm, FunctionExecutable* executable, JSScope* scope, Structure* structure) : Base(vm, scope, structure) , m_executableOrRareData(std::bit_cast(executable)) { assertTypeInfoFlagInvariants(); } inline FunctionExecutable* JSFunction::jsExecutable() const { ASSERT(!isHostFunctionNonInline()); return static_cast(executable()); } inline bool JSFunction::isHostFunction() const { ASSERT(executable()); return executable()->isHostFunction(); } inline bool JSFunction::isNonBoundHostFunction() const { return isHostFunction() && !inherits(); } inline Intrinsic JSFunction::intrinsic() const { return executable()->intrinsic(); } inline bool JSFunction::isBuiltinFunction() const { return !isHostFunction() && jsExecutable()->isBuiltinFunction(); } inline bool JSFunction::isHostOrBuiltinFunction() const { return isHostFunction() || isBuiltinFunction(); } inline bool JSFunction::isHostOrPrivateBuiltinFunction() const { return isHostFunction() || jsExecutable()->isPrivateBuiltinFunction(); } inline bool JSFunction::isClassConstructorFunction() const { return !isHostFunction() && jsExecutable()->isClassConstructorFunction(); } inline bool JSFunction::isRemoteFunction() const { return inherits(); } inline TaggedNativeFunction JSFunction::nativeFunction() { ASSERT(isHostFunctionNonInline()); return static_cast(executable())->function(); } inline TaggedNativeFunction JSFunction::nativeConstructor() { ASSERT(isHostFunctionNonInline()); return static_cast(executable())->constructor(); } inline bool isRemoteFunction(JSValue value) { return value.inherits(); } inline bool JSFunction::hasReifiedLength() const { if (FunctionRareData* rareData = this->rareData()) return rareData->hasReifiedLength(); return false; } inline bool JSFunction::hasReifiedName() const { if (FunctionRareData* rareData = this->rareData()) return rareData->hasReifiedName(); return false; } inline double JSFunction::originalLength(VM& vm) { if (inherits()) return uncheckedDowncast(this)->length(vm); if (inherits()) return uncheckedDowncast(this)->length(vm); if (isHostFunction()) { #if ENABLE(AOT) if (isCompiledClosure()) return AOT::CompiledCallee::length(this); #endif // The original length is captured in NativeExecutable at creation time. return uncheckedDowncast(executable())->length(); } return jsExecutable()->parameterCount(); } template ALWAYS_INLINE String makeNameWithOutOfMemoryCheck(JSGlobalObject* globalObject, ThrowScope& throwScope, ASCIILiteral messagePrefix, StringTypes... strings) { String name = tryMakeString(strings...); if (!name) [[unlikely]] { throwOutOfMemoryError(globalObject, throwScope, makeString(messagePrefix, "name is too long"_s)); return String(); } return name; } inline JSString* JSFunction::originalName(JSGlobalObject* globalObject) { VM& vm = globalObject->vm(); auto scope = DECLARE_THROW_SCOPE(vm); if (this->inherits()) { JSString* nameMayBeNull = uncheckedDowncast(this)->nameMayBeNull(); if (nameMayBeNull) RELEASE_AND_RETURN(scope, jsString(globalObject, vm.smallStrings.boundPrefixString(), nameMayBeNull)); return jsEmptyString(vm); } if (this->inherits()) { JSString* nameMayBeNull = uncheckedDowncast(this)->nameMayBeNull(); if (nameMayBeNull) return nameMayBeNull; return jsEmptyString(vm); } if (isHostFunction()) { #if ENABLE(AOT) if (isCompiledClosure()) return jsString(vm, AOT::CompiledCallee::name(this)); #endif // Mirror the JS path below: build a fresh JSString from the original name stored on // NativeExecutable. NativeExecutable is shared and uniquely keyed by name, so this // always returns the original (creation-time) value even after the JSFunction's "name" // property has been reified or mutated. return uncheckedDowncast(executable())->nameJSString(vm); } const Identifier& ecmaName = jsExecutable()->ecmaName(); String name; // https://tc39.github.io/ecma262/#sec-exports-runtime-semantics-evaluation // When the ident is "*default*", we need to set "default" for the ecma name. // This "*default*" name is never shown to users. if (ecmaName == vm.propertyNames->starDefaultPrivateName) name = vm.propertyNames->defaultKeyword.string(); else name = ecmaName.string(); if (jsExecutable()->isGetter()) { name = makeNameWithOutOfMemoryCheck(globalObject, scope, "Getter "_s, "get "_s, name); RETURN_IF_EXCEPTION(scope, { }); } else if (jsExecutable()->isSetter()) { name = makeNameWithOutOfMemoryCheck(globalObject, scope, "Setter "_s, "set "_s, name); RETURN_IF_EXCEPTION(scope, { }); } return jsString(vm, WTF::move(name)); } inline bool JSFunction::canAssumeNameAndLengthAreOriginal(VM&) { // Plain host, builtin, and JS functions all reify length/name lazily; the original value is // recoverable via originalLength()/originalName() (from NativeExecutable for hosts, from // FunctionExecutable for JS), so the only thing we have to refuse is bound functions // (which set their length/name at bind time, not via FunctionRareData tracking) and any // function whose length or name has since been user-mutated. // // This must be synced with JSBoundFunction::canSkipNameAndLengthMaterialization(). if (this->inherits()) return false; FunctionRareData* rareData = this->rareData(); if (!rareData) return true; if (rareData->hasModifiedNameForBoundOrNonHostFunction()) return false; if (rareData->hasModifiedLengthForBoundOrNonHostFunction()) return false; return true; } inline bool JSFunction::mayHaveNonReifiedPrototype() { return !isHostOrBuiltinFunction() && jsExecutable()->hasPrototypeProperty(); } inline bool JSFunction::canPerformFastEnumerablePropertyEnumeration(VM& vm) { #if ENABLE(AOT) // Bun: the fast path of WebKit e04a6f9faecf is off for every function. A copy that looks every own key up makes `name` a property of // the function, and Bun's stack frames show that property in front of a displayName. Compiled code copies from a closure that way, // and plain code has to print the same frames. // A compiled closure stays out of the fast path in any case: listing its own properties makes its class object and its static // fields first (AOT::Closure::getOwnSpecialPropertyNames), and a walk of its Structure would not see them. UNUSED_PARAM(vm); return false; #else if (!canAssumeNameAndLengthAreOriginal(vm)) return false; Structure* structure = this->structure(); if (structure->hasAnyKindOfGetterSetterProperties()) return false; if (structure->isUncacheableDictionary()) return false; if (structure->hasUnderscoreProtoPropertyExcludingOriginalProto()) return false; return true; #endif } inline bool JSFunction::canUseAllocationProfiles() { if (isHostOrBuiltinFunction()) { if (isHostFunction()) return false; VM& vm = this->vm(); unsigned attributes; JSValue prototype = getDirect(vm, vm.propertyNames->prototype, attributes); if (!prototype || (attributes & PropertyAttribute::AccessorOrCustomAccessorOrValue)) return false; } // If we don't have a prototype property, we're not guaranteed it's // non-configurable. For example, user code can define the prototype // as a getter. JS semantics require that the getter is called every // time |construct| occurs with this function as new.target. return jsExecutable()->hasPrototypeProperty(); } inline FunctionRareData* JSFunction::ensureRareDataAndObjectAllocationProfile(JSGlobalObject* globalObject, unsigned inlineCapacity) { ASSERT(canUseAllocationProfiles()); FunctionRareData* rareData = this->rareData(); if (!rareData) return allocateAndInitializeRareData(globalObject, inlineCapacity); if (!rareData->isObjectAllocationProfileInitialized()) [[unlikely]] return initializeRareData(globalObject, inlineCapacity); return rareData; } inline JSString* JSFunction::asStringConcurrently() const { if (inherits() || inherits()) return nullptr; if (isHostFunction()) return static_cast(executable())->asStringConcurrently(); return jsExecutable()->asStringConcurrently(); } inline CallData JSFunction::getCallDataInline(JSCell* cell) { // Keep this function OK for invocation from concurrent compilers. CallData callData; JSFunction* thisObject = uncheckedDowncast(cell); ASSERT(!thisObject->isCompiledClosure()); if (thisObject->isHostFunction()) { callData.type = CallData::Type::Native; callData.native.function = thisObject->nativeFunction(); callData.native.isBoundFunction = thisObject->inherits(); callData.native.isWasm = false; #if ENABLE(WEBASSEMBLY) callData.native.isWasm = thisObject->inherits(); #endif } else { callData.type = CallData::Type::JS; callData.js.functionExecutable = thisObject->jsExecutable(); callData.js.scope = thisObject->scope(); } return callData; } inline CallData JSFunction::getConstructDataInline(JSCell* cell) { // Keep this function OK for invocation from concurrent compilers. CallData constructData; JSFunction* thisObject = uncheckedDowncast(cell); ASSERT(!thisObject->isCompiledClosure()); if (thisObject->isHostFunction()) { if (thisObject->inherits()) { if (uncheckedDowncast(thisObject)->canConstruct()) { constructData.type = CallData::Type::Native; constructData.native.function = thisObject->nativeConstructor(); constructData.native.isBoundFunction = true; constructData.native.isWasm = false; } } else if (thisObject->nativeConstructor() != callHostFunctionAsConstructor) { constructData.type = CallData::Type::Native; constructData.native.function = thisObject->nativeConstructor(); } } else { FunctionExecutable* functionExecutable = thisObject->jsExecutable(); if (functionExecutable->constructAbility() != ConstructAbility::CannotConstruct) { constructData.type = CallData::Type::JS; constructData.js.functionExecutable = functionExecutable; constructData.js.scope = thisObject->scope(); } } return constructData; } } // namespace JSC