#include "vm/JSFunction-inl.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/CheckedInt.h"
#include "mozilla/Maybe.h"
#include "mozilla/Range.h"
#include "mozilla/Utf8.h"
#include <string.h>
#include "jsapi.h"
#include "jstypes.h"
#include "builtin/Array.h"
#include "builtin/BigInt.h"
#include "builtin/Eval.h"
#include "builtin/Object.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/String.h"
#include "frontend/BytecodeCompilation.h"
#include "frontend/BytecodeCompiler.h"
#include "frontend/TokenStream.h"
#include "gc/Marking.h"
#include "gc/Policy.h"
#include "jit/InlinableNatives.h"
#include "jit/Ion.h"
#include "js/CallNonGenericMethod.h"
#include "js/CompileOptions.h"
#include "js/PropertySpec.h"
#include "js/Proxy.h"
#include "js/SourceText.h"
#include "js/StableStringChars.h"
#include "js/Wrapper.h"
#include "util/StringBuffer.h"
#include "vm/AsyncFunction.h"
#include "vm/AsyncIteration.h"
#include "vm/Debugger.h"
#include "vm/GlobalObject.h"
#include "vm/Interpreter.h"
#include "vm/JSAtom.h"
#include "vm/JSContext.h"
#include "vm/JSObject.h"
#include "vm/JSScript.h"
#include "vm/SelfHosting.h"
#include "vm/Shape.h"
#include "vm/SharedImmutableStringsCache.h"
#include "vm/WrapperObject.h"
#include "vm/Xdr.h"
#include "wasm/AsmJS.h"
#include "vm/Interpreter-inl.h"
#include "vm/JSScript-inl.h"
#include "vm/Stack-inl.h"
using namespace js;
using mozilla::ArrayLength;
using mozilla::CheckedInt;
using mozilla::Maybe;
using mozilla::Some;
using mozilla::Utf8Unit;
using JS::AutoStableStringChars;
using JS::CompileOptions;
using JS::SourceOwnership;
using JS::SourceText;
static bool fun_enumerate(JSContext* cx, HandleObject obj) {
MOZ_ASSERT(obj->is<JSFunction>());
RootedId id(cx);
bool found;
if (!obj->isBoundFunction() && !obj->as<JSFunction>().isArrow()) {
id = NameToId(cx->names().prototype);
if (!HasOwnProperty(cx, obj, id, &found)) {
return false;
}
}
if (!obj->as<JSFunction>().hasResolvedLength()) {
id = NameToId(cx->names().length);
if (!HasOwnProperty(cx, obj, id, &found)) {
return false;
}
}
if (!obj->as<JSFunction>().hasResolvedName()) {
id = NameToId(cx->names().name);
if (!HasOwnProperty(cx, obj, id, &found)) {
return false;
}
}
return true;
}
bool IsFunction(HandleValue v) {
return v.isObject() && v.toObject().is<JSFunction>();
}
static bool AdvanceToActiveCallLinear(JSContext* cx,
NonBuiltinScriptFrameIter& iter,
HandleFunction fun) {
MOZ_ASSERT(!fun->isBuiltin());
for (; !iter.done(); ++iter) {
if (!iter.isFunctionFrame()) {
continue;
}
if (iter.matchCallee(cx, fun)) {
return true;
}
}
return false;
}
void js::ThrowTypeErrorBehavior(JSContext* cx) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_THROW_TYPE_ERROR);
}
static bool IsSloppyNormalFunction(JSFunction* fun) {
if (fun->kind() == JSFunction::NormalFunction) {
if (fun->isBuiltin() || fun->isBoundFunction()) {
return false;
}
if (fun->isGenerator() || fun->isAsync()) {
return false;
}
MOZ_ASSERT(fun->isInterpreted());
return !fun->strict();
}
if (fun->kind() == JSFunction::AsmJS) {
return !IsAsmJSStrictModeModuleOrFunction(fun);
}
return false;
}
static bool ArgumentsRestrictions(JSContext* cx, HandleFunction fun) {
if (!IsSloppyNormalFunction(fun)) {
ThrowTypeErrorBehavior(cx);
return false;
}
if (!JS_ReportErrorFlagsAndNumberASCII(
cx, JSREPORT_WARNING | JSREPORT_STRICT, GetErrorMessage, nullptr,
JSMSG_DEPRECATED_USAGE, js_arguments_str)) {
return false;
}
return true;
}
bool ArgumentsGetterImpl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsFunction(args.thisv()));
RootedFunction fun(cx, &args.thisv().toObject().as<JSFunction>());
if (!ArgumentsRestrictions(cx, fun)) {
return false;
}
NonBuiltinScriptFrameIter iter(cx);
if (!AdvanceToActiveCallLinear(cx, iter, fun)) {
args.rval().setNull();
return true;
}
Rooted<ArgumentsObject*> argsobj(cx,
ArgumentsObject::createUnexpected(cx, iter));
if (!argsobj) {
return false;
}
#ifndef JS_CODEGEN_NONE
JSScript* script = iter.script();
jit::ForbidCompilation(cx, script);
#endif
args.rval().setObject(*argsobj);
return true;
}
static bool ArgumentsGetter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFunction, ArgumentsGetterImpl>(cx, args);
}
bool ArgumentsSetterImpl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsFunction(args.thisv()));
RootedFunction fun(cx, &args.thisv().toObject().as<JSFunction>());
if (!ArgumentsRestrictions(cx, fun)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool ArgumentsSetter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFunction, ArgumentsSetterImpl>(cx, args);
}
static bool CallerRestrictions(JSContext* cx, HandleFunction fun) {
if (!IsSloppyNormalFunction(fun)) {
ThrowTypeErrorBehavior(cx);
return false;
}
if (!JS_ReportErrorFlagsAndNumberASCII(
cx, JSREPORT_WARNING | JSREPORT_STRICT, GetErrorMessage, nullptr,
JSMSG_DEPRECATED_USAGE, js_caller_str)) {
return false;
}
return true;
}
bool CallerGetterImpl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsFunction(args.thisv()));
RootedFunction fun(cx, &args.thisv().toObject().as<JSFunction>());
if (!CallerRestrictions(cx, fun)) {
return false;
}
NonBuiltinScriptFrameIter iter(cx);
if (!AdvanceToActiveCallLinear(cx, iter, fun)) {
args.rval().setNull();
return true;
}
++iter;
while (!iter.done() && iter.isEvalFrame()) {
++iter;
}
if (iter.done() || !iter.isFunctionFrame()) {
args.rval().setNull();
return true;
}
RootedObject caller(cx, iter.callee(cx));
if (!cx->compartment()->wrap(cx, &caller)) {
return false;
}
{
JSObject* callerObj = CheckedUnwrapStatic(caller);
if (!callerObj) {
args.rval().setNull();
return true;
}
if (JS_IsDeadWrapper(callerObj)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_DEAD_OBJECT);
return false;
}
JSFunction* callerFun = &callerObj->as<JSFunction>();
MOZ_ASSERT(!callerFun->isBuiltin(),
"non-builtin iterator returned a builtin?");
if (callerFun->strict()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CALLER_IS_STRICT);
return false;
}
}
args.rval().setObject(*caller);
return true;
}
static bool CallerGetter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFunction, CallerGetterImpl>(cx, args);
}
bool CallerSetterImpl(JSContext* cx, const CallArgs& args) {
MOZ_ASSERT(IsFunction(args.thisv()));
if (!CallerGetterImpl(cx, args)) {
return false;
}
args.rval().setUndefined();
return true;
}
static bool CallerSetter(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<IsFunction, CallerSetterImpl>(cx, args);
}
static const JSPropertySpec function_properties[] = {
JS_PSGS("arguments", ArgumentsGetter, ArgumentsSetter, 0),
JS_PSGS("caller", CallerGetter, CallerSetter, 0), JS_PS_END};
static bool ResolveInterpretedFunctionPrototype(JSContext* cx,
HandleFunction fun,
HandleId id) {
MOZ_ASSERT(fun->isInterpreted() || fun->isAsmJSNative());
MOZ_ASSERT(id == NameToId(cx->names().prototype));
MOZ_ASSERT(!IsInternalFunctionObject(*fun));
MOZ_ASSERT(!fun->isBoundFunction());
bool isGenerator = fun->isGenerator();
Rooted<GlobalObject*> global(cx, &fun->global());
RootedObject objProto(cx);
if (isGenerator && fun->isAsync()) {
objProto = GlobalObject::getOrCreateAsyncGeneratorPrototype(cx, global);
} else if (isGenerator) {
objProto = GlobalObject::getOrCreateGeneratorObjectPrototype(cx, global);
} else {
objProto = GlobalObject::getOrCreateObjectPrototype(cx, global);
}
if (!objProto) {
return false;
}
RootedPlainObject proto(
cx, NewObjectWithGivenProto<PlainObject>(cx, objProto, SingletonObject));
if (!proto) {
return false;
}
if (!isGenerator) {
RootedValue objVal(cx, ObjectValue(*fun));
if (!DefineDataProperty(cx, proto, cx->names().constructor, objVal, 0)) {
return false;
}
}
RootedValue protoVal(cx, ObjectValue(*proto));
return DefineDataProperty(cx, fun, id, protoVal,
JSPROP_PERMANENT | JSPROP_RESOLVING);
}
bool JSFunction::needsPrototypeProperty() {
return !isBuiltin() && (isConstructor() || isGenerator());
}
static bool fun_mayResolve(const JSAtomState& names, jsid id, JSObject*) {
if (!JSID_IS_ATOM(id)) {
return false;
}
JSAtom* atom = JSID_TO_ATOM(id);
return atom == names.prototype || atom == names.length || atom == names.name;
}
static bool fun_resolve(JSContext* cx, HandleObject obj, HandleId id,
bool* resolvedp) {
if (!JSID_IS_ATOM(id)) {
return true;
}
RootedFunction fun(cx, &obj->as<JSFunction>());
if (JSID_IS_ATOM(id, cx->names().prototype)) {
if (!fun->needsPrototypeProperty()) {
return true;
}
if (!ResolveInterpretedFunctionPrototype(cx, fun, id)) {
return false;
}
*resolvedp = true;
return true;
}
bool isLength = JSID_IS_ATOM(id, cx->names().length);
if (isLength || JSID_IS_ATOM(id, cx->names().name)) {
MOZ_ASSERT(!IsInternalFunctionObject(*obj));
RootedValue v(cx);
if (isLength) {
if (fun->hasResolvedLength()) {
return true;
}
if (!JSFunction::getUnresolvedLength(cx, fun, &v)) {
return false;
}
} else {
if (fun->hasResolvedName()) {
return true;
}
RootedString name(cx);
if (!JSFunction::getUnresolvedName(cx, fun, &name)) {
return false;
}
if (!name) {
return true;
}
v.setString(name);
}
if (!NativeDefineDataProperty(cx, fun, id, v,
JSPROP_READONLY | JSPROP_RESOLVING)) {
return false;
}
if (isLength) {
fun->setResolvedLength();
} else {
fun->setResolvedName();
}
*resolvedp = true;
return true;
}
return true;
}
template <XDRMode mode>
XDRResult js::XDRInterpretedFunction(XDRState<mode>* xdr,
HandleScope enclosingScope,
HandleScriptSourceObject sourceObject,
MutableHandleFunction objp) {
enum FirstWordFlag {
HasAtom = 0x1,
IsGenerator = 0x2,
IsAsync = 0x4,
IsLazy = 0x8,
HasSingletonType = 0x10
};
RootedAtom atom(xdr->cx());
uint32_t firstword = 0;
uint32_t flagsword = 0;
JSContext* cx = xdr->cx();
RootedFunction fun(cx);
RootedScript script(cx);
Rooted<LazyScript*> lazy(cx);
if (mode == XDR_ENCODE) {
fun = objp;
if (!fun->isInterpreted()) {
return xdr->fail(JS::TranscodeResult_Failure_NotInterpretedFun);
}
if (fun->explicitName() || fun->hasInferredName() ||
fun->hasGuessedAtom()) {
firstword |= HasAtom;
}
if (fun->isGenerator()) {
firstword |= IsGenerator;
}
if (fun->isAsync()) {
firstword |= IsAsync;
}
if (fun->isInterpretedLazy()) {
firstword |= IsLazy;
lazy = fun->lazyScript();
} else {
script = fun->nonLazyScript();
}
if (fun->isSingleton()) {
firstword |= HasSingletonType;
}
atom = fun->displayAtom();
flagsword =
(fun->nargs() << 16) | (fun->flags() & ~JSFunction::NO_XDR_FLAGS);
MOZ_ASSERT_IF(fun->isSingleton() && !((lazy && lazy->hasBeenCloned()) ||
(script && script->hasBeenCloned())),
fun->environment() == nullptr);
}
js::AutoXDRTree funTree(xdr, xdr->getTreeKey(fun));
MOZ_TRY(xdr->codeUint32(&firstword));
if (firstword & HasAtom) {
MOZ_TRY(XDRAtom(xdr, &atom));
}
MOZ_TRY(xdr->codeUint32(&flagsword));
if (mode == XDR_DECODE) {
GeneratorKind generatorKind = (firstword & IsGenerator)
? GeneratorKind::Generator
: GeneratorKind::NotGenerator;
FunctionAsyncKind asyncKind = (firstword & IsAsync)
? FunctionAsyncKind::AsyncFunction
: FunctionAsyncKind::SyncFunction;
RootedObject proto(cx);
if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &proto)) {
return xdr->fail(JS::TranscodeResult_Throw);
}
gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
if (uint16_t(flagsword) & JSFunction::EXTENDED) {
allocKind = gc::AllocKind::FUNCTION_EXTENDED;
}
fun = NewFunctionWithProto(cx, nullptr, 0, JSFunction::INTERPRETED,
nullptr, nullptr,
proto, allocKind, TenuredObject);
if (!fun) {
return xdr->fail(JS::TranscodeResult_Throw);
}
script = nullptr;
}
if (firstword & IsLazy) {
MOZ_TRY(XDRLazyScript(xdr, enclosingScope, sourceObject, fun, &lazy));
} else {
MOZ_TRY(XDRScript(xdr, enclosingScope, sourceObject, fun, &script));
}
if (mode == XDR_DECODE) {
fun->setArgCount(flagsword >> 16);
fun->setFlags(uint16_t(flagsword));
fun->initAtom(atom);
if (firstword & IsLazy) {
MOZ_ASSERT(fun->lazyScript() == lazy);
} else {
MOZ_ASSERT(fun->nonLazyScript() == script);
MOZ_ASSERT(fun->nargs() == script->numArgs());
}
bool singleton = firstword & HasSingletonType;
if (!JSFunction::setTypeForScriptedFunction(cx, fun, singleton)) {
return xdr->fail(JS::TranscodeResult_Throw);
}
objp.set(fun);
}
MOZ_TRY(xdr->codeMarker(0x9E35CA1F));
return Ok();
}
template XDRResult js::XDRInterpretedFunction(XDRState<XDR_ENCODE>*,
HandleScope,
HandleScriptSourceObject,
MutableHandleFunction);
template XDRResult js::XDRInterpretedFunction(XDRState<XDR_DECODE>*,
HandleScope,
HandleScriptSourceObject,
MutableHandleFunction);
static bool fun_symbolHasInstance(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() < 1) {
args.rval().setBoolean(false);
return true;
}
HandleValue func = args.thisv();
if (!func.isObject()) {
args.rval().setBoolean(false);
return true;
}
RootedObject obj(cx, &func.toObject());
bool result;
if (!OrdinaryHasInstance(cx, obj, args[0], &result)) {
return false;
}
args.rval().setBoolean(result);
return true;
}
bool JS::OrdinaryHasInstance(JSContext* cx, HandleObject objArg, HandleValue v,
bool* bp) {
AssertHeapIsIdle();
cx->check(objArg, v);
RootedObject obj(cx, objArg);
if (!obj->isCallable()) {
*bp = false;
return true;
}
if (obj->is<JSFunction>() && obj->isBoundFunction()) {
obj = obj->as<JSFunction>().getBoundFunctionTarget();
return InstanceofOperator(cx, obj, v, bp);
}
if (!v.isObject()) {
*bp = false;
return true;
}
RootedValue pval(cx);
if (!GetProperty(cx, obj, obj, cx->names().prototype, &pval)) {
return false;
}
if (pval.isPrimitive()) {
RootedValue val(cx, ObjectValue(*obj));
ReportValueError(cx, JSMSG_BAD_PROTOTYPE, -1, val, nullptr);
return false;
}
RootedObject pobj(cx, &pval.toObject());
bool isPrototype;
if (!IsPrototypeOf(cx, pobj, &v.toObject(), &isPrototype)) {
return false;
}
*bp = isPrototype;
return true;
}
inline void JSFunction::trace(JSTracer* trc) {
if (isExtended()) {
TraceRange(trc, ArrayLength(toExtended()->extendedSlots),
(GCPtrValue*)toExtended()->extendedSlots, "nativeReserved");
}
TraceNullableEdge(trc, &atom_, "atom");
if (isInterpreted()) {
if (hasScript() && !hasUncompletedScript()) {
TraceManuallyBarrieredEdge(trc, &u.scripted.s.script_, "script");
} else if (isInterpretedLazy() && u.scripted.s.lazy_) {
TraceManuallyBarrieredEdge(trc, &u.scripted.s.lazy_, "lazyScript");
}
if (u.scripted.env_) {
TraceManuallyBarrieredEdge(trc, &u.scripted.env_, "fun_environment");
}
}
}
static void fun_trace(JSTracer* trc, JSObject* obj) {
obj->as<JSFunction>().trace(trc);
}
static JSObject* CreateFunctionConstructor(JSContext* cx, JSProtoKey key) {
Rooted<GlobalObject*> global(cx, cx->global());
RootedObject functionProto(
cx, &global->getPrototype(JSProto_Function).toObject());
RootedObject functionCtor(
cx, NewFunctionWithProto(
cx, Function, 1, JSFunction::NATIVE_CTOR, nullptr,
HandlePropertyName(cx->names().Function), functionProto,
gc::AllocKind::FUNCTION, SingletonObject));
if (!functionCtor) {
return nullptr;
}
return functionCtor;
}
static bool FunctionPrototype(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setUndefined();
return true;
}
static JSObject* CreateFunctionPrototype(JSContext* cx, JSProtoKey key) {
Rooted<GlobalObject*> self(cx, cx->global());
RootedObject objectProto(cx, &self->getPrototype(JSProto_Object).toObject());
return NewFunctionWithProto(cx, FunctionPrototype, 0, JSFunction::NATIVE_FUN,
nullptr, HandlePropertyName(cx->names().empty),
objectProto, gc::AllocKind::FUNCTION,
SingletonObject);
}
JSString* js::FunctionToStringCache::lookup(JSScript* script) const {
for (size_t i = 0; i < NumEntries; i++) {
if (entries_[i].script == script) {
return entries_[i].string;
}
}
return nullptr;
}
void js::FunctionToStringCache::put(JSScript* script, JSString* string) {
for (size_t i = NumEntries - 1; i > 0; i--) {
entries_[i] = entries_[i - 1];
}
entries_[0].set(script, string);
}
JSString* js::FunctionToString(JSContext* cx, HandleFunction fun,
bool isToSource) {
if (fun->isInterpretedLazy() && !JSFunction::getOrCreateScript(cx, fun)) {
return nullptr;
}
if (IsAsmJSModule(fun)) {
return AsmJSModuleToString(cx, fun, isToSource);
}
if (IsAsmJSFunction(fun)) {
return AsmJSFunctionToString(cx, fun);
}
RootedScript script(cx);
if (fun->hasScript()) {
script = fun->nonLazyScript();
}
bool haveSource = fun->isInterpreted() &&
(fun->isClassConstructor() || !fun->isSelfHostedBuiltin());
bool addParentheses =
haveSource && isToSource && (fun->isLambda() && !fun->isArrow());
if (haveSource && !script->scriptSource()->hasSourceText() &&
!JSScript::loadSource(cx, script->scriptSource(), &haveSource)) {
return nullptr;
}
if (!addParentheses && haveSource) {
FunctionToStringCache& cache = cx->zone()->functionToStringCache();
if (JSString* str = cache.lookup(script)) {
return str;
}
size_t start = script->toStringStart(), end = script->toStringEnd();
JSString* str =
(end - start <= ScriptSource::SourceDeflateLimit)
? script->scriptSource()->substring(cx, start, end)
: script->scriptSource()->substringDontDeflate(cx, start, end);
if (!str) {
return nullptr;
}
cache.put(script, str);
return str;
}
StringBuffer out(cx);
if (addParentheses) {
if (!out.append('(')) {
return nullptr;
}
}
if (haveSource) {
if (!script->appendSourceDataForToString(cx, out)) {
return nullptr;
}
} else if (!isToSource) {
auto hasGetterOrSetterPrefix = [](JSAtom* name) {
auto hasGetterOrSetterPrefix = [](const auto* chars) {
return (chars[0] == 'g' || chars[0] == 's') && chars[1] == 'e' &&
chars[2] == 't' && chars[3] == ' ';
};
JS::AutoCheckCannotGC nogc;
return name->length() >= 4 &&
(name->hasLatin1Chars()
? hasGetterOrSetterPrefix(name->latin1Chars(nogc))
: hasGetterOrSetterPrefix(name->twoByteChars(nogc)));
};
if (!out.append("function")) {
return nullptr;
}
if (fun->explicitName() && !fun->isBoundFunction() &&
(fun->kind() == JSFunction::NormalFunction ||
fun->kind() == JSFunction::ClassConstructor)) {
if (!out.append(' ')) {
return nullptr;
}
JSAtom* name = fun->explicitName();
size_t offset = hasGetterOrSetterPrefix(name) ? 4 : 0;
if (!out.appendSubstring(name, offset, name->length() - offset)) {
return nullptr;
}
}
if (!out.append("() {\n [native code]\n}")) {
return nullptr;
}
} else {
if (fun->isAsync()) {
if (!out.append("async ")) {
return nullptr;
}
}
if (!fun->isArrow()) {
if (!out.append("function")) {
return nullptr;
}
if (fun->isGenerator()) {
if (!out.append('*')) {
return nullptr;
}
}
}
if (fun->explicitName()) {
if (!out.append(' ')) {
return nullptr;
}
if (fun->isBoundFunction()) {
JSLinearString* boundName = JSFunction::getBoundFunctionName(cx, fun);
if (!boundName || !out.append(boundName)) {
return nullptr;
}
} else {
if (!out.append(fun->explicitName())) {
return nullptr;
}
}
}
if (!out.append("() {\n [native code]\n}")) {
return nullptr;
}
}
if (addParentheses) {
if (!out.append(')')) {
return nullptr;
}
}
return out.finishString();
}
JSString* fun_toStringHelper(JSContext* cx, HandleObject obj, bool isToSource) {
if (!obj->is<JSFunction>()) {
if (JSFunToStringOp op = obj->getOpsFunToString()) {
return op(cx, obj, isToSource);
}
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_INCOMPATIBLE_PROTO, js_Function_str,
js_toString_str, "object");
return nullptr;
}
return FunctionToString(cx, obj.as<JSFunction>(), isToSource);
}
bool js::FunctionHasDefaultHasInstance(JSFunction* fun,
const WellKnownSymbols& symbols) {
jsid id = SYMBOL_TO_JSID(symbols.hasInstance);
Shape* shape = fun->lookupPure(id);
if (shape) {
if (!shape->isDataProperty()) {
return false;
}
const Value hasInstance = fun->as<NativeObject>().getSlot(shape->slot());
return IsNativeFunction(hasInstance, fun_symbolHasInstance);
}
return true;
}
bool js::fun_toString(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(IsFunctionObject(args.calleev()));
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj) {
return false;
}
JSString* str = fun_toStringHelper(cx, obj, false);
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
static bool fun_toSource(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(IsFunctionObject(args.calleev()));
RootedObject obj(cx, ToObject(cx, args.thisv()));
if (!obj) {
return false;
}
RootedString str(cx);
if (obj->isCallable()) {
str = fun_toStringHelper(cx, obj, true);
} else {
str = ObjectToSource(cx, obj);
}
if (!str) {
return false;
}
args.rval().setString(str);
return true;
}
bool js::fun_call(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
HandleValue func = args.thisv();
if (!IsCallable(func)) {
ReportIncompatibleMethod(cx, args, &JSFunction::class_);
return false;
}
size_t argCount = args.length();
if (argCount > 0) {
argCount--; }
InvokeArgs iargs(cx);
if (!iargs.init(cx, argCount)) {
return false;
}
for (size_t i = 0; i < argCount; i++) {
iargs[i].set(args[i + 1]);
}
return Call(cx, func, args.get(0), iargs, args.rval());
}
bool js::fun_apply(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
HandleValue fval = args.thisv();
if (!IsCallable(fval)) {
ReportIncompatibleMethod(cx, args, &JSFunction::class_);
return false;
}
if (args.length() < 2 || args[1].isNullOrUndefined()) {
return fun_call(cx, (args.length() > 0) ? 1 : 0, vp);
}
InvokeArgs args2(cx);
if (args[1].isMagic(JS_OPTIMIZED_ARGUMENTS)) {
ScriptFrameIter iter(cx);
MOZ_ASSERT(iter.numActualArgs() <= ARGS_LENGTH_MAX);
if (!args2.init(cx, iter.numActualArgs())) {
return false;
}
iter.unaliasedForEachActual(cx, CopyTo(args2.array()));
} else {
if (!args[1].isObject()) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_BAD_APPLY_ARGS, js_apply_str);
return false;
}
RootedObject aobj(cx, &args[1].toObject());
uint32_t length;
if (!GetLengthProperty(cx, aobj, &length)) {
return false;
}
if (!args2.init(cx, length)) {
return false;
}
MOZ_ASSERT(length <= ARGS_LENGTH_MAX);
if (!GetElements(cx, aobj, length, args2.array())) {
return false;
}
}
return Call(cx, fval, args[0], args2, args.rval());
}
static const JSFunctionSpec function_methods[] = {
JS_FN(js_toSource_str, fun_toSource, 0, 0),
JS_FN(js_toString_str, fun_toString, 0, 0),
JS_FN(js_apply_str, fun_apply, 2, 0),
JS_FN(js_call_str, fun_call, 1, 0),
JS_SELF_HOSTED_FN("bind", "FunctionBind", 2, 0),
JS_SYM_FN(hasInstance, fun_symbolHasInstance, 1,
JSPROP_READONLY | JSPROP_PERMANENT),
JS_FS_END};
static const ClassOps JSFunctionClassOps = {
nullptr,
nullptr,
fun_enumerate, nullptr,
fun_resolve, fun_mayResolve, nullptr,
nullptr,
nullptr, nullptr,
fun_trace,
};
static const ClassSpec JSFunctionClassSpec = {
CreateFunctionConstructor, CreateFunctionPrototype, nullptr, nullptr,
function_methods, function_properties};
const Class JSFunction::class_ = {js_Function_str,
JSCLASS_HAS_CACHED_PROTO(JSProto_Function),
&JSFunctionClassOps, &JSFunctionClassSpec};
const Class* const js::FunctionClassPtr = &JSFunction::class_;
bool JSFunction::isDerivedClassConstructor() {
bool derived;
if (isInterpretedLazy()) {
if (isSelfHostedBuiltin()) {
JSAtom* name =
&getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString()->asAtom();
derived = name == compartment()
->runtimeFromAnyThread()
->commonNames->DefaultDerivedClassConstructor;
} else {
derived = lazyScript()->isDerivedClassConstructor();
}
} else {
derived = nonLazyScript()->isDerivedClassConstructor();
}
MOZ_ASSERT_IF(derived, isClassConstructor());
return derived;
}
bool JSFunction::getLength(JSContext* cx, HandleFunction fun,
uint16_t* length) {
MOZ_ASSERT(!fun->isBoundFunction());
if (fun->isInterpretedLazy() && !getOrCreateScript(cx, fun)) {
return false;
}
*length = fun->isNative() ? fun->nargs() : fun->nonLazyScript()->funLength();
return true;
}
bool JSFunction::getUnresolvedLength(JSContext* cx, HandleFunction fun,
MutableHandleValue v) {
MOZ_ASSERT(!IsInternalFunctionObject(*fun));
MOZ_ASSERT(!fun->hasResolvedLength());
if (fun->isBoundFunction()) {
MOZ_ASSERT(fun->getExtendedSlot(BOUND_FUN_LENGTH_SLOT).isNumber());
v.set(fun->getExtendedSlot(BOUND_FUN_LENGTH_SLOT));
return true;
}
uint16_t length;
if (!JSFunction::getLength(cx, fun, &length)) {
return false;
}
v.setInt32(length);
return true;
}
JSAtom* JSFunction::infallibleGetUnresolvedName(JSContext* cx) {
MOZ_ASSERT(!IsInternalFunctionObject(*this));
MOZ_ASSERT(!hasResolvedName());
if (JSAtom* name = explicitOrInferredName()) {
return name;
}
if (isClassConstructor()) {
return nullptr;
}
return cx->names().empty;
}
bool JSFunction::getUnresolvedName(JSContext* cx, HandleFunction fun,
MutableHandleString v) {
if (fun->isBoundFunction()) {
JSLinearString* name = JSFunction::getBoundFunctionName(cx, fun);
if (!name) {
return false;
}
v.set(name);
return true;
}
v.set(fun->infallibleGetUnresolvedName(cx));
return true;
}
JSLinearString* JSFunction::getBoundFunctionName(JSContext* cx,
HandleFunction fun) {
MOZ_ASSERT(fun->isBoundFunction());
JSAtom* name = fun->explicitName();
MOZ_ASSERT(name);
if (fun->hasBoundFunctionNamePrefix()) {
return name;
}
size_t boundTargets = 0;
for (JSFunction* boundFn = fun; boundFn->isBoundFunction();) {
boundTargets++;
JSObject* target = boundFn->getBoundFunctionTarget();
if (!target->is<JSFunction>()) {
break;
}
boundFn = &target->as<JSFunction>();
}
if (name->empty() && boundTargets == 1) {
return cx->names().boundWithSpace;
}
static constexpr char boundWithSpaceChars[] = "bound ";
static constexpr size_t boundWithSpaceCharsLength =
ArrayLength(boundWithSpaceChars) - 1; MOZ_ASSERT(
StringEqualsAscii(cx->names().boundWithSpace, boundWithSpaceChars));
StringBuffer sb(cx);
if (name->hasTwoByteChars() && !sb.ensureTwoByteChars()) {
return nullptr;
}
CheckedInt<size_t> len(boundTargets);
len *= boundWithSpaceCharsLength;
len += name->length();
if (!len.isValid()) {
ReportAllocationOverflow(cx);
return nullptr;
}
if (!sb.reserve(len.value())) {
return nullptr;
}
while (boundTargets--) {
sb.infallibleAppend(boundWithSpaceChars, boundWithSpaceCharsLength);
}
sb.infallibleAppendSubstring(name, 0, name->length());
return sb.finishString();
}
static const js::Value& BoundFunctionEnvironmentSlotValue(const JSFunction* fun,
uint32_t slotIndex) {
MOZ_ASSERT(fun->isBoundFunction());
MOZ_ASSERT(fun->environment()->is<CallObject>());
CallObject* callObject = &fun->environment()->as<CallObject>();
return callObject->getSlot(slotIndex);
}
JSObject* JSFunction::getBoundFunctionTarget() const {
js::Value targetVal =
BoundFunctionEnvironmentSlotValue(this, JSSLOT_BOUND_FUNCTION_TARGET);
MOZ_ASSERT(IsCallable(targetVal));
return &targetVal.toObject();
}
const js::Value& JSFunction::getBoundFunctionThis() const {
return BoundFunctionEnvironmentSlotValue(this, JSSLOT_BOUND_FUNCTION_THIS);
}
static ArrayObject* GetBoundFunctionArguments(const JSFunction* boundFun) {
js::Value argsVal =
BoundFunctionEnvironmentSlotValue(boundFun, JSSLOT_BOUND_FUNCTION_ARGS);
return &argsVal.toObject().as<ArrayObject>();
}
const js::Value& JSFunction::getBoundFunctionArgument(unsigned which) const {
MOZ_ASSERT(which < getBoundFunctionArgumentCount());
return GetBoundFunctionArguments(this)->getDenseElement(which);
}
size_t JSFunction::getBoundFunctionArgumentCount() const {
return GetBoundFunctionArguments(this)->length();
}
static JSAtom* AppendBoundFunctionPrefix(JSContext* cx, JSString* str) {
static constexpr char boundWithSpaceChars[] = "bound ";
MOZ_ASSERT(
StringEqualsAscii(cx->names().boundWithSpace, boundWithSpaceChars));
StringBuffer sb(cx);
if (!sb.append(boundWithSpaceChars) || !sb.append(str)) {
return nullptr;
}
return sb.finishAtom();
}
bool JSFunction::finishBoundFunctionInit(JSContext* cx, HandleFunction bound,
HandleObject targetObj,
int32_t argCount) {
bound->setIsBoundFunction();
MOZ_ASSERT(bound->getBoundFunctionTarget() == targetObj);
if (targetObj->isConstructor()) {
bound->setIsConstructor();
}
RootedObject proto(cx);
if (!GetPrototype(cx, targetObj, &proto)) {
return false;
}
if (bound->staticPrototype() != proto) {
if (!SetPrototype(cx, bound, proto)) {
return false;
}
}
double length = 0.0;
if (targetObj->is<JSFunction>() &&
!targetObj->as<JSFunction>().hasResolvedLength()) {
RootedValue targetLength(cx);
if (!JSFunction::getUnresolvedLength(cx, targetObj.as<JSFunction>(),
&targetLength)) {
return false;
}
length = Max(0.0, targetLength.toNumber() - argCount);
} else {
bool hasLength;
RootedId idRoot(cx, NameToId(cx->names().length));
if (!HasOwnProperty(cx, targetObj, idRoot, &hasLength)) {
return false;
}
if (hasLength) {
RootedValue targetLength(cx);
if (!GetProperty(cx, targetObj, targetObj, idRoot, &targetLength)) {
return false;
}
if (targetLength.isNumber()) {
length = Max(0.0, JS::ToInteger(targetLength.toNumber()) - argCount);
}
}
}
bound->setExtendedSlot(BOUND_FUN_LENGTH_SLOT, NumberValue(length));
MOZ_ASSERT(!bound->hasGuessedAtom());
JSAtom* name = nullptr;
if (targetObj->is<JSFunction>() &&
!targetObj->as<JSFunction>().hasResolvedName()) {
JSFunction* targetFn = &targetObj->as<JSFunction>();
if (targetFn->isBoundFunction() && targetFn->hasBoundFunctionNamePrefix()) {
name = AppendBoundFunctionPrefix(cx, targetFn->explicitName());
if (!name) {
return false;
}
bound->setPrefixedBoundFunctionName(name);
} else {
name = targetFn->infallibleGetUnresolvedName(cx);
if (name) {
bound->setAtom(name);
}
}
}
if (!name) {
RootedValue targetName(cx);
if (!GetProperty(cx, targetObj, targetObj, cx->names().name, &targetName)) {
return false;
}
if (!targetName.isString()) {
targetName.setString(cx->names().empty);
}
if (targetObj->is<JSFunction>() &&
targetObj->as<JSFunction>().isBoundFunction()) {
name = AppendBoundFunctionPrefix(cx, targetName.toString());
if (!name) {
return false;
}
bound->setPrefixedBoundFunctionName(name);
} else {
name = AtomizeString(cx, targetName.toString());
if (!name) {
return false;
}
bound->setAtom(name);
}
}
return true;
}
bool JSFunction::createScriptForLazilyInterpretedFunction(JSContext* cx,
HandleFunction fun) {
MOZ_ASSERT(fun->isInterpretedLazy());
MOZ_ASSERT(cx->compartment() == fun->compartment());
AutoRealm ar(cx, fun);
Rooted<LazyScript*> lazy(cx, fun->lazyScriptOrNull());
if (lazy) {
RootedScript script(cx, lazy->maybeScript());
bool isBinAST = lazy->scriptSource()->hasBinASTSource();
bool canRelazify = !lazy->numInnerFunctions() && !lazy->hasDirectEval();
if (script) {
fun->setUnlazifiedScript(script);
if (canRelazify) {
script->setLazyScript(lazy);
}
return true;
}
if (fun != lazy->functionNonDelazifying()) {
if (!LazyScript::functionDelazifying(cx, lazy)) {
return false;
}
script = lazy->functionNonDelazifying()->nonLazyScript();
if (!script) {
return false;
}
fun->setUnlazifiedScript(script);
return true;
}
size_t lazyLength = lazy->sourceEnd() - lazy->sourceStart();
if (isBinAST) {
#if defined(JS_BUILD_BINAST)
if (!frontend::CompileLazyBinASTFunction(
cx, lazy,
lazy->scriptSource()->binASTSource() + lazy->sourceStart(),
lazyLength)) {
MOZ_ASSERT(fun->isInterpretedLazy());
MOZ_ASSERT(fun->lazyScript() == lazy);
MOZ_ASSERT(!lazy->hasScript());
return false;
}
#else
MOZ_CRASH("Trying to delazify BinAST function in non-BinAST build");
#endif
} else {
MOZ_ASSERT(lazy->scriptSource()->hasSourceText());
UncompressedSourceCache::AutoHoldEntry holder;
if (lazy->scriptSource()->hasSourceType<Utf8Unit>()) {
ScriptSource::PinnedUnits<Utf8Unit> units(
cx, lazy->scriptSource(), holder, lazy->sourceStart(), lazyLength);
if (!units.get()) {
return false;
}
if (!frontend::CompileLazyFunction(cx, lazy, units.get(), lazyLength)) {
MOZ_ASSERT(fun->isInterpretedLazy());
MOZ_ASSERT(fun->lazyScript() == lazy);
MOZ_ASSERT(!lazy->hasScript());
return false;
}
} else {
MOZ_ASSERT(lazy->scriptSource()->hasSourceType<char16_t>());
ScriptSource::PinnedUnits<char16_t> units(
cx, lazy->scriptSource(), holder, lazy->sourceStart(), lazyLength);
if (!units.get()) {
return false;
}
if (!frontend::CompileLazyFunction(cx, lazy, units.get(), lazyLength)) {
MOZ_ASSERT(fun->isInterpretedLazy());
MOZ_ASSERT(fun->lazyScript() == lazy);
MOZ_ASSERT(!lazy->hasScript());
return false;
}
}
}
script = fun->nonLazyScript();
if (!lazy->maybeScript()) {
lazy->initScript(script);
}
if (canRelazify) {
script->setColumn(lazy->column());
script->setLazyScript(lazy);
}
if (script->scriptSource()->hasEncoder()) {
RootedScriptSourceObject sourceObject(cx, &lazy->sourceObject());
if (!script->scriptSource()->xdrEncodeFunction(cx, fun, sourceObject)) {
return false;
}
}
return true;
}
MOZ_ASSERT(fun->isSelfHostedBuiltin());
RootedAtom funAtom(
cx, &fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString()->asAtom());
if (!funAtom) {
return false;
}
Rooted<PropertyName*> funName(cx, funAtom->asPropertyName());
return cx->runtime()->cloneSelfHostedFunctionScript(cx, funName, fun);
}
void JSFunction::maybeRelazify(JSRuntime* rt) {
if (!hasScript() || !u.scripted.s.script_) {
return;
}
Realm* realm = this->realm();
if (!rt->allowRelazificationForTesting) {
if (realm->compartment()->gcState.hasEnteredRealm) {
return;
}
MOZ_ASSERT(!realm->hasBeenEnteredIgnoringJit());
}
MOZ_ASSERT(!realm->isSelfHostingRealm());
if (realm->isDebuggee()) {
return;
}
if (realm->collectCoverageForDebug()) {
return;
}
if (!u.scripted.s.script_->isRelazifiable()) {
return;
}
if (isSelfHostedBuiltin() &&
(!isExtended() || !getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).isString())) {
return;
}
JSScript* script = nonLazyScript();
flags_ &= ~INTERPRETED;
flags_ |= INTERPRETED_LAZY;
LazyScript* lazy = script->maybeLazyScript();
u.scripted.s.lazy_ = lazy;
if (lazy) {
MOZ_ASSERT(!isSelfHostedBuiltin());
} else {
MOZ_ASSERT(isSelfHostedBuiltin());
MOZ_ASSERT(isExtended());
MOZ_ASSERT(getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString()->isAtom());
}
realm->scheduleDelazificationForDebugger();
}
static bool CreateDynamicFunction(JSContext* cx, const CallArgs& args,
GeneratorKind generatorKind,
FunctionAsyncKind asyncKind) {
using namespace frontend;
bool isGenerator = generatorKind == GeneratorKind::Generator;
bool isAsync = asyncKind == FunctionAsyncKind::AsyncFunction;
RootedScript maybeScript(cx);
const char* filename;
unsigned lineno;
bool mutedErrors;
uint32_t pcOffset;
DescribeScriptedCallerForCompilation(cx, &maybeScript, &filename, &lineno,
&pcOffset, &mutedErrors);
const char* introductionType = "Function";
if (isAsync) {
if (isGenerator) {
introductionType = "AsyncGenerator";
} else {
introductionType = "AsyncFunction";
}
} else if (isGenerator) {
introductionType = "GeneratorFunction";
}
const char* introducerFilename = filename;
if (maybeScript && maybeScript->scriptSource()->introducerFilename()) {
introducerFilename = maybeScript->scriptSource()->introducerFilename();
}
CompileOptions options(cx);
options.setMutedErrors(mutedErrors)
.setFileAndLine(filename, 1)
.setNoScriptRval(false)
.setIntroductionInfo(introducerFilename, introductionType, lineno,
maybeScript, pcOffset)
.setScriptOrModule(maybeScript);
StringBuffer sb(cx);
if (isAsync) {
if (!sb.append("async ")) {
return false;
}
}
if (!sb.append("function")) {
return false;
}
if (isGenerator) {
if (!sb.append('*')) {
return false;
}
}
if (!sb.append(" anonymous(")) {
return false;
}
if (args.length() > 1) {
RootedString str(cx);
unsigned n = args.length() - 1;
for (unsigned i = 0; i < n; i++) {
str = ToString<CanGC>(cx, args[i]);
if (!str) {
return false;
}
if (!sb.append(str)) {
return false;
}
if (i < args.length() - 2) {
if (!sb.append(',')) {
return false;
}
}
}
}
if (!sb.append('\n')) {
return false;
}
Maybe<uint32_t> parameterListEnd = Some(uint32_t(sb.length()));
MOZ_ASSERT(FunctionConstructorMedialSigils[0] == ')');
if (!sb.append(FunctionConstructorMedialSigils)) {
return false;
}
if (args.length() > 0) {
RootedString body(cx, ToString<CanGC>(cx, args[args.length() - 1]));
if (!body || !sb.append(body)) {
return false;
}
}
if (!sb.append(FunctionConstructorFinalBrace)) {
return false;
}
if (!sb.ensureTwoByteChars()) {
return false;
}
RootedString functionText(cx, sb.finishString());
if (!functionText) {
return false;
}
Handle<GlobalObject*> global = cx->global();
RootedValue v(cx, StringValue(functionText));
if (!GlobalObject::isRuntimeCodeGenEnabled(cx, v, global)) {
JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
JSMSG_CSP_BLOCKED_FUNCTION);
return false;
}
RootedAtom anonymousAtom(cx, cx->names().anonymous);
RootedObject defaultProto(cx);
if (!GetFunctionPrototype(cx, generatorKind, asyncKind, &defaultProto)) {
return false;
}
RootedObject globalLexical(cx, &global->lexicalEnvironment());
JSFunction::Flags flags =
(isGenerator || isAsync)
? JSFunction::INTERPRETED_LAMBDA_GENERATOR_OR_ASYNC
: JSFunction::INTERPRETED_LAMBDA;
gc::AllocKind allocKind = gc::AllocKind::FUNCTION;
RootedFunction fun(
cx,
NewFunctionWithProto(cx, nullptr, 0, flags, globalLexical, anonymousAtom,
defaultProto, allocKind, TenuredObject));
if (!fun) {
return false;
}
if (!JSFunction::setTypeForScriptedFunction(cx, fun)) {
return false;
}
AutoStableStringChars stableChars(cx);
if (!stableChars.initTwoByte(cx, functionText)) {
return false;
}
mozilla::Range<const char16_t> chars = stableChars.twoByteRange();
SourceOwnership ownership = stableChars.maybeGiveOwnershipToCaller()
? SourceOwnership::TakeOwnership
: SourceOwnership::Borrowed;
SourceText<char16_t> srcBuf;
if (!srcBuf.init(cx, chars.begin().get(), chars.length(), ownership)) {
return false;
}
JSProtoKey protoKey = JSProto_Null;
if (isAsync) {
if (isGenerator) {
if (!CompileStandaloneAsyncGenerator(cx, &fun, options, srcBuf,
parameterListEnd)) {
return false;
}
} else {
if (!CompileStandaloneAsyncFunction(cx, &fun, options, srcBuf,
parameterListEnd)) {
return false;
}
}
} else {
if (isGenerator) {
if (!CompileStandaloneGenerator(cx, &fun, options, srcBuf,
parameterListEnd)) {
return false;
}
} else {
if (!CompileStandaloneFunction(cx, &fun, options, srcBuf,
parameterListEnd)) {
return false;
}
protoKey = JSProto_Function;
}
}
RootedObject proto(cx);
if (!GetPrototypeFromBuiltinConstructor(cx, args, protoKey, &proto)) {
return false;
}
if (proto && !SetPrototype(cx, fun, proto)) {
return false;
}
args.rval().setObject(*fun);
return true;
}
bool js::Function(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CreateDynamicFunction(cx, args, GeneratorKind::NotGenerator,
FunctionAsyncKind::SyncFunction);
}
bool js::Generator(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CreateDynamicFunction(cx, args, GeneratorKind::Generator,
FunctionAsyncKind::SyncFunction);
}
bool js::AsyncFunctionConstructor(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CreateDynamicFunction(cx, args, GeneratorKind::NotGenerator,
FunctionAsyncKind::AsyncFunction);
}
bool js::AsyncGeneratorConstructor(JSContext* cx, unsigned argc, Value* vp) {
CallArgs args = CallArgsFromVp(argc, vp);
return CreateDynamicFunction(cx, args, GeneratorKind::Generator,
FunctionAsyncKind::AsyncFunction);
}
bool JSFunction::isBuiltinFunctionConstructor() {
return maybeNative() == Function || maybeNative() == Generator;
}
bool JSFunction::needsExtraBodyVarEnvironment() const {
MOZ_ASSERT(!isInterpretedLazy());
if (isNative()) {
return false;
}
if (!nonLazyScript()->functionHasExtraBodyVarScope()) {
return false;
}
return nonLazyScript()->functionExtraBodyVarScope()->hasEnvironment();
}
bool JSFunction::needsNamedLambdaEnvironment() const {
MOZ_ASSERT(!isInterpretedLazy());
if (!isNamedLambda()) {
return false;
}
LexicalScope* scope = nonLazyScript()->maybeNamedLambdaScope();
if (!scope) {
return false;
}
return scope->hasEnvironment();
}
JSFunction* js::NewScriptedFunction(
JSContext* cx, unsigned nargs, JSFunction::Flags flags, HandleAtom atom,
HandleObject proto ,
gc::AllocKind allocKind ,
NewObjectKind newKind ,
HandleObject enclosingEnvArg ) {
RootedObject enclosingEnv(cx, enclosingEnvArg);
if (!enclosingEnv) {
enclosingEnv = &cx->global()->lexicalEnvironment();
}
return NewFunctionWithProto(cx, nullptr, nargs, flags, enclosingEnv, atom,
proto, allocKind, newKind);
}
#ifdef DEBUG
static JSObject* SkipEnvironmentObjects(JSObject* env) {
if (!env) {
return nullptr;
}
while (env->is<EnvironmentObject>()) {
env = &env->as<EnvironmentObject>().enclosingEnvironment();
}
return env;
}
static bool NewFunctionEnvironmentIsWellFormed(JSContext* cx,
HandleObject env) {
RootedObject terminatingEnv(cx, SkipEnvironmentObjects(env));
return !terminatingEnv || terminatingEnv == cx->global() ||
terminatingEnv->is<DebugEnvironmentProxy>();
}
#endif
JSFunction* js::NewFunctionWithProto(
JSContext* cx, Native native, unsigned nargs, JSFunction::Flags flags,
HandleObject enclosingEnv, HandleAtom atom, HandleObject proto,
gc::AllocKind allocKind ,
NewObjectKind newKind ) {
MOZ_ASSERT(allocKind == gc::AllocKind::FUNCTION ||
allocKind == gc::AllocKind::FUNCTION_EXTENDED);
MOZ_ASSERT_IF(native, !enclosingEnv);
MOZ_ASSERT(NewFunctionEnvironmentIsWellFormed(cx, enclosingEnv));
JSFunction* fun =
NewObjectWithClassProto<JSFunction>(cx, proto, allocKind, newKind);
if (!fun) {
return nullptr;
}
if (allocKind == gc::AllocKind::FUNCTION_EXTENDED) {
flags = JSFunction::Flags(flags | JSFunction::EXTENDED);
}
fun->setArgCount(uint16_t(nargs));
fun->setFlags(flags);
if (fun->isInterpreted()) {
MOZ_ASSERT(!native);
if (fun->isInterpretedLazy()) {
fun->initLazyScript(nullptr);
} else {
fun->initScript(nullptr);
}
fun->initEnvironment(enclosingEnv);
} else {
MOZ_ASSERT(fun->isNative());
MOZ_ASSERT(native);
if (fun->isWasmOptimized()) {
fun->initWasmNative(native);
} else {
fun->initNative(native, nullptr);
}
}
if (allocKind == gc::AllocKind::FUNCTION_EXTENDED) {
fun->initializeExtended();
}
fun->initAtom(atom);
return fun;
}
bool js::GetFunctionPrototype(JSContext* cx, js::GeneratorKind generatorKind,
js::FunctionAsyncKind asyncKind,
js::MutableHandleObject proto) {
if (generatorKind == js::GeneratorKind::NotGenerator) {
if (asyncKind == js::FunctionAsyncKind::SyncFunction) {
proto.set(nullptr);
return true;
}
proto.set(
GlobalObject::getOrCreateAsyncFunctionPrototype(cx, cx->global()));
} else {
if (asyncKind == js::FunctionAsyncKind::SyncFunction) {
proto.set(GlobalObject::getOrCreateGeneratorFunctionPrototype(
cx, cx->global()));
} else {
proto.set(GlobalObject::getOrCreateAsyncGenerator(cx, cx->global()));
}
}
return !!proto;
}
bool js::CanReuseScriptForClone(JS::Realm* realm, HandleFunction fun,
HandleObject newParent) {
MOZ_ASSERT(fun->isInterpreted());
if (realm != fun->realm() || fun->isSingleton() ||
ObjectGroup::useSingletonForClone(fun)) {
return false;
}
if (newParent->is<GlobalObject>()) {
return true;
}
if (IsSyntacticEnvironment(newParent)) {
return true;
}
return fun->hasScript() ? fun->nonLazyScript()->hasNonSyntacticScope()
: fun->lazyScript()->hasNonSyntacticScope();
}
static inline JSFunction* NewFunctionClone(JSContext* cx, HandleFunction fun,
NewObjectKind newKind,
gc::AllocKind allocKind,
HandleObject proto) {
RootedObject cloneProto(cx, proto);
if (!proto) {
if (!GetFunctionPrototype(cx, fun->generatorKind(), fun->asyncKind(),
&cloneProto)) {
return nullptr;
}
}
RootedFunction clone(cx);
clone =
NewObjectWithClassProto<JSFunction>(cx, cloneProto, allocKind, newKind);
if (!clone) {
return nullptr;
}
constexpr uint16_t NonCloneableFlags = JSFunction::EXTENDED |
JSFunction::RESOLVED_LENGTH |
JSFunction::RESOLVED_NAME;
uint16_t flags = fun->flags() & ~NonCloneableFlags;
if (allocKind == gc::AllocKind::FUNCTION_EXTENDED) {
flags |= JSFunction::EXTENDED;
}
clone->setArgCount(fun->nargs());
clone->setFlags(flags);
JSAtom* atom = fun->displayAtom();
if (atom) {
cx->markAtom(atom);
}
clone->initAtom(atom);
if (allocKind == gc::AllocKind::FUNCTION_EXTENDED) {
if (fun->isExtended() && fun->compartment() == cx->compartment()) {
for (unsigned i = 0; i < FunctionExtended::NUM_EXTENDED_SLOTS; i++) {
clone->initExtendedSlot(i, fun->getExtendedSlot(i));
}
} else {
clone->initializeExtended();
}
}
return clone;
}
JSFunction* js::CloneFunctionReuseScript(
JSContext* cx, HandleFunction fun, HandleObject enclosingEnv,
gc::AllocKind allocKind ,
NewObjectKind newKind ,
HandleObject proto ) {
MOZ_ASSERT(cx->realm() == fun->realm());
MOZ_ASSERT(NewFunctionEnvironmentIsWellFormed(cx, enclosingEnv));
MOZ_ASSERT(fun->isInterpreted());
MOZ_ASSERT(!fun->isBoundFunction());
MOZ_ASSERT(CanReuseScriptForClone(cx->realm(), fun, enclosingEnv));
RootedFunction clone(cx,
NewFunctionClone(cx, fun, newKind, allocKind, proto));
if (!clone) {
return nullptr;
}
if (fun->hasScript()) {
clone->initScript(fun->nonLazyScript());
clone->initEnvironment(enclosingEnv);
} else {
MOZ_ASSERT(fun->isInterpretedLazy());
MOZ_ASSERT(fun->compartment() == clone->compartment());
LazyScript* lazy = fun->lazyScriptOrNull();
clone->initLazyScript(lazy);
clone->initEnvironment(enclosingEnv);
}
if (fun->staticPrototype() == clone->staticPrototype()) {
clone->setGroup(fun->group());
}
return clone;
}
JSFunction* js::CloneFunctionAndScript(JSContext* cx, HandleFunction fun,
HandleObject enclosingEnv,
HandleScope newScope,
Handle<ScriptSourceObject*> sourceObject,
gc::AllocKind allocKind ,
HandleObject proto ) {
MOZ_ASSERT(NewFunctionEnvironmentIsWellFormed(cx, enclosingEnv));
MOZ_ASSERT(fun->isInterpreted());
MOZ_ASSERT(!fun->isBoundFunction());
JSScript::AutoDelazify funScript(cx, fun);
if (!funScript) {
return nullptr;
}
RootedFunction clone(
cx, NewFunctionClone(cx, fun, SingletonObject, allocKind, proto));
if (!clone) {
return nullptr;
}
clone->initScript(nullptr);
clone->initEnvironment(enclosingEnv);
#ifdef DEBUG
RootedObject terminatingEnv(cx, enclosingEnv);
while (IsSyntacticEnvironment(terminatingEnv)) {
terminatingEnv = terminatingEnv->enclosingEnvironment();
}
MOZ_ASSERT_IF(!terminatingEnv->is<GlobalObject>(),
newScope->hasOnChain(ScopeKind::NonSyntactic));
#endif
RootedScript script(cx, fun->nonLazyScript());
MOZ_ASSERT(script->realm() == fun->realm());
MOZ_ASSERT(cx->compartment() == clone->compartment(),
"Otherwise we could relazify clone below!");
RootedScript clonedScript(
cx, CloneScriptIntoFunction(cx, newScope, clone, script, sourceObject));
if (!clonedScript) {
return nullptr;
}
Debugger::onNewScript(cx, clonedScript);
return clone;
}
JSFunction* js::CloneAsmJSModuleFunction(JSContext* cx, HandleFunction fun) {
MOZ_ASSERT(fun->isNative());
MOZ_ASSERT(IsAsmJSModule(fun));
MOZ_ASSERT(fun->isExtended());
MOZ_ASSERT(cx->compartment() == fun->compartment());
JSFunction* clone =
NewFunctionClone(cx, fun, GenericObject, gc::AllocKind::FUNCTION_EXTENDED,
nullptr);
if (!clone) {
return nullptr;
}
MOZ_ASSERT(fun->native() == InstantiateAsmJS);
MOZ_ASSERT(!fun->hasJitInfo());
clone->initNative(InstantiateAsmJS, nullptr);
clone->setGroup(fun->group());
return clone;
}
JSFunction* js::CloneSelfHostingIntrinsic(JSContext* cx, HandleFunction fun) {
MOZ_ASSERT(fun->isNative());
MOZ_ASSERT(fun->realm()->isSelfHostingRealm());
MOZ_ASSERT(!fun->isExtended());
MOZ_ASSERT(cx->compartment() != fun->compartment());
JSFunction* clone =
NewFunctionClone(cx, fun, SingletonObject, gc::AllocKind::FUNCTION,
nullptr);
if (!clone) {
return nullptr;
}
clone->initNative(fun->native(),
fun->hasJitInfo() ? fun->jitInfo() : nullptr);
return clone;
}
static JSAtom* SymbolToFunctionName(JSContext* cx, JS::Symbol* symbol,
FunctionPrefixKind prefixKind) {
JSAtom* desc = symbol->description();
if (!desc && prefixKind == FunctionPrefixKind::None) {
return cx->names().empty;
}
StringBuffer sb(cx);
if (prefixKind == FunctionPrefixKind::Get) {
if (!sb.append("get ")) {
return nullptr;
}
} else if (prefixKind == FunctionPrefixKind::Set) {
if (!sb.append("set ")) {
return nullptr;
}
}
if (desc) {
if (!sb.append('[') || !sb.append(desc) || !sb.append(']')) {
return nullptr;
}
}
return sb.finishAtom();
}
static JSAtom* NameToFunctionName(JSContext* cx, HandleValue name,
FunctionPrefixKind prefixKind) {
MOZ_ASSERT(name.isString() || name.isNumber());
if (prefixKind == FunctionPrefixKind::None) {
return ToAtom<CanGC>(cx, name);
}
JSString* nameStr = ToString(cx, name);
if (!nameStr) {
return nullptr;
}
StringBuffer sb(cx);
if (prefixKind == FunctionPrefixKind::Get) {
if (!sb.append("get ")) {
return nullptr;
}
} else {
if (!sb.append("set ")) {
return nullptr;
}
}
if (!sb.append(nameStr)) {
return nullptr;
}
return sb.finishAtom();
}
JSAtom* js::IdToFunctionName(
JSContext* cx, HandleId id,
FunctionPrefixKind prefixKind ) {
MOZ_ASSERT(JSID_IS_STRING(id) || JSID_IS_SYMBOL(id) || JSID_IS_INT(id));
if (JSID_IS_ATOM(id) && prefixKind == FunctionPrefixKind::None) {
return JSID_TO_ATOM(id);
}
if (JSID_IS_SYMBOL(id)) {
return SymbolToFunctionName(cx, JSID_TO_SYMBOL(id), prefixKind);
}
RootedValue idv(cx, IdToValue(id));
return NameToFunctionName(cx, idv, prefixKind);
}
bool js::SetFunctionName(JSContext* cx, HandleFunction fun, HandleValue name,
FunctionPrefixKind prefixKind) {
MOZ_ASSERT(name.isString() || name.isSymbol() || name.isNumber());
if (fun->hasInferredName()) {
MOZ_ASSERT(fun->isSingleton());
fun->clearInferredName();
}
MOZ_ASSERT(!fun->containsPure(cx->names().name));
MOZ_ASSERT(!fun->hasResolvedName());
JSAtom* funName = name.isSymbol()
? SymbolToFunctionName(cx, name.toSymbol(), prefixKind)
: NameToFunctionName(cx, name, prefixKind);
if (!funName) {
return false;
}
fun->setInferredName(funName);
return true;
}
JSFunction* js::DefineFunction(
JSContext* cx, HandleObject obj, HandleId id, Native native, unsigned nargs,
unsigned flags, gc::AllocKind allocKind ) {
RootedAtom atom(cx, IdToFunctionName(cx, id));
if (!atom) {
return nullptr;
}
RootedFunction fun(cx);
if (!native) {
fun = NewScriptedFunction(cx, nargs, JSFunction::INTERPRETED_LAZY, atom,
nullptr, allocKind, GenericObject,
obj);
} else if (flags & JSFUN_CONSTRUCTOR) {
fun = NewNativeConstructor(cx, native, nargs, atom, allocKind);
} else {
fun = NewNativeFunction(cx, native, nargs, atom, allocKind);
}
if (!fun) {
return nullptr;
}
RootedValue funVal(cx, ObjectValue(*fun));
if (!DefineDataProperty(cx, obj, id, funVal, flags & ~JSFUN_FLAGS_MASK)) {
return nullptr;
}
return fun;
}
void js::ReportIncompatibleMethod(JSContext* cx, const CallArgs& args,
const Class* clasp) {
RootedValue thisv(cx, args.thisv());
#ifdef DEBUG
switch (thisv.type()) {
case ValueType::Object:
MOZ_ASSERT(thisv.toObject().getClass() != clasp ||
!thisv.toObject().isNative() ||
!thisv.toObject().staticPrototype() ||
thisv.toObject().staticPrototype()->getClass() != clasp);
break;
case ValueType::String:
MOZ_ASSERT(clasp != &StringObject::class_);
break;
case ValueType::Double:
case ValueType::Int32:
MOZ_ASSERT(clasp != &NumberObject::class_);
break;
case ValueType::Boolean:
MOZ_ASSERT(clasp != &BooleanObject::class_);
break;
case ValueType::Symbol:
MOZ_ASSERT(clasp != &SymbolObject::class_);
break;
case ValueType::BigInt:
MOZ_ASSERT(clasp != &BigIntObject::class_);
break;
case ValueType::Undefined:
case ValueType::Null:
break;
case ValueType::Magic:
case ValueType::PrivateGCThing:
MOZ_CRASH("unexpected type");
}
#endif
if (JSFunction* fun = ReportIfNotFunction(cx, args.calleev())) {
UniqueChars funNameBytes;
if (const char* funName = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_INCOMPATIBLE_PROTO, clasp->name, funName,
InformalValueTypeName(thisv));
}
}
}
void js::ReportIncompatible(JSContext* cx, const CallArgs& args) {
if (JSFunction* fun = ReportIfNotFunction(cx, args.calleev())) {
UniqueChars funNameBytes;
if (const char* funName = GetFunctionNameBytes(cx, fun, &funNameBytes)) {
JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
JSMSG_INCOMPATIBLE_METHOD, funName, "method",
InformalValueTypeName(args.thisv()));
}
}
}
namespace JS {
namespace detail {
JS_PUBLIC_API void CheckIsValidConstructible(const Value& calleev) {
MOZ_ASSERT(calleev.toObject().isConstructor());
}
} }