Bug 673569 - Allow scripts to be run in a non-global scope (r=luke)

This commit is contained in:
Bill McCloskey 2014-07-16 23:03:44 -07:00
parent 303f193952
commit dc364b8178
16 changed files with 198 additions and 25 deletions

View File

@ -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;
}

View File

@ -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
};

View 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);

View File

@ -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);

View File

@ -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;
}

View File

@ -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);

View File

@ -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)

View File

@ -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");

View File

@ -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 *

View File

@ -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 *

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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). */

View File

@ -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:

View File

@ -58,7 +58,7 @@ inline JSObject &
InterpreterFrame::varObj()
{
JSObject *obj = scopeChain();
while (!obj->isVarObj())
while (!obj->isQualifiedVarObj())
obj = obj->enclosingScope();
return *obj;
}