Bug 1512509 - Clone ScriptSourceObject when cloning scripts. r=tcampbell

This fixes bug 1406437. It also simplifies JSScript because it now always stores
a ScriptSourceObject directly instead of a CCW for one.

Differential Revision: https://phabricator.services.mozilla.com/D13974

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Jan de Mooij 2018-12-16 11:43:44 +00:00
parent a8df58f265
commit 100a324c5b
9 changed files with 150 additions and 82 deletions

View File

@ -806,7 +806,7 @@ void ModuleObject::init(HandleScript script) {
initReservedSlot(ScriptSlot, PrivateGCThingValue(script));
initReservedSlot(StatusSlot, Int32Value(MODULE_STATUS_UNINSTANTIATED));
initReservedSlot(ScriptSourceObjectSlot,
ObjectValue(script->scriptSourceUnwrap()));
ObjectValue(*script->sourceObject()));
}
void ModuleObject::setInitialEnvironment(

View File

@ -5527,7 +5527,7 @@ MOZ_NEVER_INLINE bool BytecodeEmitter::emitFunction(CodeNode* funNode,
const JS::TransitiveCompileOptions& transitiveOptions = parser->options();
JS::CompileOptions options(cx, transitiveOptions);
Rooted<JSObject*> sourceObject(cx, script->sourceObject());
Rooted<ScriptSourceObject*> sourceObject(cx, script->sourceObject());
Rooted<JSScript*> script(
cx, JSScript::Create(cx, options, sourceObject, funbox->bufStart,
funbox->bufEnd, funbox->toStringStart,

View File

@ -0,0 +1,6 @@
var g = newGlobal();
g.f = function() {};
g.eval('f = clone(f);');
var dbg = new Debugger;
var dg = dbg.addDebuggee(g);
dg.getOwnPropertyDescriptor('f').value.script.source;

View File

@ -3740,16 +3740,16 @@ JS_PUBLIC_API void JS::SetModulePrivate(JSObject* module,
}
JS_PUBLIC_API JS::Value JS::GetModulePrivate(JSObject* module) {
return module->as<ModuleObject>().scriptSourceObject()->getPrivate();
return module->as<ModuleObject>().scriptSourceObject()->unwrappedPrivate();
}
JS_PUBLIC_API void JS::SetScriptPrivate(JSScript* script,
const JS::Value& value) {
script->scriptSourceUnwrap().setPrivate(value);
script->sourceObject()->setPrivate(value);
}
JS_PUBLIC_API JS::Value JS::GetScriptPrivate(JSScript* script) {
return script->scriptSourceUnwrap().getPrivate();
return script->sourceObject()->unwrappedPrivate();
}
JS_PUBLIC_API bool JS::ModuleInstantiate(JSContext* cx,

View File

@ -536,9 +536,9 @@ JS_FRIEND_API bool js::NukeCrossCompartmentWrappers(
continue;
}
// We never nuke script source objects, since only ever used internally by
// the JS engine, and are expected to remain valid throughout a scripts
// lifetime.
// We never nuke ScriptSourceObjects, since they are only ever used
// internally by the JS engine, and are expected to remain valid
// throughout a script's lifetime.
if (MOZ_UNLIKELY(wrapped->is<ScriptSourceObject>())) {
continue;
}

View File

@ -7795,7 +7795,7 @@ static bool DebuggerSource_getDisplayURL(JSContext* cx, unsigned argc,
struct DebuggerSourceGetElementMatcher {
using ReturnType = JSObject*;
ReturnType match(HandleScriptSourceObject sourceObject) {
return sourceObject->element();
return sourceObject->unwrappedElement();
}
ReturnType match(Handle<WasmInstanceObject*> wasmInstance) { return nullptr; }
};
@ -7818,7 +7818,7 @@ static bool DebuggerSource_getElement(JSContext* cx, unsigned argc, Value* vp) {
struct DebuggerSourceGetElementPropertyMatcher {
using ReturnType = Value;
ReturnType match(HandleScriptSourceObject sourceObject) {
return sourceObject->elementAttributeName();
return sourceObject->unwrappedElementAttributeName();
}
ReturnType match(Handle<WasmInstanceObject*> wasmInstance) {
return UndefinedValue();
@ -7847,7 +7847,7 @@ class DebuggerSourceGetIntroductionScriptMatcher {
using ReturnType = bool;
ReturnType match(HandleScriptSourceObject sourceObject) {
RootedScript script(cx_, sourceObject->introductionScript());
RootedScript script(cx_, sourceObject->unwrappedIntroductionScript());
if (script) {
RootedObject scriptDO(cx_, dbg_->wrapScript(cx_, script));
if (!scriptDO) {
@ -7886,7 +7886,8 @@ struct DebuggerGetIntroductionOffsetMatcher {
// ScriptSource, only hand out the introduction offset if we also have
// the script within which it applies.
ScriptSource* ss = sourceObject->source();
if (ss->hasIntroductionOffset() && sourceObject->introductionScript()) {
if (ss->hasIntroductionOffset() &&
sourceObject->unwrappedIntroductionScript()) {
return Int32Value(ss->introductionOffset());
}
return UndefinedValue();

View File

@ -3365,14 +3365,14 @@ ModuleObject* js::GetModuleObjectForScript(JSScript* script) {
Value js::FindScriptOrModulePrivateForScript(JSScript* script) {
while (script) {
ScriptSourceObject* sso = &script->scriptSourceUnwrap();
Value value = sso->getPrivate();
ScriptSourceObject* sso = script->sourceObject();
Value value = sso->unwrappedPrivate();
if (!value.isUndefined()) {
return value;
}
MOZ_ASSERT(sso->introductionScript() != script);
script = sso->introductionScript();
MOZ_ASSERT(sso->unwrappedIntroductionScript() != script);
script = sso->unwrappedIntroductionScript();
}
return UndefinedValue();

View File

@ -305,7 +305,7 @@ static XDRResult XDRRelazificationInfo(XDRState<mode>* xdr, HandleFunction fun,
MOZ_TRY(xdr->codeUint64(&packedFields));
if (mode == XDR_DECODE) {
RootedScriptSourceObject sourceObject(cx, &script->scriptSourceUnwrap());
RootedScriptSourceObject sourceObject(cx, script->sourceObject());
lazy.set(LazyScript::CreateForXDR(
cx, fun, script, enclosingScope, sourceObject, packedFields,
sourceStart, sourceEnd, toStringStart, lineno, column));
@ -528,9 +528,7 @@ XDRResult js::XDRScript(XDRState<mode>* xdr, HandleScope scriptEnclosingScope,
fun->initScript(script);
}
} else {
// When encoding, we do not mutate any of the JSScript or LazyScript, so
// we can safely unwrap it here.
sourceObject = &script->scriptSourceUnwrap();
sourceObject = script->sourceObject();
}
if (mode == XDR_DECODE) {
@ -979,14 +977,14 @@ template XDRResult js::XDRLazyScript(XDRState<XDR_DECODE>*, HandleScope,
HandleScriptSourceObject, HandleFunction,
MutableHandle<LazyScript*>);
void JSScript::setSourceObject(JSObject* object) {
void JSScript::setSourceObject(js::ScriptSourceObject* object) {
MOZ_ASSERT(compartment() == object->compartment());
sourceObject_ = object;
}
void JSScript::setDefaultClassConstructorSpan(JSObject* sourceObject,
uint32_t start, uint32_t end,
unsigned line, unsigned column) {
void JSScript::setDefaultClassConstructorSpan(
js::ScriptSourceObject* sourceObject, uint32_t start, uint32_t end,
unsigned line, unsigned column) {
MOZ_ASSERT(isDefaultClassConstructor());
setSourceObject(sourceObject);
toStringStart_ = start;
@ -1000,14 +998,8 @@ void JSScript::setDefaultClassConstructorSpan(JSObject* sourceObject,
clearFlag(ImmutableFlags::SelfHosted);
}
js::ScriptSourceObject& JSScript::scriptSourceUnwrap() const {
// This may be called off the main thread. It's OK not to expose the source
// object here as it doesn't escape.
return UncheckedUnwrapWithoutExpose(sourceObject())->as<ScriptSourceObject>();
}
js::ScriptSource* JSScript::scriptSource() const {
return scriptSourceUnwrap().source();
return sourceObject()->source();
}
js::ScriptSource* JSScript::maybeForwardedScriptSource() const {
@ -1355,32 +1347,64 @@ const Class ScriptSourceObject::class_ = {
JSCLASS_FOREGROUND_FINALIZE,
&ScriptSourceObjectClassOps};
ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
ScriptSource* source) {
RootedScriptSourceObject sourceObject(
cx, NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr));
if (!sourceObject) {
ScriptSourceObject* ScriptSourceObject::createInternal(JSContext* cx,
ScriptSource* source,
HandleObject canonical) {
ScriptSourceObject* obj =
NewObjectWithGivenProto<ScriptSourceObject>(cx, nullptr);
if (!obj) {
return nullptr;
}
source->incref(); // The matching decref is in ScriptSourceObject::finalize.
sourceObject->initReservedSlot(SOURCE_SLOT, PrivateValue(source));
// The remaining slots should eventually be populated by a call to
// initFromOptions. Poison them until that point.
sourceObject->initReservedSlot(ELEMENT_SLOT, MagicValue(JS_GENERIC_MAGIC));
sourceObject->initReservedSlot(ELEMENT_PROPERTY_SLOT,
MagicValue(JS_GENERIC_MAGIC));
sourceObject->initReservedSlot(INTRODUCTION_SCRIPT_SLOT,
MagicValue(JS_GENERIC_MAGIC));
obj->initReservedSlot(SOURCE_SLOT, PrivateValue(source));
return sourceObject;
if (canonical) {
obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*canonical));
} else {
obj->initReservedSlot(CANONICAL_SLOT, ObjectValue(*obj));
}
// The slots below should either be populated by a call to initFromOptions or,
// if this is a non-canonical ScriptSourceObject, they are unused. Poison
// them.
obj->initReservedSlot(ELEMENT_SLOT, MagicValue(JS_GENERIC_MAGIC));
obj->initReservedSlot(ELEMENT_PROPERTY_SLOT, MagicValue(JS_GENERIC_MAGIC));
obj->initReservedSlot(INTRODUCTION_SCRIPT_SLOT, MagicValue(JS_GENERIC_MAGIC));
return obj;
}
ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
ScriptSource* source) {
return createInternal(cx, source, nullptr);
}
ScriptSourceObject* ScriptSourceObject::clone(JSContext* cx,
HandleScriptSourceObject sso) {
MOZ_ASSERT(cx->compartment() != sso->compartment());
RootedObject wrapped(cx, sso);
if (!cx->compartment()->wrap(cx, &wrapped)) {
return nullptr;
}
return createInternal(cx, sso->source(), wrapped);
}
ScriptSourceObject* ScriptSourceObject::unwrappedCanonical() const {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtimeFromAnyThread()));
JSObject* obj = &getReservedSlot(CANONICAL_SLOT).toObject();
return &UncheckedUnwrap(obj)->as<ScriptSourceObject>();
}
/* static */ bool ScriptSourceObject::initFromOptions(
JSContext* cx, HandleScriptSourceObject source,
const ReadOnlyCompileOptions& options) {
cx->releaseCheck(source);
MOZ_ASSERT(source->isCanonical());
MOZ_ASSERT(source->getReservedSlot(ELEMENT_SLOT).isMagic(JS_GENERIC_MAGIC));
MOZ_ASSERT(
source->getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic(JS_GENERIC_MAGIC));
@ -1411,6 +1435,8 @@ ScriptSourceObject* ScriptSourceObject::create(JSContext* cx,
/* static */ bool ScriptSourceObject::initElementProperties(
JSContext* cx, HandleScriptSourceObject source, HandleObject element,
HandleString elementAttrName) {
MOZ_ASSERT(source->isCanonical());
RootedValue elementValue(cx, ObjectOrNullValue(element));
if (!cx->compartment()->wrap(cx, &elementValue)) {
return false;
@ -3044,7 +3070,7 @@ void PrivateScriptData::traceChildren(JSTracer* trc) {
}
JSScript::JSScript(JS::Realm* realm, uint8_t* stubEntry,
HandleObject sourceObject, uint32_t sourceStart,
HandleScriptSourceObject sourceObject, uint32_t sourceStart,
uint32_t sourceEnd, uint32_t toStringStart,
uint32_t toStringEnd)
:
@ -3065,7 +3091,8 @@ JSScript::JSScript(JS::Realm* realm, uint8_t* stubEntry,
setSourceObject(sourceObject);
}
/* static */ JSScript* JSScript::New(JSContext* cx, HandleObject sourceObject,
/* static */ JSScript* JSScript::New(JSContext* cx,
HandleScriptSourceObject sourceObject,
uint32_t sourceStart, uint32_t sourceEnd,
uint32_t toStringStart,
uint32_t toStringEnd) {
@ -3087,8 +3114,8 @@ JSScript::JSScript(JS::Realm* realm, uint8_t* stubEntry,
/* static */ JSScript* JSScript::Create(
JSContext* cx, const ReadOnlyCompileOptions& options,
HandleObject sourceObject, uint32_t sourceStart, uint32_t sourceEnd,
uint32_t toStringStart, uint32_t toStringEnd) {
HandleScriptSourceObject sourceObject, uint32_t sourceStart,
uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringEnd) {
RootedScript script(cx, JSScript::New(cx, sourceObject, sourceStart,
sourceEnd, toStringStart, toStringEnd));
if (!script) {
@ -4004,7 +4031,7 @@ static JSScript* CreateEmptyScriptForClone(JSContext* cx, HandleScript src) {
* in another runtime, so lazily create a new script source object to
* use for them.
*/
RootedObject sourceObject(cx);
RootedScriptSourceObject sourceObject(cx);
if (src->realm()->isSelfHostingRealm()) {
if (!cx->realm()->selfHostingScriptSource) {
CompileOptions options(cx);
@ -4019,8 +4046,11 @@ static JSScript* CreateEmptyScriptForClone(JSContext* cx, HandleScript src) {
sourceObject = cx->realm()->selfHostingScriptSource;
} else {
sourceObject = src->sourceObject();
if (!cx->compartment()->wrap(cx, &sourceObject)) {
return nullptr;
if (cx->compartment() != sourceObject->compartment()) {
sourceObject = ScriptSourceObject::clone(cx, sourceObject);
if (!sourceObject) {
return nullptr;
}
}
}

View File

@ -1103,15 +1103,40 @@ class ScriptSourceHolder {
ScriptSource* get() const { return ss; }
};
// [SMDOC] ScriptSourceObject
//
// ScriptSourceObject stores the ScriptSource and GC pointers related to it.
//
// ScriptSourceObjects can be cloned when we clone the JSScript (in order to
// execute the script in a different realm/compartment). In this case we create
// a new SSO that stores (a wrapper for) the original SSO in its "canonical
// slot". The canonical SSO is always used for the private, introductionScript,
// element, elementAttributeName slots. This means their accessors may return an
// object in a different compartment, hence the "unwrapped" prefix.
//
// We need ScriptSourceObject (instead of storing these GC pointers in the
// ScriptSource itself) to properly account for cross-zone pointers: the
// canonical SSO will be stored in the wrapper map if necessary so GC will do
// the right thing.
class ScriptSourceObject : public NativeObject {
static const ClassOps classOps_;
static ScriptSourceObject* createInternal(JSContext* cx, ScriptSource* source,
HandleObject canonical);
bool isCanonical() const {
return &getReservedSlot(CANONICAL_SLOT).toObject() == this;
}
ScriptSourceObject* unwrappedCanonical() const;
public:
static const Class class_;
static void trace(JSTracer* trc, JSObject* obj);
static void finalize(FreeOp* fop, JSObject* obj);
static ScriptSourceObject* create(JSContext* cx, ScriptSource* source);
static ScriptSourceObject* clone(JSContext* cx, HandleScriptSourceObject sso);
// Initialize those properties of this ScriptSourceObject whose values
// are provided by |options|, re-wrapping as necessary.
@ -1127,27 +1152,38 @@ class ScriptSourceObject : public NativeObject {
ScriptSource* source() const {
return static_cast<ScriptSource*>(getReservedSlot(SOURCE_SLOT).toPrivate());
}
JSObject* element() const {
return getReservedSlot(ELEMENT_SLOT).toObjectOrNull();
JSObject* unwrappedElement() const {
return unwrappedCanonical()->getReservedSlot(ELEMENT_SLOT).toObjectOrNull();
}
const Value& elementAttributeName() const {
MOZ_ASSERT(!getReservedSlot(ELEMENT_PROPERTY_SLOT).isMagic());
return getReservedSlot(ELEMENT_PROPERTY_SLOT);
const Value& unwrappedElementAttributeName() const {
const Value& v =
unwrappedCanonical()->getReservedSlot(ELEMENT_PROPERTY_SLOT);
MOZ_ASSERT(!v.isMagic());
return v;
}
JSScript* introductionScript() const {
Value value = getReservedSlot(INTRODUCTION_SCRIPT_SLOT);
JSScript* unwrappedIntroductionScript() const {
Value value =
unwrappedCanonical()->getReservedSlot(INTRODUCTION_SCRIPT_SLOT);
if (value.isUndefined()) {
return nullptr;
}
return value.toGCThing()->as<JSScript>();
}
void setPrivate(const Value& value) { setReservedSlot(PRIVATE_SLOT, value); }
Value getPrivate() const { return getReservedSlot(PRIVATE_SLOT); }
void setPrivate(const Value& value) {
MOZ_ASSERT(isCanonical());
setReservedSlot(PRIVATE_SLOT, value);
}
Value unwrappedPrivate() const {
return unwrappedCanonical()->getReservedSlot(PRIVATE_SLOT);
}
private:
enum {
SOURCE_SLOT = 0,
CANONICAL_SLOT,
ELEMENT_SLOT,
ELEMENT_PROPERTY_SLOT,
INTRODUCTION_SCRIPT_SLOT,
@ -1480,11 +1516,8 @@ class JSScript : public js::gc::TenuredCell {
/* Persistent type information retained across GCs. */
js::TypeScript* types_ = nullptr;
// This script's ScriptSourceObject, or a CCW thereof.
//
// (When we clone a JSScript into a new compartment, we don't clone its
// source object. Instead, the clone refers to a wrapper.)
js::GCPtrObject sourceObject_ = {};
// This script's ScriptSourceObject.
js::GCPtr<js::ScriptSourceObject*> sourceObject_ = {};
/*
* Information attached by Ion. Nexto a valid IonScript this could be
@ -1740,20 +1773,20 @@ class JSScript : public js::gc::TenuredCell {
js::MutableHandle<JS::GCVector<js::Scope*>> scopes);
private:
JSScript(JS::Realm* realm, uint8_t* stubEntry, js::HandleObject sourceObject,
uint32_t sourceStart, uint32_t sourceEnd, uint32_t toStringStart,
uint32_t toStringend);
JSScript(JS::Realm* realm, uint8_t* stubEntry,
js::HandleScriptSourceObject sourceObject, uint32_t sourceStart,
uint32_t sourceEnd, uint32_t toStringStart, uint32_t toStringend);
static JSScript* New(JSContext* cx, js::HandleObject sourceObject,
static JSScript* New(JSContext* cx, js::HandleScriptSourceObject sourceObject,
uint32_t sourceStart, uint32_t sourceEnd,
uint32_t toStringStart, uint32_t toStringEnd);
public:
static JSScript* Create(JSContext* cx,
const JS::ReadOnlyCompileOptions& options,
js::HandleObject sourceObject, uint32_t sourceStart,
uint32_t sourceEnd, uint32_t toStringStart,
uint32_t toStringEnd);
js::HandleScriptSourceObject sourceObject,
uint32_t sourceStart, uint32_t sourceEnd,
uint32_t toStringStart, uint32_t toStringEnd);
// NOTE: If you use createPrivateScriptData directly instead of via
// fullyInitFromEmitter, you are responsible for notifying the debugger
@ -2281,15 +2314,14 @@ class JSScript : public js::gc::TenuredCell {
static bool loadSource(JSContext* cx, js::ScriptSource* ss, bool* worked);
void setSourceObject(JSObject* object);
JSObject* sourceObject() const { return sourceObject_; }
js::ScriptSourceObject& scriptSourceUnwrap() const;
void setSourceObject(js::ScriptSourceObject* object);
js::ScriptSourceObject* sourceObject() const { return sourceObject_; }
js::ScriptSource* scriptSource() const;
js::ScriptSource* maybeForwardedScriptSource() const;
void setDefaultClassConstructorSpan(JSObject* sourceObject, uint32_t start,
uint32_t end, unsigned line,
unsigned column);
void setDefaultClassConstructorSpan(js::ScriptSourceObject* sourceObject,
uint32_t start, uint32_t end,
unsigned line, unsigned column);
bool mutedErrors() const { return scriptSource()->mutedErrors(); }
const char* filename() const { return scriptSource()->filename(); }
@ -2787,9 +2819,8 @@ class LazyScript : public gc::TenuredCell {
GCPtr<TenuredCell*> enclosingLazyScriptOrScope_;
// ScriptSourceObject. We leave this set to nullptr until we generate
// bytecode for our immediate parent. This is never a CCW; we don't clone
// LazyScripts into other compartments.
GCPtrObject sourceObject_;
// bytecode for our immediate parent.
GCPtr<ScriptSourceObject*> sourceObject_;
// Heap allocated table with any free variables or inner functions.
void* table_;