mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-07 20:17:37 +00:00
Bug 673569 - Allow scripts to be run in a non-global scope (r=luke)
This commit is contained in:
parent
303f193952
commit
dc364b8178
@ -479,3 +479,44 @@ js::IsAnyBuiltinEval(JSFunction *fun)
|
||||
{
|
||||
return fun->maybeNative() == IndirectEval;
|
||||
}
|
||||
|
||||
JS_FRIEND_API(bool)
|
||||
js::ExecuteInGlobalAndReturnScope(JSContext *cx, HandleObject global, HandleScript scriptArg,
|
||||
MutableHandleObject scopeArg)
|
||||
{
|
||||
CHECK_REQUEST(cx);
|
||||
assertSameCompartment(cx, global);
|
||||
MOZ_ASSERT(global->is<GlobalObject>());
|
||||
|
||||
RootedScript script(cx, scriptArg);
|
||||
if (script->compartment() != cx->compartment()) {
|
||||
script = CloneScript(cx, NullPtr(), NullPtr(), script);
|
||||
if (!script)
|
||||
return false;
|
||||
}
|
||||
|
||||
RootedObject scope(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr()));
|
||||
if (!scope)
|
||||
return false;
|
||||
|
||||
if (!scope->setQualifiedVarObj(cx))
|
||||
return false;
|
||||
|
||||
if (!scope->setUnqualifiedVarObj(cx))
|
||||
return false;
|
||||
|
||||
JSObject *thisobj = JSObject::thisObject(cx, global);
|
||||
if (!thisobj)
|
||||
return false;
|
||||
|
||||
RootedValue thisv(cx, ObjectValue(*thisobj));
|
||||
RootedValue rval(cx);
|
||||
if (!ExecuteKernel(cx, script, *scope, thisv, EXECUTE_GLOBAL,
|
||||
NullFramePtr() /* evalInFrame */, rval.address()))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
scopeArg.set(scope);
|
||||
return true;
|
||||
}
|
||||
|
@ -1890,6 +1890,47 @@ FindPath(JSContext *cx, unsigned argc, jsval *vp)
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool
|
||||
EvalReturningScope(JSContext *cx, unsigned argc, jsval *vp)
|
||||
{
|
||||
CallArgs args = CallArgsFromVp(argc, vp);
|
||||
|
||||
RootedString str(cx);
|
||||
if (!JS_ConvertArguments(cx, args, "S", str.address()))
|
||||
return false;
|
||||
|
||||
AutoStableStringChars strChars(cx);
|
||||
if (!strChars.initTwoByte(cx, str))
|
||||
return false;
|
||||
|
||||
mozilla::Range<const jschar> chars = strChars.twoByteRange();
|
||||
size_t srclen = chars.length();
|
||||
const jschar *src = chars.start().get();
|
||||
|
||||
JS::AutoFilename filename;
|
||||
unsigned lineno;
|
||||
|
||||
DescribeScriptedCaller(cx, &filename, &lineno);
|
||||
|
||||
JS::CompileOptions options(cx);
|
||||
options.setFileAndLine(filename.get(), lineno);
|
||||
options.setNoScriptRval(true);
|
||||
options.setCompileAndGo(false);
|
||||
|
||||
JS::SourceBufferHolder srcBuf(src, srclen, JS::SourceBufferHolder::NoOwnership);
|
||||
RootedScript script(cx);
|
||||
if (!JS::Compile(cx, JS::NullPtr(), options, srcBuf, &script))
|
||||
return false;
|
||||
|
||||
RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
|
||||
RootedObject scope(cx);
|
||||
if (!js::ExecuteInGlobalAndReturnScope(cx, global, script, &scope))
|
||||
return false;
|
||||
|
||||
args.rval().setObject(*scope);
|
||||
return true;
|
||||
}
|
||||
|
||||
static const JSFunctionSpecWithHelp TestingFunctions[] = {
|
||||
JS_FN_HELP("gc", ::GC, 0, 0,
|
||||
"gc([obj] | 'compartment')",
|
||||
@ -2189,6 +2230,10 @@ static const JSFunctionSpecWithHelp TestingFunctions[] = {
|
||||
" Dump an internal representation of an object."),
|
||||
#endif
|
||||
|
||||
JS_FN_HELP("evalReturningScope", EvalReturningScope, 1, 0,
|
||||
"evalReturningScope(scriptStr)",
|
||||
" Evaluate the script in a new scope and return the scope."),
|
||||
|
||||
JS_FS_HELP_END
|
||||
};
|
||||
|
||||
|
24
js/src/jit-test/tests/basic/bug673569.js
Normal file
24
js/src/jit-test/tests/basic/bug673569.js
Normal file
@ -0,0 +1,24 @@
|
||||
function qualified_tests(prefix) {
|
||||
let scope = evalReturningScope(prefix + "let x = 1");
|
||||
assertEq(scope.x, 1);
|
||||
|
||||
scope = evalReturningScope(prefix + "var x = 1");
|
||||
assertEq(scope.x, 1);
|
||||
|
||||
scope = evalReturningScope(prefix + "const x = 1");
|
||||
assertEq(scope.x, 1);
|
||||
}
|
||||
|
||||
qualified_tests("");
|
||||
qualified_tests("'use strict'; ");
|
||||
|
||||
let scope = evalReturningScope("x = 1");
|
||||
assertEq(scope.x, 1);
|
||||
|
||||
let fail = true;
|
||||
try {
|
||||
evalReturningScope("'use strict'; x = 1");
|
||||
} catch (e) {
|
||||
fail = false;
|
||||
}
|
||||
assertEq(fail, false);
|
@ -5974,7 +5974,7 @@ DoBindNameFallback(JSContext *cx, BaselineFrame *frame, ICBindName_Fallback *stu
|
||||
RootedPropertyName name(cx, frame->script()->getName(pc));
|
||||
|
||||
RootedObject scope(cx);
|
||||
if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &scope))
|
||||
if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
|
||||
return false;
|
||||
|
||||
res.setObject(*scope);
|
||||
|
@ -4166,7 +4166,7 @@ BindNameIC::update(JSContext *cx, size_t cacheIndex, HandleObject scopeChain)
|
||||
if (scopeChain->is<GlobalObject>()) {
|
||||
holder = scopeChain;
|
||||
} else {
|
||||
if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &holder))
|
||||
if (!LookupNameUnqualified(cx, name, scopeChain, &holder))
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
|
@ -186,7 +186,7 @@ DefVarOrConst(JSContext *cx, HandlePropertyName dn, unsigned attrs, HandleObject
|
||||
{
|
||||
// Given the ScopeChain, extract the VarObj.
|
||||
RootedObject obj(cx, scopeChain);
|
||||
while (!obj->isVarObj())
|
||||
while (!obj->isQualifiedVarObj())
|
||||
obj = obj->enclosingScope();
|
||||
|
||||
return DefVarOrConstOperation(cx, obj, dn, attrs);
|
||||
@ -197,7 +197,7 @@ SetConst(JSContext *cx, HandlePropertyName name, HandleObject scopeChain, Handle
|
||||
{
|
||||
// Given the ScopeChain, extract the VarObj.
|
||||
RootedObject obj(cx, scopeChain);
|
||||
while (!obj->isVarObj())
|
||||
while (!obj->isQualifiedVarObj())
|
||||
obj = obj->enclosingScope();
|
||||
|
||||
return SetConstOperation(cx, obj, name, rval);
|
||||
|
@ -2331,6 +2331,11 @@ CheckDefineProperty(JSContext *cx, JS::HandleObject obj, JS::HandleId id, JS::Ha
|
||||
JS_FRIEND_API(void)
|
||||
ReportErrorWithId(JSContext *cx, const char *msg, JS::HandleId id);
|
||||
|
||||
// This function is for one specific use case, please don't use this for anything else!
|
||||
extern JS_FRIEND_API(bool)
|
||||
ExecuteInGlobalAndReturnScope(JSContext *cx, JS::HandleObject obj, JS::HandleScript script,
|
||||
JS::MutableHandleObject scope);
|
||||
|
||||
} /* namespace js */
|
||||
|
||||
extern JS_FRIEND_API(bool)
|
||||
|
@ -4379,6 +4379,27 @@ js::LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleOb
|
||||
return true;
|
||||
}
|
||||
|
||||
bool
|
||||
js::LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
|
||||
MutableHandleObject objp)
|
||||
{
|
||||
RootedId id(cx, NameToId(name));
|
||||
|
||||
RootedObject pobj(cx);
|
||||
RootedShape prop(cx);
|
||||
|
||||
RootedObject scope(cx, scopeChain);
|
||||
for (; !scope->isUnqualifiedVarObj(); scope = scope->enclosingScope()) {
|
||||
if (!JSObject::lookupGeneric(cx, scope, id, &pobj, &prop))
|
||||
return false;
|
||||
if (prop)
|
||||
break;
|
||||
}
|
||||
|
||||
objp.set(scope);
|
||||
return true;
|
||||
}
|
||||
|
||||
template <AllowGC allowGC>
|
||||
bool
|
||||
js::HasOwnProperty(JSContext *cx, LookupGenericOp lookup,
|
||||
@ -5033,7 +5054,7 @@ baseops::SetPropertyHelper(typename ExecutionModeTraits<mode>::ContextType cxArg
|
||||
/* We should never add properties to lexical blocks. */
|
||||
JS_ASSERT(!obj->is<BlockObject>());
|
||||
|
||||
if (obj->is<GlobalObject>() && !qualified) {
|
||||
if (obj->isUnqualifiedVarObj() && !qualified) {
|
||||
if (mode == ParallelExecution)
|
||||
return false;
|
||||
|
||||
@ -5838,7 +5859,8 @@ JSObject::dump()
|
||||
if (!obj->is<ProxyObject>() && !obj->nonProxyIsExtensible()) fprintf(stderr, " not_extensible");
|
||||
if (obj->isIndexed()) fprintf(stderr, " indexed");
|
||||
if (obj->isBoundFunction()) fprintf(stderr, " bound_function");
|
||||
if (obj->isVarObj()) fprintf(stderr, " varobj");
|
||||
if (obj->isQualifiedVarObj()) fprintf(stderr, " varobj");
|
||||
if (obj->isUnqualifiedVarObj()) fprintf(stderr, " unqualified_varobj");
|
||||
if (obj->watched()) fprintf(stderr, " watched");
|
||||
if (obj->isIteratedSingleton()) fprintf(stderr, " iterated_singleton");
|
||||
if (obj->isNewTypeUnknown()) fprintf(stderr, " new_type_unknown");
|
||||
|
@ -282,9 +282,14 @@ class JSObject : public js::ObjectImpl
|
||||
}
|
||||
|
||||
/* See InterpreterFrame::varObj. */
|
||||
inline bool isVarObj();
|
||||
bool setVarObj(js::ExclusiveContext *cx) {
|
||||
return setFlag(cx, js::BaseShape::VAROBJ);
|
||||
inline bool isQualifiedVarObj();
|
||||
bool setQualifiedVarObj(js::ExclusiveContext *cx) {
|
||||
return setFlag(cx, js::BaseShape::QUALIFIED_VAROBJ);
|
||||
}
|
||||
|
||||
inline bool isUnqualifiedVarObj();
|
||||
bool setUnqualifiedVarObj(js::ExclusiveContext *cx) {
|
||||
return setFlag(cx, js::BaseShape::UNQUALIFIED_VAROBJ);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1404,7 +1409,7 @@ LookupNameNoGC(JSContext *cx, PropertyName *name, JSObject *scopeChain,
|
||||
|
||||
/*
|
||||
* Like LookupName except returns the global object if 'name' is not found in
|
||||
* any preceding non-global scope.
|
||||
* any preceding scope.
|
||||
*
|
||||
* Additionally, pobjp and propp are not needed by callers so they are not
|
||||
* returned.
|
||||
@ -1413,6 +1418,17 @@ extern bool
|
||||
LookupNameWithGlobalDefault(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
|
||||
MutableHandleObject objp);
|
||||
|
||||
/*
|
||||
* Like LookupName except returns the unqualified var object if 'name' is not found in
|
||||
* any preceding scope. Normally the unqualified var object is the global.
|
||||
*
|
||||
* Additionally, pobjp and propp are not needed by callers so they are not
|
||||
* returned.
|
||||
*/
|
||||
extern bool
|
||||
LookupNameUnqualified(JSContext *cx, HandlePropertyName name, HandleObject scopeChain,
|
||||
MutableHandleObject objp);
|
||||
|
||||
}
|
||||
|
||||
extern JSObject *
|
||||
|
@ -498,11 +498,19 @@ JSObject::setProto(JSContext *cx, JS::HandleObject obj, JS::HandleObject proto,
|
||||
}
|
||||
|
||||
inline bool
|
||||
JSObject::isVarObj()
|
||||
JSObject::isQualifiedVarObj()
|
||||
{
|
||||
if (is<js::DebugScopeObject>())
|
||||
return as<js::DebugScopeObject>().scope().isVarObj();
|
||||
return lastProperty()->hasObjectFlag(js::BaseShape::VAROBJ);
|
||||
return as<js::DebugScopeObject>().scope().isQualifiedVarObj();
|
||||
return lastProperty()->hasObjectFlag(js::BaseShape::QUALIFIED_VAROBJ);
|
||||
}
|
||||
|
||||
inline bool
|
||||
JSObject::isUnqualifiedVarObj()
|
||||
{
|
||||
if (is<js::DebugScopeObject>())
|
||||
return as<js::DebugScopeObject>().scope().isUnqualifiedVarObj();
|
||||
return lastProperty()->hasObjectFlag(js::BaseShape::UNQUALIFIED_VAROBJ);
|
||||
}
|
||||
|
||||
/* static */ inline JSObject *
|
||||
|
@ -118,7 +118,7 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle
|
||||
// Start with the empty shape and then append one shape per aliased binding.
|
||||
RootedShape shape(cx,
|
||||
EmptyShape::getInitialShape(cx, &CallObject::class_, TaggedProto(nullptr), nullptr, nullptr,
|
||||
nfixed, BaseShape::VAROBJ | BaseShape::DELEGATE));
|
||||
nfixed, BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE));
|
||||
if (!shape)
|
||||
return false;
|
||||
|
||||
@ -141,7 +141,7 @@ Bindings::initWithTemporaryStorage(ExclusiveContext *cx, InternalBindingsHandle
|
||||
#endif
|
||||
|
||||
StackBaseShape stackBase(cx, &CallObject::class_, nullptr, nullptr,
|
||||
BaseShape::VAROBJ | BaseShape::DELEGATE);
|
||||
BaseShape::QUALIFIED_VAROBJ | BaseShape::DELEGATE);
|
||||
|
||||
UnownedBaseShape *base = BaseShape::getUnowned(cx, stackBase);
|
||||
if (!base)
|
||||
|
@ -235,7 +235,9 @@ GlobalObject::create(JSContext *cx, const Class *clasp)
|
||||
|
||||
cx->compartment()->initGlobal(*global);
|
||||
|
||||
if (!global->setVarObj(cx))
|
||||
if (!global->setQualifiedVarObj(cx))
|
||||
return nullptr;
|
||||
if (!global->setUnqualifiedVarObj(cx))
|
||||
return nullptr;
|
||||
if (!global->setDelegate(cx))
|
||||
return nullptr;
|
||||
|
@ -241,7 +241,7 @@ SetNameOperation(JSContext *cx, JSScript *script, jsbytecode *pc, HandleObject s
|
||||
* undeclared global variable. To do this, we call SetPropertyHelper
|
||||
* directly and pass Unqualified.
|
||||
*/
|
||||
if (scope->is<GlobalObject>()) {
|
||||
if (scope->isUnqualifiedVarObj()) {
|
||||
JS_ASSERT(!scope->getOps()->setProperty);
|
||||
RootedId id(cx, NameToId(name));
|
||||
return baseops::SetPropertyHelper<SequentialExecution>(cx, scope, scope, id,
|
||||
@ -255,7 +255,7 @@ SetNameOperation(JSContext *cx, JSScript *script, jsbytecode *pc, HandleObject s
|
||||
inline bool
|
||||
DefVarOrConstOperation(JSContext *cx, HandleObject varobj, HandlePropertyName dn, unsigned attrs)
|
||||
{
|
||||
JS_ASSERT(varobj->isVarObj());
|
||||
JS_ASSERT(varobj->isQualifiedVarObj());
|
||||
|
||||
RootedShape prop(cx);
|
||||
RootedObject obj2(cx);
|
||||
|
@ -639,7 +639,7 @@ js::Execute(JSContext *cx, HandleScript script, JSObject &scopeChainArg, Value *
|
||||
|
||||
/* The VAROBJFIX option makes varObj == globalObj in global code. */
|
||||
if (!cx->runtime()->options().varObjFix()) {
|
||||
if (!scopeChain->setVarObj(cx))
|
||||
if (!scopeChain->setQualifiedVarObj(cx))
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -2024,7 +2024,7 @@ CASE(JSOP_BINDNAME)
|
||||
|
||||
/* Assigning to an undeclared name adds a property to the global object. */
|
||||
RootedObject &scope = rootObject1;
|
||||
if (!LookupNameWithGlobalDefault(cx, name, scopeChain, &scope))
|
||||
if (!LookupNameUnqualified(cx, name, scopeChain, &scope))
|
||||
goto error;
|
||||
|
||||
PUSH_OBJECT(*scope);
|
||||
@ -3577,7 +3577,7 @@ js::DefFunOperation(JSContext *cx, HandleScript script, HandleObject scopeChain,
|
||||
* and functions defined by eval inside let or with blocks.
|
||||
*/
|
||||
RootedObject parent(cx, scopeChain);
|
||||
while (!parent->isVarObj())
|
||||
while (!parent->isQualifiedVarObj())
|
||||
parent = parent->enclosingScope();
|
||||
|
||||
/* ES5 10.5 (NB: with subsequent errata). */
|
||||
|
@ -308,14 +308,24 @@ class BaseShape : public gc::BarrieredCell<BaseShape>
|
||||
NOT_EXTENSIBLE = 0x10,
|
||||
INDEXED = 0x20,
|
||||
BOUND_FUNCTION = 0x40,
|
||||
VAROBJ = 0x80,
|
||||
HAD_ELEMENTS_ACCESS = 0x80,
|
||||
WATCHED = 0x100,
|
||||
ITERATED_SINGLETON = 0x200,
|
||||
NEW_TYPE_UNKNOWN = 0x400,
|
||||
UNCACHEABLE_PROTO = 0x800,
|
||||
HAD_ELEMENTS_ACCESS = 0x1000,
|
||||
|
||||
OBJECT_FLAG_MASK = 0x1ff8
|
||||
// These two flags control which scope a new variables ends up on in the
|
||||
// scope chain. If the variable is "qualified" (i.e., if it was defined
|
||||
// using var, let, or const) then it ends up on the lowest scope in the
|
||||
// chain that has the QUALIFIED_VAROBJ flag set. If it's "unqualified"
|
||||
// (i.e., if it was introduced without any var, let, or const, which
|
||||
// incidentally is an error in strict mode) then it goes on the lowest
|
||||
// scope in the chain with the UNQUALIFIED_VAROBJ flag set (which is
|
||||
// typically the global).
|
||||
QUALIFIED_VAROBJ = 0x1000,
|
||||
UNQUALIFIED_VAROBJ = 0x2000,
|
||||
|
||||
OBJECT_FLAG_MASK = 0x3ff8
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -58,7 +58,7 @@ inline JSObject &
|
||||
InterpreterFrame::varObj()
|
||||
{
|
||||
JSObject *obj = scopeChain();
|
||||
while (!obj->isVarObj())
|
||||
while (!obj->isQualifiedVarObj())
|
||||
obj = obj->enclosingScope();
|
||||
return *obj;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user