blob: 34f483502f952d308d6bda289675f7bc8f5e43a7 [file] [log] [blame]
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* vim: set ts=8 sts=4 et sw=4 tw=99:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "vm/SelfHosting.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Casting.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/Maybe.h"
#include "jscntxt.h"
#include "jscompartment.h"
#include "jsdate.h"
#include "jsfriendapi.h"
#include "jshashutil.h"
#include "jsweakmap.h"
#include "jswrapper.h"
#include "selfhosted.out.h"
#include "builtin/Intl.h"
#include "builtin/MapObject.h"
#include "builtin/ModuleObject.h"
#include "builtin/Object.h"
#include "builtin/Reflect.h"
#include "builtin/SelfHostingDefines.h"
#include "builtin/SIMD.h"
#include "builtin/TypedObject.h"
#include "builtin/WeakSetObject.h"
#include "gc/Marking.h"
#include "jit/AtomicOperations.h"
#include "jit/InlinableNatives.h"
#include "js/Date.h"
#include "vm/Compression.h"
#include "vm/GeneratorObject.h"
#include "vm/Interpreter.h"
#include "vm/String.h"
#include "vm/TypedArrayCommon.h"
#include "jsfuninlines.h"
#include "jsscriptinlines.h"
#include "vm/BooleanObject-inl.h"
#include "vm/NativeObject-inl.h"
#include "vm/NumberObject-inl.h"
#include "vm/StringObject-inl.h"
using namespace js;
using namespace js::selfhosted;
using JS::AutoCheckCannotGC;
using mozilla::IsInRange;
using mozilla::Maybe;
using mozilla::PodMove;
using mozilla::UniquePtr;
using mozilla::Maybe;
static void
selfHosting_ErrorReporter(JSContext* cx, const char* message, JSErrorReport* report)
{
PrintError(cx, stderr, message, report, true);
}
static bool
intrinsic_ToObject(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue val(cx, args[0]);
RootedObject obj(cx, ToObject(cx, val));
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
static bool
intrinsic_IsObject(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
Value val = args[0];
bool isObject = val.isObject();
args.rval().setBoolean(isObject);
return true;
}
static bool
intrinsic_ToInteger(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
double result;
if (!ToInteger(cx, args[0], &result))
return false;
args.rval().setNumber(result);
return true;
}
static bool
intrinsic_ToString(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedString str(cx);
str = ToString<CanGC>(cx, args[0]);
if (!str)
return false;
args.rval().setString(str);
return true;
}
static bool
intrinsic_ToPropertyKey(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedId id(cx);
if (!ValueToId<CanGC>(cx, args[0], &id))
return false;
args.rval().set(IdToValue(id));
return true;
}
static bool
intrinsic_IsCallable(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(IsCallable(args[0]));
return true;
}
static bool
intrinsic_IsConstructor(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(IsConstructor(args[0]));
return true;
}
template<typename T>
static bool
intrinsic_IsInstanceOfBuiltin(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
args.rval().setBoolean(args[0].toObject().is<T>());
return true;
}
static bool
intrinsic_SubstringKernel(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args[0].isString());
MOZ_ASSERT(args[1].isInt32());
MOZ_ASSERT(args[2].isInt32());
RootedString str(cx, args[0].toString());
int32_t begin = args[1].toInt32();
int32_t length = args[2].toInt32();
JSString* substr = SubstringKernel(cx, str, begin, length);
if (!substr)
return false;
args.rval().setString(substr);
return true;
}
static bool
intrinsic_OwnPropertyKeys(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[1].isInt32());
return GetOwnPropertyKeys(cx, args, args[1].toInt32());
}
static void
ThrowErrorWithType(JSContext* cx, JSExnType type, const CallArgs& args)
{
uint32_t errorNumber = args[0].toInt32();
#ifdef DEBUG
const JSErrorFormatString* efs = GetErrorMessage(nullptr, errorNumber);
MOZ_ASSERT(efs->argCount == args.length() - 1);
MOZ_ASSERT(efs->exnType == type, "error-throwing intrinsic and error number are inconsistent");
#endif
JSAutoByteString errorArgs[3];
for (unsigned i = 1; i < 4 && i < args.length(); i++) {
RootedValue val(cx, args[i]);
if (val.isInt32()) {
JSString* str = ToString<CanGC>(cx, val);
if (!str)
return;
errorArgs[i - 1].encodeLatin1(cx, str);
} else if (val.isString()) {
errorArgs[i - 1].encodeLatin1(cx, val.toString());
} else {
UniquePtr<char[], JS::FreePolicy> bytes =
DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, nullptr);
if (!bytes)
return;
errorArgs[i - 1].initBytes(bytes.release());
}
if (!errorArgs[i - 1])
return;
}
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, errorNumber,
errorArgs[0].ptr(), errorArgs[1].ptr(), errorArgs[2].ptr());
}
static bool
intrinsic_ThrowRangeError(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() >= 1);
ThrowErrorWithType(cx, JSEXN_RANGEERR, args);
return false;
}
static bool
intrinsic_ThrowTypeError(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() >= 1);
ThrowErrorWithType(cx, JSEXN_TYPEERR, args);
return false;
}
static bool
intrinsic_ThrowSyntaxError(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() >= 1);
ThrowErrorWithType(cx, JSEXN_SYNTAXERR, args);
return false;
}
/**
* Handles an assertion failure in self-hosted code just like an assertion
* failure in C++ code. Information about the failure can be provided in args[0].
*/
static bool
intrinsic_AssertionFailed(JSContext* cx, unsigned argc, Value* vp)
{
#ifdef DEBUG
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 0) {
// try to dump the informative string
JSString* str = ToString<CanGC>(cx, args[0]);
if (str) {
fprintf(stderr, "Self-hosted JavaScript assertion info: ");
str->dumpCharsNoNewline();
fputc('\n', stderr);
}
}
#endif
MOZ_ASSERT(false);
return false;
}
static bool
intrinsic_MakeConstructible(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[0].toObject().is<JSFunction>());
MOZ_ASSERT(args[0].toObject().as<JSFunction>().isSelfHostedBuiltin());
MOZ_ASSERT(args[1].isObject());
// Normal .prototype properties aren't enumerable. But for this to clone
// correctly, it must be enumerable.
RootedObject ctor(cx, &args[0].toObject());
if (!DefineProperty(cx, ctor, cx->names().prototype, args[1],
nullptr, nullptr,
JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT))
{
return false;
}
ctor->as<JSFunction>().setIsConstructor();
args.rval().setUndefined();
return true;
}
/*
* Used to decompile values in the nearest non-builtin stack frame, falling
* back to decompiling in the current frame. Helpful for printing higher-order
* function arguments.
*
* The user must supply the argument number of the value in question; it
* _cannot_ be automatically determined.
*/
static bool
intrinsic_DecompileArg(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
RootedValue value(cx, args[1]);
ScopedJSFreePtr<char> str(DecompileArgument(cx, args[0].toInt32(), value));
if (!str)
return false;
RootedAtom atom(cx, Atomize(cx, str, strlen(str)));
if (!atom)
return false;
args.rval().setString(atom);
return true;
}
static bool
intrinsic_DefineDataProperty(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() >= 3);
MOZ_ASSERT(args[0].isObject());
RootedObject obj(cx, &args[0].toObject());
RootedId id(cx);
if (!ValueToId<CanGC>(cx, args[1], &id))
return false;
RootedValue value(cx, args[2]);
unsigned attrs = 0;
if (args.length() >= 4) {
unsigned attributes = args[3].toInt32();
MOZ_ASSERT(bool(attributes & ATTR_ENUMERABLE) != bool(attributes & ATTR_NONENUMERABLE),
"_DefineDataProperty must receive either ATTR_ENUMERABLE xor ATTR_NONENUMERABLE");
if (attributes & ATTR_ENUMERABLE)
attrs |= JSPROP_ENUMERATE;
MOZ_ASSERT(bool(attributes & ATTR_CONFIGURABLE) != bool(attributes & ATTR_NONCONFIGURABLE),
"_DefineDataProperty must receive either ATTR_CONFIGURABLE xor "
"ATTR_NONCONFIGURABLE");
if (attributes & ATTR_NONCONFIGURABLE)
attrs |= JSPROP_PERMANENT;
MOZ_ASSERT(bool(attributes & ATTR_WRITABLE) != bool(attributes & ATTR_NONWRITABLE),
"_DefineDataProperty must receive either ATTR_WRITABLE xor ATTR_NONWRITABLE");
if (attributes & ATTR_NONWRITABLE)
attrs |= JSPROP_READONLY;
} else {
// If the fourth argument is unspecified, the attributes are for a
// plain data property.
attrs = JSPROP_ENUMERATE;
}
Rooted<PropertyDescriptor> desc(cx);
desc.setDataDescriptor(value, attrs);
if (!DefineProperty(cx, obj, id, desc))
return false;
args.rval().setUndefined();
return true;
}
static bool
intrinsic_UnsafeSetReservedSlot(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[1].isInt32());
args[0].toObject().as<NativeObject>().setReservedSlot(args[1].toPrivateUint32(), args[2]);
args.rval().setUndefined();
return true;
}
static bool
intrinsic_UnsafeGetReservedSlot(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(args[1].isInt32());
args.rval().set(args[0].toObject().as<NativeObject>().getReservedSlot(args[1].toPrivateUint32()));
return true;
}
static bool
intrinsic_UnsafeGetObjectFromReservedSlot(JSContext* cx, unsigned argc, Value* vp)
{
if (!intrinsic_UnsafeGetReservedSlot(cx, argc, vp))
return false;
MOZ_ASSERT(vp->isObject());
return true;
}
static bool
intrinsic_UnsafeGetInt32FromReservedSlot(JSContext* cx, unsigned argc, Value* vp)
{
if (!intrinsic_UnsafeGetReservedSlot(cx, argc, vp))
return false;
MOZ_ASSERT(vp->isInt32());
return true;
}
static bool
intrinsic_UnsafeGetStringFromReservedSlot(JSContext* cx, unsigned argc, Value* vp)
{
if (!intrinsic_UnsafeGetReservedSlot(cx, argc, vp))
return false;
MOZ_ASSERT(vp->isString());
return true;
}
static bool
intrinsic_UnsafeGetBooleanFromReservedSlot(JSContext* cx, unsigned argc, Value* vp)
{
if (!intrinsic_UnsafeGetReservedSlot(cx, argc, vp))
return false;
MOZ_ASSERT(vp->isBoolean());
return true;
}
static bool
intrinsic_IsPackedArray(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
JSObject* obj = &args[0].toObject();
bool isPacked = obj->is<ArrayObject>() && !obj->hasLazyGroup() &&
!obj->group()->hasAllFlags(OBJECT_FLAG_NON_PACKED) &&
obj->as<ArrayObject>().getDenseInitializedLength() ==
obj->as<ArrayObject>().length();
args.rval().setBoolean(isPacked);
return true;
}
static bool
intrinsic_GetIteratorPrototype(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
JSObject* obj = GlobalObject::getOrCreateIteratorPrototype(cx, cx->global());
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
static bool
intrinsic_NewArrayIterator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedObject proto(cx, GlobalObject::getOrCreateArrayIteratorPrototype(cx, cx->global()));
if (!proto)
return false;
JSObject* obj = NewObjectWithGivenProto(cx, &ArrayIteratorObject::class_, proto);
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
static bool
intrinsic_GetNextMapEntryForIterator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].toObject().is<MapIteratorObject>());
MOZ_ASSERT(args[1].isObject());
Rooted<MapIteratorObject*> mapIterator(cx, &args[0].toObject().as<MapIteratorObject>());
RootedArrayObject result(cx, &args[1].toObject().as<ArrayObject>());
args.rval().setBoolean(MapIteratorObject::next(cx, mapIterator, result));
return true;
}
static bool
intrinsic_NewStringIterator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedObject proto(cx, GlobalObject::getOrCreateStringIteratorPrototype(cx, cx->global()));
if (!proto)
return false;
JSObject* obj = NewObjectWithGivenProto(cx, &StringIteratorObject::class_, proto);
if (!obj)
return false;
args.rval().setObject(*obj);
return true;
}
static bool
intrinsic_NewListIterator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
RootedObject proto(cx, GlobalObject::getOrCreateIteratorPrototype(cx, cx->global()));
if (!proto)
return false;
RootedObject iterator(cx);
iterator = NewObjectWithGivenProto(cx, &ListIteratorObject::class_, proto);
if (!iterator)
return false;
args.rval().setObject(*iterator);
return true;
}
static bool
intrinsic_ActiveFunction(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
ScriptFrameIter iter(cx);
MOZ_ASSERT(iter.isFunctionFrame());
args.rval().setObject(*iter.callee(cx));
return true;
}
static bool
intrinsic_StarGeneratorObjectIsClosed(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
StarGeneratorObject* genObj = &args[0].toObject().as<StarGeneratorObject>();
args.rval().setBoolean(genObj->isClosed());
return true;
}
bool
js::intrinsic_IsSuspendedStarGenerator(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
if (!args[0].isObject() || !args[0].toObject().is<StarGeneratorObject>()) {
args.rval().setBoolean(false);
return true;
}
StarGeneratorObject& genObj = args[0].toObject().as<StarGeneratorObject>();
args.rval().setBoolean(!genObj.isClosed() && genObj.isSuspended());
return true;
}
static bool
intrinsic_LegacyGeneratorObjectIsClosed(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
LegacyGeneratorObject* genObj = &args[0].toObject().as<LegacyGeneratorObject>();
args.rval().setBoolean(genObj->isClosed());
return true;
}
static bool
intrinsic_CloseClosingLegacyGeneratorObject(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
LegacyGeneratorObject* genObj = &args[0].toObject().as<LegacyGeneratorObject>();
MOZ_ASSERT(genObj->isClosing());
genObj->setClosed();
return true;
}
static bool
intrinsic_ThrowStopIteration(JSContext* cx, unsigned argc, Value* vp)
{
MOZ_ASSERT(CallArgsFromVp(argc, vp).length() == 0);
return ThrowStopIteration(cx);
}
static bool
intrinsic_GeneratorIsRunning(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
GeneratorObject* genObj = &args[0].toObject().as<GeneratorObject>();
args.rval().setBoolean(genObj->isRunning() || genObj->isClosing());
return true;
}
static bool
intrinsic_GeneratorSetClosed(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
GeneratorObject* genObj = &args[0].toObject().as<GeneratorObject>();
genObj->setClosed();
return true;
}
static bool
intrinsic_IsPossiblyWrappedTypedArray(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
bool isTypedArray = false;
if (args[0].isObject()) {
JSObject* obj = CheckedUnwrap(&args[0].toObject());
if (!obj) {
JS_ReportError(cx, "Permission denied to access object");
return false;
}
isTypedArray = obj->is<TypedArrayObject>();
}
args.rval().setBoolean(isTypedArray);
return true;
}
static bool
intrinsic_TypedArrayBuffer(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(TypedArrayObject::is(args[0]));
Rooted<TypedArrayObject*> tarray(cx, &args[0].toObject().as<TypedArrayObject>());
if (!TypedArrayObject::ensureHasBuffer(cx, tarray))
return false;
args.rval().set(TypedArrayObject::bufferValue(tarray));
return true;
}
static bool
intrinsic_TypedArrayByteOffset(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(TypedArrayObject::is(args[0]));
args.rval().set(TypedArrayObject::byteOffsetValue(&args[0].toObject().as<TypedArrayObject>()));
return true;
}
static bool
intrinsic_TypedArrayElementShift(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(TypedArrayObject::is(args[0]));
unsigned shift = TypedArrayShift(args[0].toObject().as<TypedArrayObject>().type());
MOZ_ASSERT(shift == 0 || shift == 1 || shift == 2 || shift == 3);
args.rval().setInt32(mozilla::AssertedCast<int32_t>(shift));
return true;
}
// Return the value of [[ArrayLength]] internal slot of the TypedArray
static bool
intrinsic_TypedArrayLength(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedObject obj(cx, &args[0].toObject());
MOZ_ASSERT(obj->is<TypedArrayObject>());
args.rval().setInt32(obj->as<TypedArrayObject>().length());
return true;
}
static bool
intrinsic_MoveTypedArrayElements(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 4);
Rooted<TypedArrayObject*> tarray(cx, &args[0].toObject().as<TypedArrayObject>());
uint32_t to = uint32_t(args[1].toInt32());
uint32_t from = uint32_t(args[2].toInt32());
uint32_t count = uint32_t(args[3].toInt32());
MOZ_ASSERT(count > 0,
"don't call this method if copying no elements, because then "
"the not-neutered requirement is wrong");
if (tarray->isNeutered() && tarray->hasBuffer()) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_BAD_ARGS);
return false;
}
// Don't multiply by |tarray->bytesPerElement()| in case the compiler can't
// strength-reduce multiplication by 1/2/4/8 into the equivalent shift.
const size_t ElementShift = TypedArrayShift(tarray->type());
MOZ_ASSERT((UINT32_MAX >> ElementShift) > to);
uint32_t byteDest = to << ElementShift;
MOZ_ASSERT((UINT32_MAX >> ElementShift) > from);
uint32_t byteSrc = from << ElementShift;
MOZ_ASSERT((UINT32_MAX >> ElementShift) >= count);
uint32_t byteSize = count << ElementShift;
#ifdef DEBUG
{
uint32_t viewByteLength = tarray->byteLength();
MOZ_ASSERT(byteSize <= viewByteLength);
MOZ_ASSERT(byteDest < viewByteLength);
MOZ_ASSERT(byteSrc < viewByteLength);
MOZ_ASSERT(byteDest <= viewByteLength - byteSize);
MOZ_ASSERT(byteSrc <= viewByteLength - byteSize);
}
#endif
SharedMem<uint8_t*> data = tarray->viewDataEither().cast<uint8_t*>();
jit::AtomicOperations::memmoveSafeWhenRacy(data + byteDest, data + byteSrc, byteSize);
args.rval().setUndefined();
return true;
}
// Extract the TypedArrayObject* underlying |obj| and return it. This method,
// in a TOTALLY UNSAFE manner, completely violates the normal compartment
// boundaries, returning an object not necessarily in the current compartment
// or in |obj|'s compartment.
//
// All callers of this method are expected to sigil this TypedArrayObject*, and
// all values and information derived from it, with an "unsafe" prefix, to
// indicate the extreme caution required when dealing with such values.
//
// If calling code discipline ever fails to be maintained, it's gonna have a
// bad time.
static TypedArrayObject*
DangerouslyUnwrapTypedArray(JSContext* cx, JSObject* obj)
{
// An unwrapped pointer to an object potentially on the other side of a
// compartment boundary! Isn't this such fun?
JSObject* unwrapped = CheckedUnwrap(obj);
if (!unwrapped->is<TypedArrayObject>()) {
// By *appearances* this can't happen, as self-hosted TypedArraySet
// checked this. But. Who's to say a GC couldn't happen between
// the check that this value was a typed array, and this extraction
// occurring? A GC might turn a cross-compartment wrapper |obj| into
// |unwrapped == obj|, a dead object no longer connected its typed
// array.
//
// Yeah, yeah, it's pretty unlikely. Are you willing to stake a
// sec-critical bug on that assessment, now and forever, against
// all changes those pesky GC and JIT people might make?
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEAD_OBJECT);
return nullptr;
}
// Be super-duper careful using this, as we've just punched through
// the compartment boundary, and things like buffer() on this aren't
// same-compartment with anything else in the calling method.
return &unwrapped->as<TypedArrayObject>();
}
// ES6 draft 20150403 22.2.3.22.2, steps 12-24, 29.
static bool
intrinsic_SetFromTypedArrayApproach(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 4);
Rooted<TypedArrayObject*> target(cx, &args[0].toObject().as<TypedArrayObject>());
MOZ_ASSERT(!target->hasBuffer() || !target->isNeutered(),
"something should have defended against a neutered target");
// As directed by |DangerouslyUnwrapTypedArray|, sigil this pointer and all
// variables derived from it to counsel extreme caution here.
Rooted<TypedArrayObject*> unsafeTypedArrayCrossCompartment(cx);
unsafeTypedArrayCrossCompartment = DangerouslyUnwrapTypedArray(cx, &args[1].toObject());
if (!unsafeTypedArrayCrossCompartment)
return false;
double doubleTargetOffset = args[2].toNumber();
MOZ_ASSERT(doubleTargetOffset >= 0, "caller failed to ensure |targetOffset >= 0|");
uint32_t targetLength = uint32_t(args[3].toInt32());
// Handle all checks preceding the actual element-setting. A visual skim
// of 22.2.3.22.2 should confirm these are the only steps after steps 1-11
// that might abort processing (other than for reason of internal error.)
// Steps 12-13.
if (unsafeTypedArrayCrossCompartment->hasBuffer() &&
unsafeTypedArrayCrossCompartment->isNeutered())
{
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_TYPED_ARRAY_DETACHED);
return false;
}
// Steps 21, 23.
uint32_t unsafeSrcLengthCrossCompartment = unsafeTypedArrayCrossCompartment->length();
if (unsafeSrcLengthCrossCompartment + doubleTargetOffset > targetLength) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_BAD_INDEX);
return false;
}
// Now that that's confirmed, we can use |targetOffset| of a sane type.
uint32_t targetOffset = uint32_t(doubleTargetOffset);
// The remaining steps are unobservable *except* through their effect on
// which elements are copied and how.
Scalar::Type targetType = target->type();
Scalar::Type unsafeSrcTypeCrossCompartment = unsafeTypedArrayCrossCompartment->type();
size_t targetElementSize = TypedArrayElemSize(targetType);
SharedMem<uint8_t*> targetData =
target->viewDataEither().cast<uint8_t*>() + targetOffset * targetElementSize;
SharedMem<uint8_t*> unsafeSrcDataCrossCompartment =
unsafeTypedArrayCrossCompartment->viewDataEither().cast<uint8_t*>();
uint32_t unsafeSrcElementSizeCrossCompartment =
TypedArrayElemSize(unsafeSrcTypeCrossCompartment);
uint32_t unsafeSrcByteLengthCrossCompartment =
unsafeSrcLengthCrossCompartment * unsafeSrcElementSizeCrossCompartment;
// Step 29.
//
// The same-type case requires exact copying preserving the bit-level
// encoding of the source data, so move the values. (We could PodCopy if
// we knew the buffers differed, but it's doubtful the work to check
// wouldn't swap any minor wins PodCopy would afford. Because of the
// TOTALLY UNSAFE CROSS-COMPARTMENT NONSENSE here, comparing buffer
// pointers directly could give an incorrect answer.) If this occurs,
// the %TypedArray%.prototype.set operation is completely finished.
if (targetType == unsafeSrcTypeCrossCompartment) {
jit::AtomicOperations::memmoveSafeWhenRacy(targetData,
unsafeSrcDataCrossCompartment,
unsafeSrcByteLengthCrossCompartment);
args.rval().setInt32(JS_SETTYPEDARRAY_SAME_TYPE);
return true;
}
// Every other bit of element-copying is handled by step 28. Indicate
// whether such copying must take care not to overlap, so that self-hosted
// code may correctly perform the copying.
SharedMem<uint8_t*> unsafeSrcDataLimitCrossCompartment =
unsafeSrcDataCrossCompartment + unsafeSrcByteLengthCrossCompartment;
SharedMem<uint8_t*> targetDataLimit =
target->viewDataEither().cast<uint8_t*>() + targetLength * targetElementSize;
// Step 24 test (but not steps 24a-d -- the caller handles those).
bool overlap =
IsInRange(targetData.unwrap(/*safe - used for ptr value*/),
unsafeSrcDataCrossCompartment.unwrap(/*safe - ditto*/),
unsafeSrcDataLimitCrossCompartment.unwrap(/*safe - ditto*/)) ||
IsInRange(unsafeSrcDataCrossCompartment.unwrap(/*safe - ditto*/),
targetData.unwrap(/*safe - ditto*/),
targetDataLimit.unwrap(/*safe - ditto*/));
args.rval().setInt32(overlap ? JS_SETTYPEDARRAY_OVERLAPPING : JS_SETTYPEDARRAY_DISJOINT);
return true;
}
template <typename From, typename To>
static void
CopyValues(SharedMem<To*> dest, SharedMem<From*> src, uint32_t count)
{
#ifdef DEBUG
void* destVoid = dest.template cast<void*>().unwrap(/*safe - used for ptr value*/);
void* destVoidEnd = (dest + count).template cast<void*>().unwrap(/*safe - ditto*/);
const void* srcVoid = src.template cast<void*>().unwrap(/*safe - ditto*/);
const void* srcVoidEnd = (src + count).template cast<void*>().unwrap(/*safe - ditto*/);
MOZ_ASSERT(!IsInRange(destVoid, srcVoid, srcVoidEnd));
MOZ_ASSERT(!IsInRange(srcVoid, destVoid, destVoidEnd));
#endif
using namespace jit;
for (; count > 0; count--) {
AtomicOperations::storeSafeWhenRacy(dest++,
To(AtomicOperations::loadSafeWhenRacy(src++)));
}
}
struct DisjointElements
{
template <typename To>
static void
copy(SharedMem<To*> dest, SharedMem<void*> src, Scalar::Type fromType, uint32_t count) {
switch (fromType) {
case Scalar::Int8:
CopyValues(dest, src.cast<int8_t*>(), count);
return;
case Scalar::Uint8:
CopyValues(dest, src.cast<uint8_t*>(), count);
return;
case Scalar::Int16:
CopyValues(dest, src.cast<int16_t*>(), count);
return;
case Scalar::Uint16:
CopyValues(dest, src.cast<uint16_t*>(), count);
return;
case Scalar::Int32:
CopyValues(dest, src.cast<int32_t*>(), count);
return;
case Scalar::Uint32:
CopyValues(dest, src.cast<uint32_t*>(), count);
return;
case Scalar::Float32:
CopyValues(dest, src.cast<float*>(), count);
return;
case Scalar::Float64:
CopyValues(dest, src.cast<double*>(), count);
return;
case Scalar::Uint8Clamped:
CopyValues(dest, src.cast<uint8_clamped*>(), count);
return;
default:
MOZ_CRASH("NonoverlappingSet with bogus from-type");
}
}
};
static void
CopyToDisjointArray(TypedArrayObject* target, uint32_t targetOffset, SharedMem<void*> src,
Scalar::Type srcType, uint32_t count)
{
Scalar::Type destType = target->type();
SharedMem<uint8_t*> dest = target->viewDataEither().cast<uint8_t*>() + targetOffset * TypedArrayElemSize(destType);
switch (destType) {
case Scalar::Int8: {
DisjointElements::copy(dest.cast<int8_t*>(), src, srcType, count);
break;
}
case Scalar::Uint8: {
DisjointElements::copy(dest.cast<uint8_t*>(), src, srcType, count);
break;
}
case Scalar::Int16: {
DisjointElements::copy(dest.cast<int16_t*>(), src, srcType, count);
break;
}
case Scalar::Uint16: {
DisjointElements::copy(dest.cast<uint16_t*>(), src, srcType, count);
break;
}
case Scalar::Int32: {
DisjointElements::copy(dest.cast<int32_t*>(), src, srcType, count);
break;
}
case Scalar::Uint32: {
DisjointElements::copy(dest.cast<uint32_t*>(), src, srcType, count);
break;
}
case Scalar::Float32: {
DisjointElements::copy(dest.cast<float*>(), src, srcType, count);
break;
}
case Scalar::Float64: {
DisjointElements::copy(dest.cast<double*>(), src, srcType, count);
break;
}
case Scalar::Uint8Clamped: {
DisjointElements::copy(dest.cast<uint8_clamped*>(), src, srcType, count);
break;
}
default:
MOZ_CRASH("setFromAnyTypedArray with a typed array with bogus type");
}
}
// |unsafeSrcCrossCompartment| is produced by |DangerouslyUnwrapTypedArray|,
// counseling extreme caution when using it. As directed by
// |DangerouslyUnwrapTypedArray|, sigil this pointer and all variables derived
// from it to counsel extreme caution here.
void
js::SetDisjointTypedElements(TypedArrayObject* target, uint32_t targetOffset,
TypedArrayObject* unsafeSrcCrossCompartment)
{
Scalar::Type unsafeSrcTypeCrossCompartment = unsafeSrcCrossCompartment->type();
SharedMem<void*> unsafeSrcDataCrossCompartment = unsafeSrcCrossCompartment->viewDataEither();
uint32_t count = unsafeSrcCrossCompartment->length();
CopyToDisjointArray(target, targetOffset,
unsafeSrcDataCrossCompartment,
unsafeSrcTypeCrossCompartment, count);
}
static bool
intrinsic_SetDisjointTypedElements(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
Rooted<TypedArrayObject*> target(cx, &args[0].toObject().as<TypedArrayObject>());
MOZ_ASSERT(!target->hasBuffer() || !target->isNeutered(),
"a neutered typed array has no elements to set, so "
"it's nonsensical to be setting them");
uint32_t targetOffset = uint32_t(args[1].toInt32());
// As directed by |DangerouslyUnwrapTypedArray|, sigil this pointer and all
// variables derived from it to counsel extreme caution here.
Rooted<TypedArrayObject*> unsafeSrcCrossCompartment(cx);
unsafeSrcCrossCompartment = DangerouslyUnwrapTypedArray(cx, &args[2].toObject());
if (!unsafeSrcCrossCompartment)
return false;
SetDisjointTypedElements(target, targetOffset, unsafeSrcCrossCompartment);
args.rval().setUndefined();
return true;
}
static bool
intrinsic_SetOverlappingTypedElements(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
Rooted<TypedArrayObject*> target(cx, &args[0].toObject().as<TypedArrayObject>());
MOZ_ASSERT(!target->hasBuffer() || !target->isNeutered(),
"shouldn't be setting elements if neutered");
uint32_t targetOffset = uint32_t(args[1].toInt32());
// As directed by |DangerouslyUnwrapTypedArray|, sigil this pointer and all
// variables derived from it to counsel extreme caution here.
Rooted<TypedArrayObject*> unsafeSrcCrossCompartment(cx);
unsafeSrcCrossCompartment = DangerouslyUnwrapTypedArray(cx, &args[2].toObject());
if (!unsafeSrcCrossCompartment)
return false;
// Smarter algorithms exist to perform overlapping transfers of the sort
// this method performs (for example, v8's self-hosted implementation).
// But it seems likely deliberate overlapping transfers are rare enough
// that it's not worth the trouble to implement one (and worry about its
// safety/correctness!). Make a copy and do a disjoint set from that.
uint32_t count = unsafeSrcCrossCompartment->length();
Scalar::Type unsafeSrcTypeCrossCompartment = unsafeSrcCrossCompartment->type();
size_t sourceByteLen = count * TypedArrayElemSize(unsafeSrcTypeCrossCompartment);
auto copyOfSrcData = target->zone()->make_pod_array<uint8_t>(sourceByteLen);
if (!copyOfSrcData)
return false;
jit::AtomicOperations::memcpySafeWhenRacy(copyOfSrcData.get(),
unsafeSrcCrossCompartment->viewDataEither().cast<uint8_t*>(),
sourceByteLen);
CopyToDisjointArray(target, targetOffset, SharedMem<void*>::unshared(copyOfSrcData.get()),
unsafeSrcTypeCrossCompartment, count);
args.rval().setUndefined();
return true;
}
bool
CallSelfHostedNonGenericMethod(JSContext* cx, const CallArgs& args)
{
// This function is called when a self-hosted method is invoked on a
// wrapper object, like a CrossCompartmentWrapper. The last argument is
// the name of the self-hosted function. The other arguments are the
// arguments to pass to this function.
MOZ_ASSERT(args.length() > 0);
RootedPropertyName name(cx, args[args.length() - 1].toString()->asAtom().asPropertyName());
RootedValue selfHostedFun(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), name, &selfHostedFun))
return false;
MOZ_ASSERT(selfHostedFun.toObject().is<JSFunction>());
InvokeArgs args2(cx);
if (!args2.init(args.length() - 1))
return false;
args2.setCallee(selfHostedFun);
args2.setThis(args.thisv());
for (size_t i = 0; i < args.length() - 1; i++)
args2[i].set(args[i]);
if (!Invoke(cx, args2))
return false;
args.rval().set(args2.rval());
return true;
}
template<typename T>
bool
Is(HandleValue v)
{
return v.isObject() && v.toObject().is<T>();
}
template<IsAcceptableThis Test>
static bool
CallNonGenericSelfhostedMethod(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return CallNonGenericMethod<Test, CallSelfHostedNonGenericMethod>(cx, args);
}
/**
* Returns the default locale as a well-formed, but not necessarily canonicalized,
* BCP-47 language tag.
*/
static bool
intrinsic_RuntimeDefaultLocale(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const char* locale = cx->runtime()->getDefaultLocale();
if (!locale) {
JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_DEFAULT_LOCALE_ERROR);
return false;
}
RootedString jslocale(cx, JS_NewStringCopyZ(cx, locale));
if (!jslocale)
return false;
args.rval().setString(jslocale);
return true;
}
static bool
intrinsic_LocalTZA(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0, "the LocalTZA intrinsic takes no arguments");
args.rval().setDouble(DateTimeInfo::localTZA());
return true;
}
static bool
intrinsic_IsConstructing(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 0);
ScriptFrameIter iter(cx);
bool isConstructing = iter.isConstructing();
args.rval().setBoolean(isConstructing);
return true;
}
static bool
intrinsic_ConstructorForTypedArray(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
MOZ_ASSERT(args[0].isObject());
MOZ_ASSERT(IsAnyTypedArray(&args[0].toObject()));
RootedObject object(cx, &args[0].toObject());
JSProtoKey protoKey = StandardProtoKeyOrNull(object);
MOZ_ASSERT(protoKey);
// While it may seem like an invariant that in any compartment,
// seeing a typed array object implies that the TypedArray constructor
// for that type is initialized on the compartment's global, this is not
// the case. When we construct a typed array given a cross-compartment
// ArrayBuffer, we put the constructed TypedArray in the same compartment
// as the ArrayBuffer. Since we use the prototype from the initial
// compartment, and never call the constructor in the ArrayBuffer's
// compartment from script, we are not guaranteed to have initialized
// the constructor.
RootedObject ctor(cx);
if (!GetBuiltinConstructor(cx, protoKey, &ctor))
return false;
args.rval().setObject(*ctor);
return true;
}
static bool
intrinsic_HostResolveImportedModule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
MOZ_ASSERT(args[0].toObject().is<ModuleObject>());
MOZ_ASSERT(args[1].isString());
RootedFunction moduleResolveHook(cx, cx->global()->moduleResolveHook());
if (!moduleResolveHook) {
JS_ReportError(cx, "Module resolve hook not set");
return false;
}
RootedValue result(cx);
if (!JS_CallFunction(cx, nullptr, moduleResolveHook, args, &result))
return false;
if (!result.isObject() || !result.toObject().is<ModuleObject>()) {
JS_ReportError(cx, "Module resolve hook did not return Module object");
return false;
}
args.rval().set(result);
return true;
}
static bool
intrinsic_GetModuleEnvironment(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
RootedModuleEnvironmentObject env(cx, module->environment());
args.rval().setUndefined();
if (!env) {
args.rval().setUndefined();
return true;
}
args.rval().setObject(*env);
return true;
}
static bool
intrinsic_CreateModuleEnvironment(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
module->createEnvironment();
args.rval().setUndefined();
return true;
}
static bool
intrinsic_CreateImportBinding(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 4);
RootedModuleEnvironmentObject environment(cx, &args[0].toObject().as<ModuleEnvironmentObject>());
RootedAtom importedName(cx, &args[1].toString()->asAtom());
RootedModuleObject module(cx, &args[2].toObject().as<ModuleObject>());
RootedAtom localName(cx, &args[3].toString()->asAtom());
if (!environment->createImportBinding(cx, importedName, module, localName))
return false;
args.rval().setUndefined();
return true;
}
static bool
intrinsic_CreateNamespaceBinding(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 3);
RootedModuleEnvironmentObject environment(cx, &args[0].toObject().as<ModuleEnvironmentObject>());
RootedId name(cx, AtomToId(&args[1].toString()->asAtom()));
MOZ_ASSERT(args[2].toObject().is<ModuleNamespaceObject>());
// The property already exists in the evironment but is not writable, so set
// the slot directly.
RootedShape shape(cx, environment->lookup(cx, name));
MOZ_ASSERT(shape);
MOZ_ASSERT(environment->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL));
environment->setSlot(shape->slot(), args[2]);
args.rval().setUndefined();
return true;
}
static bool
intrinsic_InstantiateModuleFunctionDeclarations(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
args.rval().setUndefined();
return ModuleObject::instantiateFunctionDeclarations(cx, module);
}
static bool
intrinsic_SetModuleEvaluated(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
module->setEvaluated();
args.rval().setUndefined();
return true;
}
static bool
intrinsic_EvaluateModule(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
return ModuleObject::evaluate(cx, module, args.rval());
}
static bool
intrinsic_NewModuleNamespace(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 2);
RootedModuleObject module(cx, &args[0].toObject().as<ModuleObject>());
RootedArrayObject exports(cx, &args[1].toObject().as<ArrayObject>());
RootedObject namespace_(cx, ModuleObject::createNamespace(cx, module, exports));
if (!namespace_)
return false;
args.rval().setObject(*namespace_);
return true;
}
static bool
intrinsic_AddModuleNamespaceBinding(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 4);
RootedModuleNamespaceObject namespace_(cx, &args[0].toObject().as<ModuleNamespaceObject>());
RootedAtom exportedName(cx, &args[1].toString()->asAtom());
RootedModuleObject targetModule(cx, &args[2].toObject().as<ModuleObject>());
RootedAtom localName(cx, &args[3].toString()->asAtom());
if (!namespace_->addBinding(cx, exportedName, targetModule, localName))
return false;
args.rval().setUndefined();
return true;
}
static bool
intrinsic_ModuleNamespaceExports(JSContext* cx, unsigned argc, Value* vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
MOZ_ASSERT(args.length() == 1);
RootedModuleNamespaceObject namespace_(cx, &args[0].toObject().as<ModuleNamespaceObject>());
args.rval().setObject(namespace_->exports());
return true;
}
// The self-hosting global isn't initialized with the normal set of builtins.
// Instead, individual C++-implemented functions that're required by
// self-hosted code are defined as global functions. Accessing these
// functions via a content compartment's builtins would be unsafe, because
// content script might have changed the builtins' prototypes' members.
// Installing the whole set of builtins in the self-hosting compartment, OTOH,
// would be wasteful: it increases memory usage and initialization time for
// self-hosting compartment.
//
// Additionally, a set of C++-implemented helper functions is defined on the
// self-hosting global.
static const JSFunctionSpec intrinsic_functions[] = {
JS_INLINABLE_FN("std_Array", ArrayConstructor, 1,0, Array),
JS_FN("std_Array_join", array_join, 1,0),
JS_INLINABLE_FN("std_Array_push", array_push, 1,0, ArrayPush),
JS_INLINABLE_FN("std_Array_pop", array_pop, 0,0, ArrayPop),
JS_INLINABLE_FN("std_Array_shift", array_shift, 0,0, ArrayShift),
JS_FN("std_Array_unshift", array_unshift, 1,0),
JS_INLINABLE_FN("std_Array_slice", array_slice, 2,0, ArraySlice),
JS_FN("std_Array_sort", array_sort, 1,0),
JS_FN("std_Date_now", date_now, 0,0),
JS_FN("std_Date_valueOf", date_valueOf, 0,0),
JS_FN("std_Function_bind", fun_bind, 1,0),
JS_FN("std_Function_apply", fun_apply, 2,0),
JS_INLINABLE_FN("std_Math_floor", math_floor, 1,0, MathFloor),
JS_INLINABLE_FN("std_Math_max", math_max, 2,0, MathMax),
JS_INLINABLE_FN("std_Math_min", math_min, 2,0, MathMin),
JS_INLINABLE_FN("std_Math_abs", math_abs, 1,0, MathAbs),
JS_INLINABLE_FN("std_Math_imul", math_imul, 2,0, MathImul),
JS_INLINABLE_FN("std_Math_log2", math_log2, 1,0, MathLog2),
JS_FN("std_Map_has", MapObject::has, 1,0),
JS_FN("std_Map_iterator", MapObject::entries, 0,0),
JS_FN("std_Number_valueOf", num_valueOf, 0,0),
JS_INLINABLE_FN("std_Object_create", obj_create, 2, 0, ObjectCreate),
JS_FN("std_Object_propertyIsEnumerable", obj_propertyIsEnumerable, 1,0),
JS_FN("std_Object_defineProperty", obj_defineProperty, 3,0),
JS_FN("std_Object_getOwnPropertyNames", obj_getOwnPropertyNames, 1,0),
JS_FN("std_Object_getOwnPropertyDescriptor", obj_getOwnPropertyDescriptor, 2,0),
JS_FN("std_Object_hasOwnProperty", obj_hasOwnProperty, 1,0),
JS_FN("std_Object_toString", obj_toString, 0,0),
JS_FN("std_Reflect_getPrototypeOf", Reflect_getPrototypeOf, 1,0),
JS_FN("std_Reflect_isExtensible", Reflect_isExtensible, 1,0),
JS_FN("std_Set_has", SetObject::has, 1,0),
JS_FN("std_Set_iterator", SetObject::values, 0,0),
JS_INLINABLE_FN("std_String_fromCharCode", str_fromCharCode, 1,0, StringFromCharCode),
JS_INLINABLE_FN("std_String_charCodeAt", str_charCodeAt, 1,0, StringCharCodeAt),
JS_FN("std_String_indexOf", str_indexOf, 1,0),
JS_FN("std_String_lastIndexOf", str_lastIndexOf, 1,0),
JS_FN("std_String_match", str_match, 1,0),
JS_INLINABLE_FN("std_String_replace", str_replace, 2,0, StringReplace),
JS_INLINABLE_FN("std_String_split", str_split, 2,0, StringSplit),
JS_FN("std_String_startsWith", str_startsWith, 1,0),
JS_FN("std_String_toLowerCase", str_toLowerCase, 0,0),
JS_FN("std_String_toUpperCase", str_toUpperCase, 0,0),
JS_FN("std_WeakMap_has", WeakMap_has, 1,0),
JS_FN("std_WeakMap_get", WeakMap_get, 2,0),
JS_FN("std_WeakMap_set", WeakMap_set, 2,0),
JS_FN("std_WeakMap_delete", WeakMap_delete, 1,0),
JS_FN("std_WeakMap_clear", WeakMap_clear, 0,0),
JS_INLINABLE_FN("std_SIMD_Int32x4_extractLane", simd_int32x4_extractLane, 2,0, SimdInt32x4),
JS_INLINABLE_FN("std_SIMD_Float32x4_extractLane", simd_float32x4_extractLane,2,0, SimdFloat32x4),
JS_FN("std_SIMD_Float64x2_extractLane", simd_float64x2_extractLane, 2,0),
// Helper funtions after this point.
JS_INLINABLE_FN("ToObject", intrinsic_ToObject, 1,0, IntrinsicToObject),
JS_INLINABLE_FN("IsObject", intrinsic_IsObject, 1,0, IntrinsicIsObject),
JS_INLINABLE_FN("ToInteger", intrinsic_ToInteger, 1,0, IntrinsicToInteger),
JS_INLINABLE_FN("ToString", intrinsic_ToString, 1,0, IntrinsicToString),
JS_FN("ToPropertyKey", intrinsic_ToPropertyKey, 1,0),
JS_INLINABLE_FN("IsCallable", intrinsic_IsCallable, 1,0, IntrinsicIsCallable),
JS_FN("IsConstructor", intrinsic_IsConstructor, 1,0),
JS_FN("OwnPropertyKeys", intrinsic_OwnPropertyKeys, 1,0),
JS_FN("ThrowRangeError", intrinsic_ThrowRangeError, 4,0),
JS_FN("ThrowTypeError", intrinsic_ThrowTypeError, 4,0),
JS_FN("ThrowSyntaxError", intrinsic_ThrowSyntaxError, 4,0),
JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1,0),
JS_FN("MakeConstructible", intrinsic_MakeConstructible, 2,0),
JS_FN("_ConstructorForTypedArray", intrinsic_ConstructorForTypedArray, 1,0),
JS_FN("DecompileArg", intrinsic_DecompileArg, 2,0),
JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0),
JS_FN("LocalTZA", intrinsic_LocalTZA, 0,0),
JS_INLINABLE_FN("_IsConstructing", intrinsic_IsConstructing, 0,0,
IntrinsicIsConstructing),
JS_INLINABLE_FN("SubstringKernel", intrinsic_SubstringKernel, 3,0,
IntrinsicSubstringKernel),
JS_INLINABLE_FN("_DefineDataProperty", intrinsic_DefineDataProperty, 4,0,
IntrinsicDefineDataProperty),
JS_INLINABLE_FN("UnsafeSetReservedSlot", intrinsic_UnsafeSetReservedSlot, 3,0,
IntrinsicUnsafeSetReservedSlot),
JS_INLINABLE_FN("UnsafeGetReservedSlot", intrinsic_UnsafeGetReservedSlot, 2,0,
IntrinsicUnsafeGetReservedSlot),
JS_INLINABLE_FN("UnsafeGetObjectFromReservedSlot", intrinsic_UnsafeGetObjectFromReservedSlot, 2,0,
IntrinsicUnsafeGetObjectFromReservedSlot),
JS_INLINABLE_FN("UnsafeGetInt32FromReservedSlot", intrinsic_UnsafeGetInt32FromReservedSlot, 2,0,
IntrinsicUnsafeGetInt32FromReservedSlot),
JS_INLINABLE_FN("UnsafeGetStringFromReservedSlot", intrinsic_UnsafeGetStringFromReservedSlot, 2,0,
IntrinsicUnsafeGetStringFromReservedSlot),
JS_INLINABLE_FN("UnsafeGetBooleanFromReservedSlot", intrinsic_UnsafeGetBooleanFromReservedSlot,2,0,
IntrinsicUnsafeGetBooleanFromReservedSlot),
JS_FN("IsPackedArray", intrinsic_IsPackedArray, 1,0),
JS_FN("GetIteratorPrototype", intrinsic_GetIteratorPrototype, 0,0),
JS_FN("NewArrayIterator", intrinsic_NewArrayIterator, 0,0),
JS_FN("CallArrayIteratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<ArrayIteratorObject>>, 2,0),
JS_FN("NewListIterator", intrinsic_NewListIterator, 0,0),
JS_FN("CallListIteratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<ListIteratorObject>>, 2,0),
JS_FN("ActiveFunction", intrinsic_ActiveFunction, 0,0),
JS_INLINABLE_FN("IsArrayIterator",
intrinsic_IsInstanceOfBuiltin<ArrayIteratorObject>, 1,0,
IntrinsicIsArrayIterator),
JS_INLINABLE_FN("IsMapIterator",
intrinsic_IsInstanceOfBuiltin<MapIteratorObject>, 1,0,
IntrinsicIsMapIterator),
JS_INLINABLE_FN("IsStringIterator",
intrinsic_IsInstanceOfBuiltin<StringIteratorObject>, 1,0,
IntrinsicIsStringIterator),
JS_INLINABLE_FN("IsListIterator",
intrinsic_IsInstanceOfBuiltin<ListIteratorObject>, 1,0,
IntrinsicIsListIterator),
JS_FN("_GetNextMapEntryForIterator", intrinsic_GetNextMapEntryForIterator, 3,0),
JS_FN("CallMapIteratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<MapIteratorObject>>, 2,0),
JS_FN("NewStringIterator", intrinsic_NewStringIterator, 0,0),
JS_FN("CallStringIteratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<StringIteratorObject>>, 2,0),
JS_FN("IsStarGeneratorObject",
intrinsic_IsInstanceOfBuiltin<StarGeneratorObject>, 1,0),
JS_FN("StarGeneratorObjectIsClosed", intrinsic_StarGeneratorObjectIsClosed, 1,0),
JS_FN("IsSuspendedStarGenerator",intrinsic_IsSuspendedStarGenerator,1,0),
JS_FN("IsLegacyGeneratorObject",
intrinsic_IsInstanceOfBuiltin<LegacyGeneratorObject>, 1,0),
JS_FN("LegacyGeneratorObjectIsClosed", intrinsic_LegacyGeneratorObjectIsClosed, 1,0),
JS_FN("CloseClosingLegacyGeneratorObject", intrinsic_CloseClosingLegacyGeneratorObject, 1,0),
JS_FN("ThrowStopIteration", intrinsic_ThrowStopIteration, 0,0),
JS_FN("GeneratorIsRunning", intrinsic_GeneratorIsRunning, 1,0),
JS_FN("GeneratorSetClosed", intrinsic_GeneratorSetClosed, 1,0),
JS_FN("IsArrayBuffer",
intrinsic_IsInstanceOfBuiltin<ArrayBufferObject>, 1,0),
JS_FN("IsSharedArrayBuffer",
intrinsic_IsInstanceOfBuiltin<SharedArrayBufferObject>, 1,0),
JS_INLINABLE_FN("IsTypedArray",
intrinsic_IsInstanceOfBuiltin<TypedArrayObject>, 1,0,
IntrinsicIsTypedArray),
JS_INLINABLE_FN("IsPossiblyWrappedTypedArray",intrinsic_IsPossiblyWrappedTypedArray,1,0,
IntrinsicIsPossiblyWrappedTypedArray),
JS_FN("TypedArrayBuffer", intrinsic_TypedArrayBuffer, 1,0),
JS_FN("TypedArrayByteOffset", intrinsic_TypedArrayByteOffset, 1,0),
JS_FN("TypedArrayElementShift", intrinsic_TypedArrayElementShift, 1,0),
JS_INLINABLE_FN("TypedArrayLength", intrinsic_TypedArrayLength, 1,0,
IntrinsicTypedArrayLength),
JS_FN("MoveTypedArrayElements", intrinsic_MoveTypedArrayElements, 4,0),
JS_FN("SetFromTypedArrayApproach",intrinsic_SetFromTypedArrayApproach, 4, 0),
JS_FN("SetOverlappingTypedElements",intrinsic_SetOverlappingTypedElements,3,0),
JS_INLINABLE_FN("SetDisjointTypedElements",intrinsic_SetDisjointTypedElements,3,0,
IntrinsicSetDisjointTypedElements),
JS_FN("CallTypedArrayMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<TypedArrayObject>>, 2, 0),
JS_FN("CallLegacyGeneratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<LegacyGeneratorObject>>, 2, 0),
JS_FN("CallStarGeneratorMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<StarGeneratorObject>>, 2, 0),
JS_FN("IsWeakSet", intrinsic_IsInstanceOfBuiltin<WeakSetObject>, 1,0),
JS_FN("CallWeakSetMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<WeakSetObject>>, 2, 0),
// See builtin/TypedObject.h for descriptors of the typedobj functions.
JS_FN("NewOpaqueTypedObject", js::NewOpaqueTypedObject, 1, 0),
JS_FN("NewDerivedTypedObject", js::NewDerivedTypedObject, 3, 0),
JS_FN("TypedObjectBuffer", TypedObject::GetBuffer, 1, 0),
JS_FN("TypedObjectByteOffset", TypedObject::GetByteOffset, 1, 0),
JS_FN("AttachTypedObject", js::AttachTypedObject, 3, 0),
JS_FN("TypedObjectIsAttached", js::TypedObjectIsAttached, 1, 0),
JS_FN("TypedObjectTypeDescr", js::TypedObjectTypeDescr, 1, 0),
JS_FN("ClampToUint8", js::ClampToUint8, 1, 0),
JS_FN("GetTypedObjectModule", js::GetTypedObjectModule, 0, 0),
JS_FN("GetFloat32x4TypeDescr", js::GetFloat32x4TypeDescr, 0, 0),
JS_FN("GetFloat64x2TypeDescr", js::GetFloat64x2TypeDescr, 0, 0),
JS_FN("GetInt8x16TypeDescr", js::GetInt8x16TypeDescr, 0, 0),
JS_FN("GetInt16x8TypeDescr", js::GetInt16x8TypeDescr, 0, 0),
JS_FN("GetInt32x4TypeDescr", js::GetInt32x4TypeDescr, 0, 0),
JS_INLINABLE_FN("ObjectIsTypeDescr" , js::ObjectIsTypeDescr, 1, 0,
IntrinsicObjectIsTypeDescr),
JS_INLINABLE_FN("ObjectIsTypedObject", js::ObjectIsTypedObject, 1, 0,
IntrinsicObjectIsTypedObject),
JS_INLINABLE_FN("ObjectIsOpaqueTypedObject", js::ObjectIsOpaqueTypedObject, 1, 0,
IntrinsicObjectIsOpaqueTypedObject),
JS_INLINABLE_FN("ObjectIsTransparentTypedObject", js::ObjectIsTransparentTypedObject, 1, 0,
IntrinsicObjectIsTransparentTypedObject),
JS_INLINABLE_FN("TypeDescrIsArrayType", js::TypeDescrIsArrayType, 1, 0,
IntrinsicTypeDescrIsArrayType),
JS_INLINABLE_FN("TypeDescrIsSimpleType", js::TypeDescrIsSimpleType, 1, 0,
IntrinsicTypeDescrIsSimpleType),
JS_INLINABLE_FN("SetTypedObjectOffset", js::SetTypedObjectOffset, 2, 0,
IntrinsicSetTypedObjectOffset),
#define LOAD_AND_STORE_SCALAR_FN_DECLS(_constant, _type, _name) \
JS_FN("Store_" #_name, js::StoreScalar##_type::Func, 3, 0), \
JS_FN("Load_" #_name, js::LoadScalar##_type::Func, 3, 0),
JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(LOAD_AND_STORE_SCALAR_FN_DECLS)
#undef LOAD_AND_STORE_SCALAR_FN_DECLS
#define LOAD_AND_STORE_REFERENCE_FN_DECLS(_constant, _type, _name) \
JS_FN("Store_" #_name, js::StoreReference##_type::Func, 3, 0), \
JS_FN("Load_" #_name, js::LoadReference##_type::Func, 3, 0),
JS_FOR_EACH_REFERENCE_TYPE_REPR(LOAD_AND_STORE_REFERENCE_FN_DECLS)
#undef LOAD_AND_STORE_REFERENCE_FN_DECLS
// See builtin/Intl.h for descriptions of the intl_* functions.
JS_FN("intl_availableCalendars", intl_availableCalendars, 1,0),
JS_FN("intl_availableCollations", intl_availableCollations, 1,0),
JS_FN("intl_Collator", intl_Collator, 2,0),
JS_FN("intl_Collator_availableLocales", intl_Collator_availableLocales, 0,0),
JS_FN("intl_CompareStrings", intl_CompareStrings, 3,0),
JS_FN("intl_DateTimeFormat", intl_DateTimeFormat, 2,0),
JS_FN("intl_DateTimeFormat_availableLocales", intl_DateTimeFormat_availableLocales, 0,0),
JS_FN("intl_FormatDateTime", intl_FormatDateTime, 2,0),
JS_FN("intl_FormatNumber", intl_FormatNumber, 2,0),
JS_FN("intl_NumberFormat", intl_NumberFormat, 2,0),
JS_FN("intl_NumberFormat_availableLocales", intl_NumberFormat_availableLocales, 0,0),
JS_FN("intl_numberingSystem", intl_numberingSystem, 1,0),
JS_FN("intl_patternForSkeleton", intl_patternForSkeleton, 2,0),
// See builtin/RegExp.h for descriptions of the regexp_* functions.
JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0),
JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0),
JS_FN("regexp_construct_no_statics", regexp_construct_no_statics, 2,0),
JS_FN("IsModule", intrinsic_IsInstanceOfBuiltin<ModuleObject>, 1, 0),
JS_FN("CallModuleMethodIfWrapped",
CallNonGenericSelfhostedMethod<Is<ModuleObject>>, 2, 0),
JS_FN("HostResolveImportedModule", intrinsic_HostResolveImportedModule, 2, 0),
JS_FN("GetModuleEnvironment", intrinsic_GetModuleEnvironment, 1, 0),
JS_FN("CreateModuleEnvironment", intrinsic_CreateModuleEnvironment, 1, 0),
JS_FN("CreateImportBinding", intrinsic_CreateImportBinding, 4, 0),
JS_FN("CreateNamespaceBinding", intrinsic_CreateNamespaceBinding, 3, 0),
JS_FN("InstantiateModuleFunctionDeclarations",
intrinsic_InstantiateModuleFunctionDeclarations, 1, 0),
JS_FN("SetModuleEvaluated", intrinsic_SetModuleEvaluated, 1, 0),
JS_FN("EvaluateModule", intrinsic_EvaluateModule, 1, 0),
JS_FN("IsModuleNamespace", intrinsic_IsInstanceOfBuiltin<ModuleNamespaceObject>, 1, 0),
JS_FN("NewModuleNamespace", intrinsic_NewModuleNamespace, 2, 0),
JS_FN("AddModuleNamespaceBinding", intrinsic_AddModuleNamespaceBinding, 4, 0),
JS_FN("ModuleNamespaceExports", intrinsic_ModuleNamespaceExports, 1, 0),
JS_FS_END
};
void
js::FillSelfHostingCompileOptions(CompileOptions& options)
{
/*
* In self-hosting mode, scripts use JSOP_GETINTRINSIC instead of
* JSOP_GETNAME or JSOP_GETGNAME to access unbound variables.
* JSOP_GETINTRINSIC does a name lookup on a special object, whose
* properties are filled in lazily upon first access for a given global.
*
* As that object is inaccessible to client code, the lookups are
* guaranteed to return the original objects, ensuring safe implementation
* of self-hosted builtins.
*
* Additionally, the special syntax callFunction(fun, receiver, ...args)
* is supported, for which bytecode is emitted that invokes |fun| with
* |receiver| as the this-object and ...args as the arguments.
*/
options.setIntroductionType("self-hosted");
options.setFileAndLine("self-hosted", 1);
options.setSelfHostingMode(true);
options.setCanLazilyParse(false);
options.setVersion(JSVERSION_LATEST);
options.werrorOption = true;
options.strictOption = true;
#ifdef DEBUG
options.extraWarningsOption = true;
#endif
}
GlobalObject*
JSRuntime::createSelfHostingGlobal(JSContext* cx)
{
MOZ_ASSERT(!cx->isExceptionPending());
MOZ_ASSERT(!cx->runtime()->isAtomsCompartment(cx->compartment()));
JS::CompartmentOptions options;
options.setDiscardSource(true);
options.setZone(JS::FreshZone);
JSCompartment* compartment = NewCompartment(cx, nullptr, nullptr, options);
if (!compartment)
return nullptr;
static const Class shgClass = {
"self-hosting-global", JSCLASS_GLOBAL_FLAGS,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr, nullptr,
nullptr, nullptr, nullptr,
JS_GlobalObjectTraceHook
};
AutoCompartment ac(cx, compartment);
Rooted<GlobalObject*> shg(cx, GlobalObject::createInternal(cx, &shgClass));
if (!shg)
return nullptr;
cx->runtime()->selfHostingGlobal_ = shg;
compartment->isSelfHosting = true;
compartment->setIsSystem(true);
if (!GlobalObject::initSelfHostingBuiltins(cx, shg, intrinsic_functions))
return nullptr;
JS_FireOnNewGlobalObject(cx, shg);
return shg;
}
bool
JSRuntime::initSelfHosting(JSContext* cx)
{
MOZ_ASSERT(!selfHostingGlobal_);
if (cx->runtime()->parentRuntime) {
selfHostingGlobal_ = cx->runtime()->parentRuntime->selfHostingGlobal_;
return true;
}
/*
* Self hosted state can be accessed from threads for other runtimes
* parented to this one, so cannot include state in the nursery.
*/
JS::AutoDisableGenerationalGC disable(cx->runtime());
Rooted<GlobalObject*> shg(cx, JSRuntime::createSelfHostingGlobal(cx));
if (!shg)
return false;
JSAutoCompartment ac(cx, shg);
CompileOptions options(cx);
FillSelfHostingCompileOptions(options);
/*
* Set a temporary error reporter printing to stderr because it is too
* early in the startup process for any other reporter to be registered
* and we don't want errors in self-hosted code to be silently swallowed.
*/
JSErrorReporter oldReporter = JS_SetErrorReporter(cx->runtime(), selfHosting_ErrorReporter);
RootedValue rv(cx);
bool ok = true;
char* filename = js_sb_getenv("MOZ_SELFHOSTEDJS");
if (filename) {
RootedScript script(cx);
if (Compile(cx, options, filename, &script))
ok = Execute(cx, script, *shg.get(), rv.address());
} else {
uint32_t srcLen = GetRawScriptsSize();
const unsigned char* compressed = compressedSources;
uint32_t compressedLen = GetCompressedSize();
ScopedJSFreePtr<char> src(selfHostingGlobal_->zone()->pod_malloc<char>(srcLen));
if (!src || !DecompressString(compressed, compressedLen,
reinterpret_cast<unsigned char*>(src.get()), srcLen))
{
ok = false;
}
ok = ok && Evaluate(cx, options, src, srcLen, &rv);
}
JS_SetErrorReporter(cx->runtime(), oldReporter);
return ok;
}
void
JSRuntime::finishSelfHosting()
{
selfHostingGlobal_ = nullptr;
}
void
JSRuntime::markSelfHostingGlobal(JSTracer* trc)
{
if (selfHostingGlobal_ && !parentRuntime)
TraceRoot(trc, &selfHostingGlobal_, "self-hosting global");
}
bool
JSRuntime::isSelfHostingCompartment(JSCompartment* comp) const
{
return selfHostingGlobal_->compartment() == comp;
}
bool
JSRuntime::isSelfHostingZone(const JS::Zone* zone) const
{
return selfHostingGlobal_ && selfHostingGlobal_->zoneFromAnyThread() == zone;
}
static bool
CloneValue(JSContext* cx, HandleValue selfHostedValue, MutableHandleValue vp);
static bool
GetUnclonedValue(JSContext* cx, HandleNativeObject selfHostedObject,
HandleId id, MutableHandleValue vp)
{
vp.setUndefined();
if (JSID_IS_INT(id)) {
size_t index = JSID_TO_INT(id);
if (index < selfHostedObject->getDenseInitializedLength() &&
!selfHostedObject->getDenseElement(index).isMagic(JS_ELEMENTS_HOLE))
{
vp.set(selfHostedObject->getDenseElement(JSID_TO_INT(id)));
return true;
}
}
// Since all atoms used by self hosting are marked as permanent, any
// attempt to look up a non-permanent atom will fail. We should only
// see such atoms when code is looking for properties on the self
// hosted global which aren't present.
if (JSID_IS_STRING(id) && !JSID_TO_STRING(id)->isPermanentAtom()) {
MOZ_ASSERT(selfHostedObject->is<GlobalObject>());
RootedValue value(cx, IdToValue(id));
return ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_NO_SUCH_SELF_HOSTED_PROP,
JSDVG_IGNORE_STACK, value, nullptr, nullptr, nullptr);
}
RootedShape shape(cx, selfHostedObject->lookupPure(id));
if (!shape) {
RootedValue value(cx, IdToValue(id));
return ReportValueErrorFlags(cx, JSREPORT_ERROR, JSMSG_NO_SUCH_SELF_HOSTED_PROP,
JSDVG_IGNORE_STACK, value, nullptr, nullptr, nullptr);
}
MOZ_ASSERT(shape->hasSlot() && shape->hasDefaultGetter());
vp.set(selfHostedObject->getSlot(shape->slot()));
return true;
}
static bool
CloneProperties(JSContext* cx, HandleNativeObject selfHostedObject, HandleObject clone)
{
AutoIdVector ids(cx);
Vector<uint8_t, 16> attrs(cx);
for (size_t i = 0; i < selfHostedObject->getDenseInitializedLength(); i++) {
if (!selfHostedObject->getDenseElement(i).isMagic(JS_ELEMENTS_HOLE)) {
if (!ids.append(INT_TO_JSID(i)))
return false;
if (!attrs.append(JSPROP_ENUMERATE))
return false;
}
}
Rooted<ShapeVector> shapes(cx, ShapeVector(cx));
for (Shape::Range<NoGC> range(selfHostedObject->lastProperty()); !range.empty(); range.popFront()) {
Shape& shape = range.front();
if (shape.enumerable() && !shapes.append(&shape))
return false;
}
// Now our shapes are in last-to-first order, so....
Reverse(shapes.begin(), shapes.end());
for (size_t i = 0; i < shapes.length(); ++i) {
MOZ_ASSERT(!shapes[i]->isAccessorShape(),
"Can't handle cloning accessors here yet.");
if (!ids.append(shapes[i]->propid()))
return false;
uint8_t shapeAttrs =
shapes[i]->attributes() & (JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
if (!attrs.append(shapeAttrs))
return false;
}
RootedId id(cx);
RootedValue val(cx);
RootedValue selfHostedValue(cx);
for (uint32_t i = 0; i < ids.length(); i++) {
id = ids[i];
if (!GetUnclonedValue(cx, selfHostedObject, id, &selfHostedValue))
return false;
if (!CloneValue(cx, selfHostedValue, &val) ||
!JS_DefinePropertyById(cx, clone, id, val, attrs[i]))
{
return false;
}
}
return true;
}
static JSString*
CloneString(JSContext* cx, JSFlatString* selfHostedString)
{
size_t len = selfHostedString->length();
{
JS::AutoCheckCannotGC nogc;
JSString* clone;
if (selfHostedString->hasLatin1Chars())
clone = NewStringCopyN<NoGC>(cx, selfHostedString->latin1Chars(nogc), len);
else
clone = NewStringCopyNDontDeflate<NoGC>(cx, selfHostedString->twoByteChars(nogc), len);
if (clone)
return clone;
}
AutoStableStringChars chars(cx);
if (!chars.init(cx, selfHostedString))
return nullptr;
return chars.isLatin1()
? NewStringCopyN<CanGC>(cx, chars.latin1Range().start().get(), len)
: NewStringCopyNDontDeflate<CanGC>(cx, chars.twoByteRange().start().get(), len);
}
static JSObject*
CloneObject(JSContext* cx, HandleNativeObject selfHostedObject)
{
#ifdef DEBUG
// Object hash identities are owned by the hashed object, which may be on a
// different thread than the clone target. In theory, these objects are all
// tenured and will not be compacted; however, we simply avoid the issue
// altogether by skipping the cycle-detection when off the main thread.
mozilla::Maybe<AutoCycleDetector> detect;
if (js::CurrentThreadCanAccessZone(selfHostedObject->zoneFromAnyThread())) {
detect.emplace(cx, selfHostedObject);
if (!detect->init())
return nullptr;
if (detect->foundCycle())
MOZ_CRASH("SelfHosted cloning cannot handle cyclic object graphs.");
}
#endif
RootedObject clone(cx);
if (selfHostedObject->is<JSFunction>()) {
RootedFunction selfHostedFunction(cx, &selfHostedObject->as<JSFunction>());
bool hasName = selfHostedFunction->atom() != nullptr;
// Arrow functions use the first extended slot for their lexical |this| value.
MOZ_ASSERT(!selfHostedFunction->isArrow());
js::gc::AllocKind kind = hasName
? gc::AllocKind::FUNCTION_EXTENDED
: selfHostedFunction->getAllocKind();
MOZ_ASSERT(!CanReuseScriptForClone(cx->compartment(), selfHostedFunction, cx->global()));
Rooted<ClonedBlockObject*> globalLexical(cx, &cx->global()->lexicalScope());
RootedObject staticGlobalLexical(cx, &globalLexical->staticBlock());
clone = CloneFunctionAndScript(cx, selfHostedFunction, globalLexical,
staticGlobalLexical, kind);
// To be able to re-lazify the cloned function, its name in the
// self-hosting compartment has to be stored on the clone.
if (clone && hasName)
clone->as<JSFunction>().setExtendedSlot(0, StringValue(selfHostedFunction->atom()));
} else if (selfHostedObject->is<RegExpObject>()) {
RegExpObject& reobj = selfHostedObject->as<RegExpObject>();
RootedAtom source(cx, reobj.getSource());
MOZ_ASSERT(source->isPermanentAtom());
clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), nullptr, cx->tempLifoAlloc());
} else if (selfHostedObject->is<DateObject>()) {
clone = JS::NewDateObject(cx, selfHostedObject->as<DateObject>().clippedTime());
} else if (selfHostedObject->is<BooleanObject>()) {
clone = BooleanObject::create(cx, selfHostedObject->as<BooleanObject>().unbox());
} else if (selfHostedObject->is<NumberObject>()) {
clone = NumberObject::create(cx, selfHostedObject->as<NumberObject>().unbox());
} else if (selfHostedObject->is<StringObject>()) {
JSString* selfHostedString = selfHostedObject->as<StringObject>().unbox();
if (!selfHostedString->isFlat())
MOZ_CRASH();
RootedString str(cx, CloneString(cx, &selfHostedString->asFlat()));
if (!str)
return nullptr;
clone = StringObject::create(cx, str);
} else if (selfHostedObject->is<ArrayObject>()) {
clone = NewDenseEmptyArray(cx, nullptr, TenuredObject);
} else {
MOZ_ASSERT(selfHostedObject->isNative());
clone = NewObjectWithGivenProto(cx, selfHostedObject->getClass(), nullptr,
selfHostedObject->asTenured().getAllocKind(),
SingletonObject);
}
if (!clone)
return nullptr;
if (!CloneProperties(cx, selfHostedObject, clone))
return nullptr;
return clone;
}
static bool
CloneValue(JSContext* cx, HandleValue selfHostedValue, MutableHandleValue vp)
{
if (selfHostedValue.isObject()) {
RootedNativeObject selfHostedObject(cx, &selfHostedValue.toObject().as<NativeObject>());
JSObject* clone = CloneObject(cx, selfHostedObject);
if (!clone)
return false;
vp.setObject(*clone);
} else if (selfHostedValue.isBoolean() || selfHostedValue.isNumber() || selfHostedValue.isNullOrUndefined()) {
// Nothing to do here: these are represented inline in the value.
vp.set(selfHostedValue);
} else if (selfHostedValue.isString()) {
if (!selfHostedValue.toString()->isFlat())
MOZ_CRASH();
JSFlatString* selfHostedString = &selfHostedValue.toString()->asFlat();
JSString* clone = CloneString(cx, selfHostedString);
if (!clone)
return false;
vp.setString(clone);
} else if (selfHostedValue.isSymbol()) {
// Well-known symbols are shared.
mozilla::DebugOnly<JS::Symbol*> sym = selfHostedValue.toSymbol();
MOZ_ASSERT(sym->isWellKnownSymbol());
MOZ_ASSERT(cx->wellKnownSymbols().get(size_t(sym->code())) == sym);
vp.set(selfHostedValue);
} else {
MOZ_CRASH("Self-hosting CloneValue can't clone given value.");
}
return true;
}
bool
JSRuntime::cloneSelfHostedFunctionScript(JSContext* cx, HandlePropertyName name,
HandleFunction targetFun)
{
RootedId id(cx, NameToId(name));
RootedValue funVal(cx);
if (!GetUnclonedValue(cx, HandleNativeObject::fromMarkedLocation(&selfHostingGlobal_), id, &funVal))
return false;
RootedFunction sourceFun(cx, &funVal.toObject().as<JSFunction>());
// JSFunction::generatorKind can't handle lazy self-hosted functions, so we make sure there
// aren't any.
MOZ_ASSERT(!sourceFun->isGenerator());
MOZ_ASSERT(sourceFun->nargs() == targetFun->nargs());
MOZ_ASSERT(targetFun->isExtended());
MOZ_ASSERT(targetFun->isInterpretedLazy());
MOZ_ASSERT(targetFun->isSelfHostedBuiltin());
RootedScript sourceScript(cx, sourceFun->getOrCreateScript(cx));
if (!sourceScript)
return false;
// Assert that there are no intervening scopes between the global scope
// and the self-hosted script. Toplevel lexicals are explicitly forbidden
// by the parser when parsing self-hosted code. The fact they have the
// global lexical scope on the scope chain is for uniformity and engine
// invariants.
MOZ_ASSERT(IsStaticGlobalLexicalScope(sourceScript->enclosingStaticScope()));
Rooted<ScopeObject*> enclosingScope(cx, &cx->global()->lexicalScope().staticBlock());
if (!CloneScriptIntoFunction(cx, enclosingScope, targetFun, sourceScript))
return false;
MOZ_ASSERT(!targetFun->isInterpretedLazy());
// The target function might have been relazified after its flags changed.
targetFun->setFlags(targetFun->flags() | sourceFun->flags());
return true;
}
bool
JSRuntime::cloneSelfHostedValue(JSContext* cx, HandlePropertyName name, MutableHandleValue vp)
{
RootedId id(cx, NameToId(name));
RootedValue selfHostedValue(cx);
if (!GetUnclonedValue(cx, HandleNativeObject::fromMarkedLocation(&selfHostingGlobal_), id, &selfHostedValue))
return false;
/*
* We don't clone if we're operating in the self-hosting global, as that
* means we're currently executing the self-hosting script while
* initializing the runtime (see JSRuntime::initSelfHosting).
*/
if (cx->global() == selfHostingGlobal_) {
vp.set(selfHostedValue);
return true;
}
return CloneValue(cx, selfHostedValue, vp);
}
JSFunction*
js::SelfHostedFunction(JSContext* cx, HandlePropertyName propName)
{
RootedValue func(cx);
if (!GlobalObject::getIntrinsicValue(cx, cx->global(), propName, &func))
return nullptr;
MOZ_ASSERT(func.isObject());
MOZ_ASSERT(func.toObject().is<JSFunction>());
return &func.toObject().as<JSFunction>();
}
bool
js::IsSelfHostedFunctionWithName(JSFunction* fun, JSAtom* name)
{
return fun->isSelfHostedBuiltin() &&
fun->getExtendedSlot(LAZY_FUNCTION_NAME_SLOT).toString() == name;
}
static_assert(JSString::MAX_LENGTH <= INT32_MAX,
"StringIteratorNext in builtin/String.js assumes the stored index "
"into the string is an Int32Value");