Backed out changesets b61921a307e9 and e9e74f6bd12a (bug 1054759) for breaking web compat by implementing Symbol.unscopables without Array.prototype[@@unscopables].

This commit is contained in:
Shu-yu Guo 2016-03-19 19:18:12 -07:00
Родитель 04e76674fe
Коммит b4a736d2a2
21 изменённых файлов: 15 добавлений и 478 удалений

Просмотреть файл

@ -1,37 +0,0 @@
// An Environment for a `with` statement does not observe bindings ruled out by @@unscopables.
load(libdir + "asserts.js");
let g = newGlobal();
g.eval(`
let x = 'global';
function f() {
let obj = {
x: 'obj',
y: 'obj',
[Symbol.unscopables]: {x: 1},
};
with (obj)
debugger;
}
`);
let dbg = Debugger(g);
let hits = 0;
dbg.onDebuggerStatement = function (frame) {
let env = frame.environment;
assertEq(env.find("x") !== env, true);
assertEq(env.names().indexOf("x"), -1);
assertEq(env.getVariable("x"), undefined);
assertThrowsInstanceOf(() => env.setVariable("x", 7), TypeError);
assertEq(env.find("y") === env, true);
assertEq(env.getVariable("y"), "obj");
env.setVariable("y", 8);
assertEq(frame.eval("x").return, "global");
assertEq(frame.eval("y").return, 8);
hits++;
};
g.f();
assertEq(hits, 1);

Просмотреть файл

@ -4715,25 +4715,17 @@ JS_PUBLIC_API(JSString*)
GetSymbolDescription(HandleSymbol symbol);
/* Well-known symbols. */
#define JS_FOR_EACH_WELL_KNOWN_SYMBOL(macro) \
macro(iterator) \
macro(match) \
macro(species) \
macro(toPrimitive) \
macro(unscopables)
enum class SymbolCode : uint32_t {
// There is one SymbolCode for each well-known symbol.
#define JS_DEFINE_SYMBOL_ENUM(name) name,
JS_FOR_EACH_WELL_KNOWN_SYMBOL(JS_DEFINE_SYMBOL_ENUM) // SymbolCode::iterator, etc.
#undef JS_DEFINE_SYMBOL_ENUM
Limit,
iterator, // well-known symbols
match,
species,
toPrimitive,
InSymbolRegistry = 0xfffffffe, // created by Symbol.for() or JS::GetSymbolFor()
UniqueSymbol = 0xffffffff // created by Symbol() or JS::NewSymbol()
};
/* For use in loops that iterate over the well-known symbols. */
const size_t WellKnownSymbolLimit = size_t(SymbolCode::Limit);
const size_t WellKnownSymbolLimit = 4;
/**
* Return the SymbolCode telling what sort of symbol `symbol` is.

Просмотреть файл

@ -1,22 +0,0 @@
// Basics of @@unscopables support.
// In with(obj), if obj[@@unscopables][id] is truthy, then the identifier id
// is not present as a binding in the with-block's scope.
var x = "global";
with ({x: "with", [Symbol.unscopables]: {x: true}})
assertEq(x, "global");
// But if obj[@@unscopables][id] is false or not present, there is a binding.
with ({y: "with", z: "with", [Symbol.unscopables]: {y: false}}) {
assertEq(y, "with");
assertEq(z, "with");
}
// ToBoolean(obj[@@unscopables][id]) determines whether there's a binding.
let someValues = [0, -0, NaN, "", undefined, null, "x", {}, []];
for (let v of someValues) {
with ({x: "with", [Symbol.unscopables]: {x: v}})
assertEq(x, v ? "global" : "with");
}
reportCompare(0, 0);

Просмотреть файл

@ -1,23 +0,0 @@
// @@unscopables continues to work after exiting the relevant `with` block,
// if the environment is captured by a closure.
let env = {
x: 9000,
[Symbol.unscopables]: {x: true}
};
function make_adder(x) {
with (env)
return function (y) { return x + y; };
}
assertEq(make_adder(3)(10), 13);
// Same test, but with a bunch of different parts for bad luck
let x = 500;
function make_adder_with_eval() {
with (env)
return eval('y => eval("x + y")');
}
assertEq(make_adder_with_eval()(10), 510);
reportCompare(0, 0);

Просмотреть файл

@ -1,8 +0,0 @@
// @@unscopables prevents a property from having any effect on assigning to a
// const binding (which is an error).
const x = 1;
with ({x: 1, [Symbol.unscopables]: {x: true}})
assertThrowsInstanceOf(() => {x = 2;}, TypeError);
reportCompare(0, 0);

Просмотреть файл

@ -1,27 +0,0 @@
// If obj[@@unscopables][id], then `delete id` works across `with (obj)` scope.
this.niche = 7;
let obj = { niche: 8, [Symbol.unscopables]: { niche: true } };
with (obj) {
delete niche;
}
assertEq(obj.niche, 8);
assertEq("niche" in this, false);
// Same thing, but delete a variable introduced by sloppy direct eval.
this.niche = 9;
function f() {
eval("var niche = 10;");
with (obj) {
assertEq(niche, 10);
delete niche;
}
assertEq(niche, 9);
}
// Of course none of this affects a qualified delete.
assertEq(delete this.niche, true);
assertEq("niche" in this, false);
reportCompare(0, 0);

Просмотреть файл

@ -1,41 +0,0 @@
// @@unscopables checks can call getters.
// The @@unscopables property itself can be a getter.
let hit1 = 0;
let x = "global x";
let env1 = {
x: "env1.x",
get [Symbol.unscopables]() {
hit1++;
return {x: true};
}
};
with (env1)
assertEq(x, "global x");
assertEq(hit1, 1);
// It can throw; the exception is propagated out.
function Fit() {}
with ({x: 0, get [Symbol.unscopables]() { throw new Fit; }})
assertThrowsInstanceOf(() => x, Fit);
// Individual properties on the @@unscopables object can have getters.
let hit2 = 0;
let env2 = {
x: "env2.x",
[Symbol.unscopables]: {
get x() {
hit2++;
return true;
}
}
};
with (env2)
assertEq(x, "global x");
assertEq(hit2, 1);
// And they can throw.
with ({x: 0, [Symbol.unscopables]: {get x() { throw new Fit; }}})
assertThrowsInstanceOf(() => x, Fit);
reportCompare(0, 0);

Просмотреть файл

@ -1,18 +0,0 @@
// @@unscopables does not affect the global environment.
this.x = "global property x";
let y = "global lexical y";
this[Symbol.unscopables] = {x: true, y: true};
assertEq(x, "global property x");
assertEq(y, "global lexical y");
assertEq(eval("x"), "global property x");
assertEq(eval("y"), "global lexical y");
// But it does affect `with` statements targeting the global object.
{
let x = "local x";
with (this)
assertEq(x, "local x");
}
reportCompare(0, 0);

Просмотреть файл

@ -1,22 +0,0 @@
// In these cases, @@unscopables should not be consulted.
// Because obj has no properties `assertEq` or `x`,
// obj[@@unscopables] is not checked here:
var obj = {
get [Symbol.unscopables]() {
throw "tried to read @@unscopables";
}
};
var x = 3;
with (obj)
assertEq(x, 3);
// If @@unscopables is present but not an object, it is ignored:
for (let nonObject of [undefined, null, "nothing", Symbol.for("moon")]) {
let y = 4;
let obj2 = {[Symbol.unscopables]: nonObject, y: 5};
with (obj2)
assertEq(y, 5);
}
reportCompare(0, 0);

Просмотреть файл

@ -1,7 +0,0 @@
// Trying to access a binding that doesn't exist due to @@unscopables
// is a ReferenceError.
with ({x: 1, [Symbol.unscopables]: {x: true}})
assertThrowsInstanceOf(() => x, ReferenceError);
reportCompare(0, 0);

Просмотреть файл

@ -1,18 +0,0 @@
// When env[@@unscopables].x changes, bindings can appear even if env is inextensible.
let x = "global";
let unscopables = {x: true};
let env = Object.create(null);
env[Symbol.unscopables] = unscopables;
env.x = "object";
Object.freeze(env);
for (let i = 0; i < 1004; i++) {
if (i === 1000)
unscopables.x = false;
with (env) {
assertEq(x, i < 1000 ? "global" : "object");
}
}
reportCompare(0, 0);

Просмотреть файл

@ -1,44 +0,0 @@
// When obj[@@unscopables].x changes, bindings appear and disappear accordingly.
let x = "global";
function getX() { return x; }
let unscopables = {x: true};
let obj = {x: "obj", [Symbol.unscopables]: unscopables};
with (obj) {
assertEq(x, "global");
x = "global-1";
assertEq(x, "global-1");
assertEq(obj.x, "obj");
unscopables.x = false; // suddenly x appears in the with-environment
assertEq(x, "obj");
x = "obj-1";
assertEq(getX(), "global-1"); // unchanged
assertEq(obj.x, "obj-1");
unscopables.x = true; // *poof*
assertEq(x, "global-1");
x = "global-2";
assertEq(getX(), "global-2");
assertEq(obj.x, "obj-1"); // unchanged
// The determination of which binding is assigned happens when the LHS of
// assignment is evaluated, before the RHS. This is observable if we make
// the binding appear or disappear during evaluation of the RHS, before
// assigning.
x = (unscopables.x = false, "global-3");
assertEq(getX(), "global-3");
assertEq(obj.x, "obj-1");
x = (unscopables.x = true, "obj-2");
assertEq(getX(), "global-3");
assertEq(obj.x, "obj-2");
}
assertEq(x, "global-3");
reportCompare(0, 0);

Просмотреть файл

@ -1,39 +0,0 @@
// @@unscopables treats properties found on prototype chains the same as other
// properties.
const x = "global x";
const y = "global y";
// obj[@@unscopables].x works when obj.x is inherited via the prototype chain.
let proto = {x: "object x", y: "object y"};
let env = Object.create(proto);
env[Symbol.unscopables] = {x: true, y: false};
with (env) {
assertEq(x, "global x");
assertEq(delete x, false);
assertEq(y, "object y");
}
assertEq(env.x, "object x");
// @@unscopables works if is inherited via the prototype chain.
env = {
x: "object",
[Symbol.unscopables]: {x: true, y: true}
};
for (let i = 0; i < 50; i++)
env = Object.create(env);
env.y = 1;
with (env) {
assertEq(x, "global x");
assertEq(y, "global y");
}
// @@unscopables works if the obj[@@unscopables][id] property is inherited.
env = {
x: "object",
[Symbol.unscopables]: Object.create({x: true})
};
with (env)
assertEq(x, "global x");
reportCompare(0, 0);

Просмотреть файл

@ -1,46 +0,0 @@
// Object operations are performed in the right order, as observed by proxies.
let log = [];
function LoggingProxyHandlerWrapper(name, handler={}) {
return new Proxy(handler, {
get(t, id) {
let method = handler[id];
return function (...args) {
log.push([name + "." + id, ...args.filter(v => typeof v !== "object")]);
if (method === undefined)
return Reflect[id].apply(null, args);
return method.apply(this, args);
};
}
});
}
function LoggingProxy(name, target) {
return new Proxy(target, new LoggingProxyHandlerWrapper(name));
}
let proto = {x: 44};
let proto_proxy = new LoggingProxy("proto", proto);
let unscopables = {x: true};
let unscopables_proxy = new LoggingProxy("unscopables", {x: true});
let env = Object.create(proto_proxy, {
[Symbol.unscopables]: { value: unscopables_proxy }
});
let env_proxy = new LoggingProxy("env", env);
let x = 11;
function f() {
with (env_proxy)
return x;
}
assertEq(f(), 11);
assertDeepEq(log, [
["env.has", "x"],
["proto.has", "x"],
["env.get", Symbol.unscopables],
["unscopables.get", "x"]
]);
reportCompare(0, 0);

Просмотреть файл

@ -1,32 +0,0 @@
// Strict assignment to the name of a property that's masked by @@unscopables
// throws a ReferenceError.
let env = {k: 1};
let f;
with (env) {
f = function () {
"use strict";
k = 2;
};
}
f();
assertEq(env.k, 2);
env[Symbol.unscopables] = {k: true};
assertThrowsInstanceOf(f, ReferenceError);
// @@unscopables is tested when the LHS of assignment is evaluated, so there is
// no effect on the assignment if it is changed while evaluating the RHS.
let g;
with (env) {
g = function () {
"use strict";
k = (env[Symbol.unscopables].k = true, 3);
}
}
env[Symbol.unscopables].k = false;
g();
assertEq(env.k, 3);
reportCompare(0, 0);

Просмотреть файл

@ -1,9 +0,0 @@
// Accessing an uninitialized variable due to @@unscopables is still a ReferenceError.
with ({x: 1, [Symbol.unscopables]: {x: true}})
assertThrowsInstanceOf(() => x, ReferenceError);
let x;
reportCompare(0, 0);

Просмотреть файл

@ -5,8 +5,6 @@ var names = [
"iterator",
"match",
"species",
"toPrimitive",
"unscopables"
];
for (var name of names) {

Просмотреть файл

@ -293,7 +293,6 @@
macro(match, match, "match") \
macro(species, species, "species") \
macro(toPrimitive, toPrimitive, "toPrimitive") \
macro(unscopables, unscopables, "unscopables") \
/* Same goes for the descriptions of the well-known symbols. */ \
macro(Symbol_hasInstance, Symbol_hasInstance, "Symbol.hasInstance") \
macro(Symbol_isConcatSpreadable, Symbol_isConcatSpreadable, "Symbol.isConcatSpreadable") \
@ -301,6 +300,7 @@
macro(Symbol_match, Symbol_match, "Symbol.match") \
macro(Symbol_species, Symbol_species, "Symbol.species") \
macro(Symbol_toPrimitive, Symbol_toPrimitive, "Symbol.toPrimitive") \
macro(Symbol_toStringTag, Symbol_toStringTag, "Symbol.toStringTag") \
macro(Symbol_unscopables, Symbol_unscopables, "Symbol.unscopables") \
/* Function names for properties named by symbols. */ \
macro(Symbol_iterator_fun, Symbol_iterator_fun, "[Symbol.iterator]") \

Просмотреть файл

@ -8866,14 +8866,6 @@ DebuggerEnv_getVariable(JSContext* cx, unsigned argc, Value* vp)
/* This can trigger getters. */
ErrorCopier ec(ac);
bool found;
if (!HasProperty(cx, env, id, &found))
return false;
if (!found) {
args.rval().setUndefined();
return true;
}
// For DebugScopeObjects, we get sentinel values for optimized out
// slots and arguments instead of throwing (the default behavior).
//

Просмотреть файл

@ -447,9 +447,10 @@ namespace js {
*/
struct WellKnownSymbols
{
#define DECLARE_SYMBOL(name) js::ImmutableSymbolPtr name;
JS_FOR_EACH_WELL_KNOWN_SYMBOL(DECLARE_SYMBOL)
#undef DECLARE_SYMBOL
js::ImmutableSymbolPtr iterator;
js::ImmutableSymbolPtr match;
js::ImmutableSymbolPtr species;
js::ImmutableSymbolPtr toPrimitive;
const ImmutableSymbolPtr& get(size_t u) const {
MOZ_ASSERT(u < JS::WellKnownSymbolLimit);

Просмотреть файл

@ -755,26 +755,6 @@ DynamicWithObject::create(JSContext* cx, HandleObject object, HandleObject enclo
return obj;
}
/* Implements ES6 8.1.1.2.1 HasBinding steps 7-9. */
static bool
CheckUnscopables(JSContext *cx, HandleObject obj, HandleId id, bool *scopable)
{
RootedId unscopablesId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols()
.get(JS::SymbolCode::unscopables)));
RootedValue v(cx);
if (!GetProperty(cx, obj, obj, unscopablesId, &v))
return false;
if (v.isObject()) {
RootedObject unscopablesObj(cx, &v.toObject());
if (!GetProperty(cx, unscopablesObj, unscopablesObj, id, &v))
return false;
*scopable = !ToBoolean(v);
} else {
*scopable = true;
}
return true;
}
static bool
with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
MutableHandleObject objp, MutableHandleShape propp)
@ -785,19 +765,7 @@ with_LookupProperty(JSContext* cx, HandleObject obj, HandleId id,
return true;
}
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
if (!LookupProperty(cx, actual, id, objp, propp))
return false;
if (propp) {
bool scopable;
if (!CheckUnscopables(cx, actual, id, &scopable))
return false;
if (!scopable) {
objp.set(nullptr);
propp.set(nullptr);
}
}
return true;
return LookupProperty(cx, actual, id, objp, propp);
}
static bool
@ -814,15 +782,7 @@ with_HasProperty(JSContext* cx, HandleObject obj, HandleId id, bool* foundp)
{
MOZ_ASSERT(!JSID_IS_ATOM(id, cx->names().dotThis));
RootedObject actual(cx, &obj->as<DynamicWithObject>().object());
// ES 8.1.1.2.1 step 3-5.
if (!HasProperty(cx, actual, id, foundp))
return false;
if (!*foundp)
return true;
// Steps 7-10. (Step 6 is a no-op.)
return CheckUnscopables(cx, actual, id, foundp);
return HasProperty(cx, actual, id, foundp);
}
static bool
@ -2315,25 +2275,12 @@ class DebugScopeProxy : public BaseProxyHandler
// target object, the object would indicate that native enumeration is
// the thing to do, but native enumeration over the DynamicWithObject
// wrapper yields no properties. So instead here we hack around the
// issue: punch a hole through to the with object target, then manually
// examine @@unscopables.
bool isWith = scope->is<DynamicWithObject>();
Rooted<JSObject*> target(cx, (isWith ? &scope->as<DynamicWithObject>().object() : scope));
// issue, and punch a hole through to the with object target.
Rooted<JSObject*> target(cx, (scope->is<DynamicWithObject>()
? &scope->as<DynamicWithObject>().object() : scope));
if (!GetPropertyKeys(cx, target, JSITER_OWNONLY, &props))
return false;
if (isWith) {
size_t j = 0;
for (size_t i = 0; i < props.length(); i++) {
bool inScope;
if (!CheckUnscopables(cx, scope, props[i], &inScope))
return false;
if (inScope)
props[j++].set(props[i]);
}
props.resize(j);
}
/*
* Function scopes are optimized to not contain unaliased variables so
* they must be manually appended here.