blob: aecc8952607f5e016867befd5b8d27f5283f09d9 [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 "jscntxt.h"
#include "jscompartment.h"
#include "jsobj.h"
#include "jsfriendapi.h"
#include "builtin/Intl.h"
#include "builtin/ParallelArray.h"
#include "gc/Marking.h"
#include "vm/ForkJoin.h"
#include "vm/Interpreter.h"
#include "jsfuninlines.h"
#include "jstypedarrayinlines.h"
#include "vm/BooleanObject-inl.h"
#include "vm/NumberObject-inl.h"
#include "vm/RegExpObject-inl.h"
#include "vm/StringObject-inl.h"
#include "selfhosted.out.h"
#if defined(DEBUG) && defined(STARBOARD)
// On Starboard platforms, DEBUG will get #undef'd when we #include zlib.h
#define STARBOARD_DEBUG
#endif
#ifdef USE_ZLIB
#include "zlib.h"
#endif
using namespace js;
using namespace js::selfhosted;
namespace js {
/*
* A linked-list container for self-hosted prototypes that have need of a
* Class for reserved slots. These are freed when self-hosting is destroyed at
* the destruction of the last context.
*/
struct SelfHostedClass
{
/* Next class in the list. */
SelfHostedClass *next;
/* Class of instances. */
Class class_;
/*
* Create a new self-hosted proto with its class set to a new dynamically
* allocated class with numSlots reserved slots.
*/
static JSObject *newPrototype(JSContext *cx, uint32_t numSlots);
static bool is(JSContext *cx, Class *clasp);
SelfHostedClass(const char *name, uint32_t numSlots);
};
} /* namespace js */
JSObject *
SelfHostedClass::newPrototype(JSContext *cx, uint32_t numSlots)
{
/* Allocate a new self hosted class and prepend it to the list. */
SelfHostedClass *shClass = cx->new_<SelfHostedClass>("Self-hosted Class", numSlots);
if (!shClass)
return NULL;
cx->runtime()->addSelfHostedClass(shClass);
Rooted<GlobalObject *> global(cx, cx->global());
RootedObject proto(cx, global->createBlankPrototype(cx, &shClass->class_));
if (!proto)
return NULL;
return proto;
}
bool
SelfHostedClass::is(JSContext *cx, Class *clasp)
{
SelfHostedClass *shClass = cx->runtime()->selfHostedClasses();
while (shClass) {
if (clasp == &shClass->class_)
return true;
shClass = shClass->next;
}
return false;
}
SelfHostedClass::SelfHostedClass(const char *name, uint32_t numSlots)
{
mozilla::PodZero(this);
class_.name = name;
class_.flags = JSCLASS_HAS_RESERVED_SLOTS(numSlots);
class_.addProperty = JS_PropertyStub;
class_.delProperty = JS_DeletePropertyStub;
class_.getProperty = JS_PropertyStub;
class_.setProperty = JS_StrictPropertyStub;
class_.enumerate = JS_EnumerateStub;
class_.resolve = JS_ResolveStub;
class_.convert = JS_ConvertStub;
}
static void
selfHosting_ErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
{
PrintError(cx, stderr, message, report, true);
}
static JSClass self_hosting_global_class = {
"self-hosting-global", JSCLASS_GLOBAL_FLAGS,
JS_PropertyStub, JS_DeletePropertyStub,
JS_PropertyStub, JS_StrictPropertyStub,
JS_EnumerateStub, JS_ResolveStub,
JS_ConvertStub, NULL
};
JSBool
js::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 JSBool
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().setDouble(result);
return true;
}
JSBool
js::intrinsic_IsCallable(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
Value val = args[0];
bool isCallable = val.isObject() && val.toObject().isCallable();
args.rval().setBoolean(isCallable);
return true;
}
JSBool
js::intrinsic_ThrowError(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() >= 1);
uint32_t errorNumber = args[0].toInt32();
char *errorArgs[3] = {NULL, NULL, NULL};
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 false;
errorArgs[i - 1] = JS_EncodeString(cx, str);
} else if (val.isString()) {
errorArgs[i - 1] = JS_EncodeString(cx, ToString<CanGC>(cx, val));
} else {
errorArgs[i - 1] = DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, val, NullPtr());
}
if (!errorArgs[i - 1])
return false;
}
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, errorNumber,
errorArgs[0], errorArgs[1], errorArgs[2]);
for (unsigned i = 0; i < 3; i++)
js_free(errorArgs[i]);
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 JSBool
intrinsic_AssertionFailed(JSContext *cx, unsigned argc, Value *vp)
{
#if defined(DEBUG) || defined(STARBOARD_DEBUG)
CallArgs args = CallArgsFromVp(argc, vp);
if (args.length() > 0) {
// try to dump the informative string
JSString *str = ToString<CanGC>(cx, args.handleAt(0));
if (str) {
const jschar *chars = str->getChars(cx);
if (chars) {
fprintf(stderr, "Self-hosted JavaScript assertion info: ");
JSString::dumpChars(chars, str->length());
fputc('\n', stderr);
}
}
}
#endif
JS_ASSERT(false);
return false;
}
static JSBool
intrinsic_MakeConstructible(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 2);
JS_ASSERT(args[0].isObject());
JS_ASSERT(args[0].toObject().is<JSFunction>());
JS_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 (!JSObject::defineProperty(cx, ctor, cx->names().classPrototype, args.handleAt(1),
JS_PropertyStub, JS_StrictPropertyStub,
JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT))
{
return false;
}
ctor->as<JSFunction>().setIsSelfHostedConstructor();
args.rval().setUndefined();
return true;
}
static JSBool
intrinsic_MakeWrappable(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() >= 1);
JS_ASSERT(args[0].isObject());
JS_ASSERT(args[0].toObject().is<JSFunction>());
args[0].toObject().as<JSFunction>().makeWrappable();
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 JSBool
intrinsic_DecompileArg(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_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;
}
/*
* SetScriptHints(fun, flags): Sets various internal hints to the ion
* compiler for use when compiling |fun| or calls to |fun|. Flags
* should be a dictionary object.
*
* The function |fun| should be a self-hosted function (in particular,
* it *must* be a JS function).
*
* Possible flags:
* - |cloneAtCallsite: true| will hint that |fun| should be cloned
* each callsite to improve TI resolution. This is important for
* higher-order functions like |Array.map|.
*/
static JSBool
intrinsic_SetScriptHints(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() >= 2);
JS_ASSERT(args[0].isObject() && args[0].toObject().is<JSFunction>());
JS_ASSERT(args[1].isObject());
RootedFunction fun(cx, &args[0].toObject().as<JSFunction>());
RootedScript funScript(cx, fun->nonLazyScript());
RootedObject flags(cx, &args[1].toObject());
RootedId id(cx);
RootedValue propv(cx);
id = AtomToId(Atomize(cx, "cloneAtCallsite", strlen("cloneAtCallsite")));
if (!JSObject::getGeneric(cx, flags, flags, id, &propv))
return false;
if (ToBoolean(propv))
funScript->shouldCloneAtCallsite = true;
args.rval().setUndefined();
return true;
}
#if defined(DEBUG) || defined(STARBOARD_DEBUG)
/*
* Dump(val): Dumps a value for debugging, even in parallel mode.
*/
JSBool
js::intrinsic_Dump(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
RootedValue val(cx, args[0]);
js_DumpValue(val);
args.rval().setUndefined();
return true;
}
#endif
/*
* ForkJoin(func, feedback): Invokes |func| many times in parallel.
*
* See ForkJoin.cpp for details and ParallelArray.js for examples.
*/
static JSBool
intrinsic_ForkJoin(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
return ForkJoin(cx, args);
}
/*
* ForkJoinSlices(): Returns the number of parallel slices that will
* be created by ForkJoin().
*/
static JSBool
intrinsic_ForkJoinSlices(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setInt32(ForkJoinSlices(cx));
return true;
}
/*
* NewParallelArray(init, ...args): Creates a new parallel array using
* an initialization function |init|. All subsequent arguments are
* passed to |init|. The new instance will be passed as the |this|
* argument.
*/
JSBool
js::intrinsic_NewParallelArray(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args[0].isObject() && args[0].toObject().is<JSFunction>());
RootedFunction init(cx, &args[0].toObject().as<JSFunction>());
CallArgs args0 = CallArgsFromVp(argc - 1, vp + 1);
if (!js::ParallelArrayObject::constructHelper(cx, &init, args0))
return false;
args.rval().set(args0.rval());
return true;
}
/*
* NewDenseArray(length): Allocates and returns a new dense array with
* the given length where all values are initialized to holes.
*/
JSBool
js::intrinsic_NewDenseArray(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
// Check that index is an int32
if (!args[0].isInt32()) {
JS_ReportError(cx, "Expected int32 as second argument");
return false;
}
uint32_t length = args[0].toInt32();
// Make a new buffer and initialize it up to length.
RootedObject buffer(cx, NewDenseAllocatedArray(cx, length));
if (!buffer)
return false;
types::TypeObject *newtype = types::GetTypeCallerInitObject(cx, JSProto_Array);
if (!newtype)
return false;
buffer->setType(newtype);
JSObject::EnsureDenseResult edr = buffer->ensureDenseElements(cx, length, 0);
switch (edr) {
case JSObject::ED_OK:
args.rval().setObject(*buffer);
return true;
case JSObject::ED_SPARSE: // shouldn't happen!
JS_ASSERT(!"%EnsureDenseArrayElements() would yield sparse array");
JS_ReportError(cx, "%EnsureDenseArrayElements() would yield sparse array");
break;
case JSObject::ED_FAILED:
break;
}
return false;
}
/*
* UnsafeSetElement(arr0, idx0, elem0, ..., arrN, idxN, elemN): For
* each set of (arr, idx, elem) arguments that are passed, performs
* the assignment |arr[idx] = elem|. |arr| must be either a dense array
* or a typed array.
*
* If |arr| is a dense array, the index must be an int32 less than the
* initialized length of |arr|. Use |%EnsureDenseResultArrayElements|
* to ensure that the initialized length is long enough.
*
* If |arr| is a typed array, the index must be an int32 less than the
* length of |arr|.
*/
JSBool
js::intrinsic_UnsafeSetElement(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
if ((args.length() % 3) != 0) {
JS_ReportError(cx, "Incorrect number of arguments, not divisible by 3");
return false;
}
for (uint32_t base = 0; base < args.length(); base += 3) {
uint32_t arri = base;
uint32_t idxi = base+1;
uint32_t elemi = base+2;
JS_ASSERT(args[arri].isObject());
JS_ASSERT(args[arri].toObject().isNative() ||
args[arri].toObject().isTypedArray());
JS_ASSERT(args[idxi].isInt32());
RootedObject arrobj(cx, &args[arri].toObject());
uint32_t idx = args[idxi].toInt32();
if (arrobj->isNative()) {
JS_ASSERT(idx < arrobj->getDenseInitializedLength());
JSObject::setDenseElementWithType(cx, arrobj, idx, args[elemi]);
} else {
JS_ASSERT(idx < TypedArray::length(arrobj));
RootedValue tmp(cx, args[elemi]);
// XXX: Always non-strict.
if (!JSObject::setElement(cx, arrobj, arrobj, idx, &tmp, false))
return false;
}
}
args.rval().setUndefined();
return true;
}
JSBool
js::intrinsic_UnsafeSetReservedSlot(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 3);
JS_ASSERT(args[0].isObject());
JS_ASSERT(args[1].isInt32());
args[0].toObject().setReservedSlot(args[1].toPrivateUint32(), args[2]);
args.rval().setUndefined();
return true;
}
JSBool
js::intrinsic_UnsafeGetReservedSlot(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 2);
JS_ASSERT(args[0].isObject());
JS_ASSERT(args[1].isInt32());
args.rval().set(args[0].toObject().getReservedSlot(args[1].toPrivateUint32()));
return true;
}
static JSBool
intrinsic_NewClassPrototype(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 1);
JS_ASSERT(args[0].isInt32());
JSObject *proto = SelfHostedClass::newPrototype(cx, args[0].toPrivateUint32());
if (!proto)
return false;
args.rval().setObject(*proto);
return true;
}
JSBool
js::intrinsic_NewObjectWithClassPrototype(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 1);
JS_ASSERT(args[0].isObject());
RootedObject proto(cx, &args[0].toObject());
JSObject *result = NewObjectWithGivenProto(cx, proto->getClass(), proto, cx->global());
if (!result)
return false;
args.rval().setObject(*result);
return true;
}
JSBool
js::intrinsic_HaveSameClass(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
JS_ASSERT(args.length() == 2);
JS_ASSERT(args[0].isObject());
JS_ASSERT(args[1].isObject());
args.rval().setBoolean(args[0].toObject().getClass() == args[1].toObject().getClass());
return true;
}
/*
* ParallelTestsShouldPass(): Returns false if we are running in a
* mode (such as --ion-eager) that is known to cause additional
* bailouts or disqualifications for parallel array tests.
*
* This is needed because the parallel tests generally assert that,
* under normal conditions, they will run without bailouts or
* compilation failures, but this does not hold under "stress-testing"
* conditions like --ion-eager or --no-ti. However, running the tests
* under those conditions HAS exposed bugs and thus we do not wish to
* disable them entirely. Instead, we simply disable the assertions
* that state that no bailouts etc should occur.
*/
static JSBool
intrinsic_ParallelTestsShouldPass(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
args.rval().setBoolean(ParallelTestsShouldPass(cx));
return true;
}
/*
* ShouldForceSequential(): Returns true if parallel ops should take
* the sequential fallback path.
*/
JSBool
js::intrinsic_ShouldForceSequential(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
#ifdef JS_THREADSAFE
args.rval().setBoolean(cx->runtime()->parallelWarmup ||
InParallelSection());
#else
args.rval().setBoolean(true);
#endif
return true;
}
/**
* Returns the default locale as a well-formed, but not necessarily canonicalized,
* BCP-47 language tag.
*/
static JSBool
intrinsic_RuntimeDefaultLocale(JSContext *cx, unsigned argc, Value *vp)
{
CallArgs args = CallArgsFromVp(argc, vp);
const char *locale = cx->runtime()->getDefaultLocale();
if (!locale) {
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_DEFAULT_LOCALE_ERROR);
return false;
}
RootedString jslocale(cx, JS_NewStringCopyZ(cx, locale));
if (!jslocale)
return false;
args.rval().setString(jslocale);
return true;
}
const JSFunctionSpec intrinsic_functions[] = {
JS_FN("ToObject", intrinsic_ToObject, 1,0),
JS_FN("ToInteger", intrinsic_ToInteger, 1,0),
JS_FN("IsCallable", intrinsic_IsCallable, 1,0),
JS_FN("ThrowError", intrinsic_ThrowError, 4,0),
JS_FN("AssertionFailed", intrinsic_AssertionFailed, 1,0),
JS_FN("SetScriptHints", intrinsic_SetScriptHints, 2,0),
JS_FN("MakeConstructible", intrinsic_MakeConstructible, 1,0),
JS_FN("MakeWrappable", intrinsic_MakeWrappable, 1,0),
JS_FN("DecompileArg", intrinsic_DecompileArg, 2,0),
JS_FN("RuntimeDefaultLocale", intrinsic_RuntimeDefaultLocale, 0,0),
JS_FN("UnsafeSetElement", intrinsic_UnsafeSetElement, 3,0),
JS_FN("UnsafeSetReservedSlot", intrinsic_UnsafeSetReservedSlot, 3,0),
JS_FN("UnsafeGetReservedSlot", intrinsic_UnsafeGetReservedSlot, 2,0),
JS_FN("NewClassPrototype", intrinsic_NewClassPrototype, 1,0),
JS_FN("NewObjectWithClassPrototype", intrinsic_NewObjectWithClassPrototype, 1,0),
JS_FN("HaveSameClass", intrinsic_HaveSameClass, 2,0),
JS_FN("ForkJoin", intrinsic_ForkJoin, 2,0),
JS_FN("ForkJoinSlices", intrinsic_ForkJoinSlices, 0,0),
JS_FN("NewParallelArray", intrinsic_NewParallelArray, 3,0),
JS_FN("NewDenseArray", intrinsic_NewDenseArray, 1,0),
JS_FN("ShouldForceSequential", intrinsic_ShouldForceSequential, 0,0),
JS_FN("ParallelTestsShouldPass", intrinsic_ParallelTestsShouldPass, 0,0),
// 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),
#if defined(DEBUG) || defined(STARBOARD_DEBUG)
JS_FN("Dump", intrinsic_Dump, 1,0),
#endif
JS_FS_END
};
bool
JSRuntime::initSelfHosting(JSContext *cx)
{
JS_ASSERT(!selfHostingGlobal_);
RootedObject savedGlobal(cx, js::GetDefaultGlobalForContext(cx));
if (!(selfHostingGlobal_ = JS_NewGlobalObject(cx, &self_hosting_global_class, NULL)))
return false;
JSAutoCompartment ac(cx, selfHostingGlobal_);
JS_SetGlobalObject(cx, selfHostingGlobal_);
Rooted<GlobalObject*> shg(cx, &selfHostingGlobal_->as<GlobalObject>());
/*
* During initialization of standard classes for the self-hosting global,
* all self-hosted functions are ignored. Thus, we don't create cyclic
* dependencies in the order of initialization.
*/
if (!GlobalObject::initStandardClasses(cx, shg))
return false;
if (!JS_DefineFunctions(cx, shg, intrinsic_functions))
return false;
CompileOptions options(cx);
options.setFileAndLine("self-hosted", 1);
options.setSelfHostingMode(true);
options.setCanLazilyParse(false);
options.setSourcePolicy(CompileOptions::NO_SOURCE);
options.setVersion(JSVERSION_LATEST);
/*
* 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, selfHosting_ErrorReporter);
Value rv;
bool ok = false;
#if defined(STARBOARD)
char *filename = NULL;
#else
char *filename = getenv("MOZ_SELFHOSTEDJS");
#endif
if (filename) {
RootedScript script(cx, Compile(cx, shg, options, filename));
if (script)
ok = Execute(cx, script, *shg.get(), &rv);
} else {
uint32_t srcLen = GetRawScriptsSize();
#ifdef USE_ZLIB
const unsigned char *compressed = compressedSources;
uint32_t compressedLen = GetCompressedSize();
ScopedJSFreePtr<char> src(reinterpret_cast<char *>(cx->malloc_(srcLen)));
if (!src || !DecompressString(compressed, compressedLen,
reinterpret_cast<unsigned char *>(src.get()), srcLen))
{
return false;
}
#else
const char *src = rawSources;
#endif
ok = Evaluate(cx, shg, options, src, srcLen, &rv);
}
JS_SetErrorReporter(cx, oldReporter);
JS_SetGlobalObject(cx, savedGlobal);
return ok;
}
void
JSRuntime::finishSelfHosting()
{
selfHostingGlobal_ = NULL;
SelfHostedClass *shClass = selfHostedClasses_;
while (shClass) {
SelfHostedClass *tmp = shClass;
shClass = shClass->next;
js_delete(tmp);
}
selfHostedClasses_ = NULL;
}
void
JSRuntime::markSelfHostingGlobal(JSTracer *trc)
{
if (selfHostingGlobal_)
MarkObjectRoot(trc, &selfHostingGlobal_, "self-hosting global");
}
void
JSRuntime::addSelfHostedClass(SelfHostedClass *shClass)
{
shClass->next = selfHostedClasses_;
selfHostedClasses_ = shClass;
}
typedef AutoObjectObjectHashMap CloneMemory;
static bool CloneValue(JSContext *cx, MutableHandleValue vp, CloneMemory &clonedObjects);
static bool
GetUnclonedValue(JSContext *cx, Handle<JSObject*> src, HandleId id, MutableHandleValue vp)
{
AutoCompartment ac(cx, src);
return JSObject::getGeneric(cx, src, src, id, vp);
}
static bool
CloneProperties(JSContext *cx, HandleObject obj, HandleObject clone, CloneMemory &clonedObjects)
{
RootedId id(cx);
RootedValue val(cx);
AutoIdVector ids(cx);
{
AutoCompartment ac(cx, obj);
if (!GetPropertyNames(cx, obj, JSITER_OWNONLY, &ids))
return false;
}
for (uint32_t i = 0; i < ids.length(); i++) {
id = ids[i];
if (!GetUnclonedValue(cx, obj, id, &val) ||
!CloneValue(cx, &val, clonedObjects) ||
!JS_DefinePropertyById(cx, clone, id, val.get(), NULL, NULL, 0))
{
return false;
}
}
if (SelfHostedClass::is(cx, obj->getClass())) {
for (uint32_t i = 0; i < JSCLASS_RESERVED_SLOTS(obj->getClass()); i++) {
val = obj->getReservedSlot(i);
if (!CloneValue(cx, &val, clonedObjects))
return false;
clone->setReservedSlot(i, val);
}
/* Privates are not cloned, so be careful! */
if (obj->hasPrivate())
clone->setPrivate(obj->getPrivate());
}
return true;
}
static JSObject *
CloneObject(JSContext *cx, HandleObject srcObj, CloneMemory &clonedObjects)
{
CloneMemory::AddPtr p = clonedObjects.lookupForAdd(srcObj.get());
if (p)
return p->value;
RootedObject clone(cx);
if (srcObj->is<JSFunction>()) {
if (srcObj->as<JSFunction>().isWrappable()) {
clone = srcObj;
if (!cx->compartment()->wrap(cx, clone.address()))
return NULL;
} else {
RootedFunction fun(cx, &srcObj->as<JSFunction>());
clone = CloneFunctionObject(cx, fun, cx->global(), fun->getAllocKind(), TenuredObject);
}
} else if (srcObj->is<RegExpObject>()) {
RegExpObject &reobj = srcObj->as<RegExpObject>();
RootedAtom source(cx, reobj.getSource());
clone = RegExpObject::createNoStatics(cx, source, reobj.getFlags(), NULL);
} else if (srcObj->isDate()) {
clone = JS_NewDateObjectMsec(cx, srcObj->getDateUTCTime().toNumber());
} else if (srcObj->is<BooleanObject>()) {
clone = BooleanObject::create(cx, srcObj->as<BooleanObject>().unbox());
} else if (srcObj->is<NumberObject>()) {
clone = NumberObject::create(cx, srcObj->as<NumberObject>().unbox());
} else if (srcObj->is<StringObject>()) {
Rooted<JSStableString*> str(cx, srcObj->as<StringObject>().unbox()->ensureStable(cx));
if (!str)
return NULL;
str = js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length())->ensureStable(cx);
if (!str)
return NULL;
clone = StringObject::create(cx, str);
} else if (srcObj->isArray()) {
clone = NewDenseEmptyArray(cx, NULL, TenuredObject);
} else {
JS_ASSERT(srcObj->isNative());
clone = NewObjectWithGivenProto(cx, srcObj->getClass(), NULL, cx->global(),
srcObj->tenuredGetAllocKind(), SingletonObject);
}
if (!clone || !clonedObjects.relookupOrAdd(p, srcObj.get(), clone.get()) ||
!CloneProperties(cx, srcObj, clone, clonedObjects))
{
return NULL;
}
return clone;
}
static bool
CloneValue(JSContext *cx, MutableHandleValue vp, CloneMemory &clonedObjects)
{
if (vp.isObject()) {
RootedObject obj(cx, &vp.toObject());
RootedObject clone(cx, CloneObject(cx, obj, clonedObjects));
if (!clone)
return false;
vp.setObject(*clone);
} else if (vp.isBoolean() || vp.isNumber() || vp.isNullOrUndefined()) {
// Nothing to do here: these are represented inline in the value
} else if (vp.isString()) {
Rooted<JSStableString*> str(cx, vp.toString()->ensureStable(cx));
if (!str)
return false;
RootedString clone(cx, js_NewStringCopyN<CanGC>(cx, str->chars().get(), str->length()));
if (!clone)
return false;
vp.setString(clone);
} else {
if (JSString *valSrc = JS_ValueToSource(cx, vp))
printf("Error: Can't yet clone value: %s\n", JS_EncodeString(cx, valSrc));
return false;
}
return true;
}
bool
JSRuntime::cloneSelfHostedFunctionScript(JSContext *cx, Handle<PropertyName*> name,
Handle<JSFunction*> targetFun)
{
RootedObject shg(cx, selfHostingGlobal_);
RootedValue funVal(cx);
RootedId id(cx, NameToId(name));
if (!GetUnclonedValue(cx, shg, id, &funVal))
return false;
RootedFunction sourceFun(cx, &funVal.toObject().as<JSFunction>());
RootedScript sourceScript(cx, sourceFun->nonLazyScript());
JS_ASSERT(!sourceScript->enclosingStaticScope());
JSScript *cscript = CloneScript(cx, NullPtr(), targetFun, sourceScript);
if (!cscript)
return false;
targetFun->setScript(cscript);
cscript->setFunction(targetFun);
JS_ASSERT(sourceFun->nargs == targetFun->nargs);
targetFun->flags = sourceFun->flags | JSFunction::EXTENDED;
return true;
}
bool
JSRuntime::cloneSelfHostedValue(JSContext *cx, Handle<PropertyName*> name, MutableHandleValue vp)
{
RootedObject shg(cx, selfHostingGlobal_);
RootedValue val(cx);
RootedId id(cx, NameToId(name));
if (!GetUnclonedValue(cx, shg, id, &val))
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_) {
CloneMemory clonedObjects(cx);
if (!clonedObjects.init() || !CloneValue(cx, &val, clonedObjects))
return false;
}
vp.set(val);
return true;
}
bool
JSRuntime::maybeWrappedSelfHostedFunction(JSContext *cx, Handle<PropertyName*> name,
MutableHandleValue funVal)
{
RootedObject shg(cx, selfHostingGlobal_);
RootedId id(cx, NameToId(name));
if (!GetUnclonedValue(cx, shg, id, funVal))
return false;
JS_ASSERT(funVal.isObject());
JS_ASSERT(funVal.toObject().isCallable());
if (!funVal.toObject().as<JSFunction>().isWrappable()) {
funVal.setUndefined();
return true;
}
return cx->compartment()->wrap(cx, funVal);
}