Bug 1082672, part 4 - Change XrayWrapper code to be able to resolve symbol-keyed methods. r=bz, r=bholley.

--HG--
extra : rebase_source : f78cbb83f63dfffd648c6d3c280273f4a61c9fe8
extra : amend_source : f006a096174eee166125430753e65e9a31bd930b
This commit is contained in:
Jason Orendorff 2014-09-18 12:30:38 -05:00
Родитель bf48f46a72
Коммит 8ec9f238d9
6 изменённых файлов: 144 добавлений и 54 удалений

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

@ -1406,8 +1406,11 @@ XrayAttributeOrMethodKeys(JSContext* cx, JS::Handle<JSObject*> wrapper,
// looking at now.
size_t i = list->specs - specList;
for ( ; ids[i] != JSID_VOID; ++i) {
// Skip non-enumerable properties and symbol-keyed properties unless
// they are specially requested via flags.
if (((flags & JSITER_HIDDEN) ||
(specList[i].flags & JSPROP_ENUMERATE)) &&
((flags & JSITER_SYMBOLS) || !JSID_IS_SYMBOL(ids[i])) &&
!props.append(ids[i])) {
return false;
}

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

@ -3244,13 +3244,21 @@ JS_DefineConstIntegers(JSContext *cx, HandleObject obj, const JSConstIntegerSpec
return DefineConstScalar(cx, obj, cis);
}
static JS::SymbolCode
PropertySpecNameToSymbolCode(const char *name)
{
MOZ_ASSERT(JS::PropertySpecNameIsSymbol(name));
uintptr_t u = reinterpret_cast<uintptr_t>(name);
return JS::SymbolCode(u - 1);
}
static bool
PropertySpecNameToId(JSContext *cx, const char *name, MutableHandleId id,
js::InternBehavior ib = js::DoNotInternAtom)
{
if (JS::PropertySpecNameIsSymbol(name)) {
uintptr_t u = reinterpret_cast<uintptr_t>(name);
id.set(SYMBOL_TO_JSID(cx->wellKnownSymbols().get(u - 1)));
JS::SymbolCode which = PropertySpecNameToSymbolCode(name);
id.set(SYMBOL_TO_JSID(cx->wellKnownSymbols().get(which)));
} else {
JSAtom *atom = Atomize(cx, name, strlen(name), ib);
if (!atom)
@ -5516,6 +5524,33 @@ JS::GetWellKnownSymbol(JSContext *cx, JS::SymbolCode which)
return cx->runtime()->wellKnownSymbols->get(uint32_t(which));
}
static bool
PropertySpecNameIsDigits(const char *s) {
if (JS::PropertySpecNameIsSymbol(s))
return false;
if (!*s)
return false;
for (; *s; s++) {
if (*s < '0' || *s > '9')
return false;
}
return true;
}
JS_PUBLIC_API(bool)
JS::PropertySpecNameEqualsId(const char *name, HandleId id)
{
if (JS::PropertySpecNameIsSymbol(name)) {
if (!JSID_IS_SYMBOL(id))
return false;
Symbol *sym = JSID_TO_SYMBOL(id);
return sym->isWellKnownSymbol() && sym->code() == PropertySpecNameToSymbolCode(name);
}
MOZ_ASSERT(!PropertySpecNameIsDigits(name));
return JSID_IS_ATOM(id) && JS_FlatStringEqualsAscii(JSID_TO_ATOM(id), name);
}
JS_PUBLIC_API(bool)
JS_Stringify(JSContext *cx, MutableHandleValue vp, HandleObject replacer,
HandleValue space, JSONWriteCallback callback, void *data)

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

@ -4517,6 +4517,9 @@ PropertySpecNameIsSymbol(const char *name)
return u != 0 && u - 1 < WellKnownSymbolLimit;
}
JS_PUBLIC_API(bool)
PropertySpecNameEqualsId(const char *name, HandleId id);
/*
* Create a jsid that does not need to be marked for GC.
*

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

@ -423,10 +423,14 @@ struct WellKnownSymbols
{
js::ImmutableSymbolPtr iterator;
ImmutableSymbolPtr &get(size_t i) {
MOZ_ASSERT(i < JS::WellKnownSymbolLimit);
ImmutableSymbolPtr &get(size_t u) {
MOZ_ASSERT(u < JS::WellKnownSymbolLimit);
ImmutableSymbolPtr *symbols = reinterpret_cast<ImmutableSymbolPtr *>(this);
return symbols[i];
return symbols[u];
}
ImmutableSymbolPtr &get(JS::SymbolCode code) {
return get(size_t(code));
}
};

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

@ -145,6 +145,7 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
var version = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo).version;
var isNightlyBuild = version.endsWith("a1");
var isReleaseBuild = !version.contains("a");
const jsHasSymbols = typeof Symbol === "function";
var gPrototypeProperties = {};
gPrototypeProperties['Date'] =
["getTime", "getTimezoneOffset", "getYear", "getFullYear", "getUTCFullYear",
@ -197,19 +198,23 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
["constructor", "toSource", "toString", "apply", "call", "bind",
"isGenerator", "length", "name", "arguments", "caller"];
// Sort an array that may contain symbols as well as strings.
function sortProperties(arr) {
function sortKey(prop) {
return typeof prop + ":" + prop.toString();
}
arr.sort((a, b) => sortKey(a) < sortKey(b) ? -1 : +1);
}
// Sort all the lists so we don't need to mutate them later (or copy them
// again to sort them).
for (var c of Object.keys(gPrototypeProperties))
gPrototypeProperties[c].sort();
sortProperties(gPrototypeProperties[c]);
function filterOut(array, props) {
return array.filter(p => props.indexOf(p) == -1);
}
function appendUnique(array, vals) {
filterOut(vals, array).forEach(v => array.push(v));
}
function isTypedArrayClass(classname) {
return typedArrayClasses.indexOf(classname) >= 0;
}
@ -267,6 +272,12 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
gPrototypeProperties[classname].filter(id => typeof id === "string").toSource(),
"A property on the " + classname +
" prototype has changed! You need a security audit from an XPConnect peer");
if (jsHasSymbols) {
is(Object.getOwnPropertySymbols(localProto).map(uneval).sort().toSource(),
gPrototypeProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
"A symbol-keyed property on the " + classname +
" prototype has been changed! You need a security audit from an XPConnect peer");
}
let protoProps = filterOut(desiredProtoProps, propsToSkip);
let protoCallables = protoProps.filter(name => propertyIsGetter(localProto, name, classname) ||
@ -280,6 +291,11 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
testProtoCallables(protoCallables, xray, xrayProto, localProto);
is(Object.getOwnPropertyNames(xrayProto).sort().toSource(),
protoProps.toSource(), "getOwnPropertyNames works");
if (jsHasSymbols) {
is(Object.getOwnPropertySymbols(xrayProto).map(uneval).sort().toSource(),
gPrototypeProperties[classname].filter(id => typeof id !== "string").map(uneval).sort().toSource(),
protoProps.toSource(), "getOwnPropertySymbols works");
}
is(xrayProto.constructor, iwin[classname], "constructor property works");
@ -303,26 +319,35 @@ https://bugzilla.mozilla.org/show_bug.cgi?id=933681
is(d.toLocaleString('de-DE'), d.wrappedJSObject.toLocaleString('de-DE'), "Results match");
}
var uniqueSymbol;
function testObject() {
testXray('Object', Cu.unwaiveXrays(Cu.waiveXrays(iwin).Object.create(new iwin.Object())),
new iwin.Object(), []);
// Construct an object full of tricky things.
let symbolProps = '';
if (jsHasSymbols) {
uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol');
symbolProps = `, [uniqueSymbol]: 43,
[Symbol.for("registrySymbolProp")]: 44`;
}
var trickyObject =
iwin.eval('new Object({ \
primitiveProp: 42, objectProp: { foo: 2 }, \
xoProp: top.location, hasOwnProperty: 10, \
get getterProp() { return 2; }, \
set setterProp(x) { }, \
get getterSetterProp() { return 3; }, \
set getterSetterProp(x) { }, \
callableProp: function() { }, \
nonXrayableProp: new WeakMap() })');
iwin.eval(`new Object({
primitiveProp: 42, objectProp: { foo: 2 },
xoProp: top.location, hasOwnProperty: 10,
get getterProp() { return 2; },
set setterProp(x) { },
get getterSetterProp() { return 3; },
set getterSetterProp(x) { },
callableProp: function() { },
nonXrayableProp: new WeakMap()
${symbolProps}
})`);
testTrickyObject(trickyObject);
}
function testArray() {
function testArray() {
// The |length| property is generally very weird, especially with respect
// to its behavior on the prototype. Array.prototype is actually an Array
// instance, and therefore has a vestigial .length. But we don't want to
@ -332,18 +357,25 @@ function testArray() {
let propsToSkip = ['length'];
testXray('Array', new iwin.Array(20), new iwin.Array(), propsToSkip);
let symbolProps = '';
if (jsHasSymbols) {
uniqueSymbol = iwin.eval('var uniqueSymbol = Symbol("uniqueSymbol"); uniqueSymbol');
symbolProps = `trickyArray[uniqueSymbol] = 43;
trickyArray[Symbol.for("registrySymbolProp")] = 44;`;
}
var trickyArray =
iwin.eval("var trickyArray = []; \
trickyArray.primitiveProp = 42; \
trickyArray.objectProp = { foo: 2 }; \
trickyArray.xoProp = top.location; \
trickyArray.hasOwnProperty = 10; \
Object.defineProperty(trickyArray, 'getterProp', { get: function() { return 2; }}); \
Object.defineProperty(trickyArray, 'setterProp', { set: function(x) {}}); \
Object.defineProperty(trickyArray, 'getterSetterProp', { get: function() { return 3; }, set: function(x) {}}); \
trickyArray.callableProp = function() {}; \
trickyArray.nonXrayableProp = new WeakMap(); \
trickyArray;");
iwin.eval(`var trickyArray = [];
trickyArray.primitiveProp = 42;
trickyArray.objectProp = { foo: 2 };
trickyArray.xoProp = top.location;
trickyArray.hasOwnProperty = 10;
Object.defineProperty(trickyArray, 'getterProp', { get: function() { return 2; }});
Object.defineProperty(trickyArray, 'setterProp', { set: function(x) {}});
Object.defineProperty(trickyArray, 'getterSetterProp', { get: function() { return 3; }, set: function(x) {}});
trickyArray.callableProp = function() {};
trickyArray.nonXrayableProp = new WeakMap();
${symbolProps}
trickyArray;`);
// Test indexed access.
trickyArray.wrappedJSObject[9] = "some indexed property";
@ -366,11 +398,11 @@ function testArray() {
is(trickyArray[1], undefined, "Frozen length forbids new properties");
testTrickyObject(trickyArray);
}
}
// Parts of this function are kind of specific to testing Object, but we factor
// it out so that we can re-use the trickyObject stuff on Arrays.
function testTrickyObject(trickyObject) {
// Parts of this function are kind of specific to testing Object, but we factor
// it out so that we can re-use the trickyObject stuff on Arrays.
function testTrickyObject(trickyObject) {
// Make sure it looks right under the hood.
is(trickyObject.wrappedJSObject.getterProp, 2, "Underlying object has getter");
@ -382,11 +414,21 @@ function testTrickyObject(trickyObject) {
expectedNames.push('length');
is(Object.getOwnPropertyNames(trickyObject).sort().toSource(),
expectedNames.sort().toSource(), "getOwnPropertyNames should be filtered correctly");
if (jsHasSymbols) {
var expectedSymbols = [Symbol.for("registrySymbolProp"), uniqueSymbol];
is(Object.getOwnPropertySymbols(trickyObject).map(uneval).sort().toSource(),
expectedSymbols.map(uneval).sort().toSource(),
"getOwnPropertySymbols should be filtered correctly");
}
// Test that cloning uses the Xray view.
var cloned = Cu.cloneInto(trickyObject, this);
is(Object.getOwnPropertyNames(cloned).sort().toSource(),
expectedNames.sort().toSource(), "structured clone should use the Xray view");
if (jsHasSymbols) {
is(Object.getOwnPropertySymbols(cloned).map(uneval).sort().toSource(),
"[]", "structured cloning doesn't clone symbol-keyed properties yet");
}
// Test iteration and in-place modification. Beware of 'expando', which is the property
// we placed on the xray proto.

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

@ -458,21 +458,14 @@ JSXrayTraits::resolveOwnProperty(JSContext *cx, const Wrapper &jsWrapper,
return JS_IdToValue(cx, className, desc.value());
}
// Compute the property name we're looking for. Indexed array properties
// are handled above. We'll handle well-known symbols when we start
// supporting Symbol.iterator in bug 918828.
if (!JSID_IS_STRING(id))
return true;
Rooted<JSFlatString*> str(cx, JSID_TO_FLAT_STRING(id));
// Grab the JSClass. We require all Xrayable classes to have a ClassSpec.
const js::Class *clasp = js::GetObjectClass(target);
MOZ_ASSERT(clasp->spec.defined());
// Scan through the functions.
// Scan through the functions. Indexed array properties are handled above.
const JSFunctionSpec *fsMatch = nullptr;
for (const JSFunctionSpec *fs = clasp->spec.prototypeFunctions; fs && fs->name; ++fs) {
if (JS_FlatStringEqualsAscii(str, fs->name)) {
if (PropertySpecNameEqualsId(fs->name, id)) {
fsMatch = fs;
break;
}
@ -501,7 +494,7 @@ JSXrayTraits::resolveOwnProperty(JSContext *cx, const Wrapper &jsWrapper,
// Scan through the properties.
const JSPropertySpec *psMatch = nullptr;
for (const JSPropertySpec *ps = clasp->spec.prototypeProperties; ps && ps->name; ++ps) {
if (JS_FlatStringEqualsAscii(str, ps->name)) {
if (PropertySpecNameEqualsId(ps->name, id)) {
psMatch = ps;
break;
}
@ -632,6 +625,15 @@ JSXrayTraits::defineProperty(JSContext *cx, HandleObject wrapper, HandleId id,
return true;
}
static bool
MaybeAppend(jsid id, unsigned flags, AutoIdVector &props)
{
MOZ_ASSERT(!(flags & JSITER_SYMBOLSONLY));
if (!(flags & JSITER_SYMBOLS) && JSID_IS_SYMBOL(id))
return true;
return props.append(id);
}
bool
JSXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags,
AutoIdVector &props)
@ -711,12 +713,12 @@ JSXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags
const js::Class *clasp = js::GetObjectClass(target);
MOZ_ASSERT(clasp->spec.defined());
// Intern all the strings, and pass theme to the caller.
// Convert the method and property names to jsids and pass them to the caller.
for (const JSFunctionSpec *fs = clasp->spec.prototypeFunctions; fs && fs->name; ++fs) {
RootedString str(cx, JS_InternString(cx, fs->name));
if (!str)
jsid id;
if (!PropertySpecNameToPermanentId(cx, fs->name, &id))
return false;
if (!props.append(INTERNED_STRING_TO_JSID(cx, str)))
if (!MaybeAppend(id, flags, props))
return false;
}
for (const JSPropertySpec *ps = clasp->spec.prototypeProperties; ps && ps->name; ++ps) {
@ -727,10 +729,11 @@ JSXrayTraits::enumerateNames(JSContext *cx, HandleObject wrapper, unsigned flags
MOZ_ASSERT(ps->flags & JSPROP_NATIVE_ACCESSORS,
"Self-hosted accessor added to Xrayable class - ping the XPConnect "
"module owner about adding test coverage");
RootedString str(cx, JS_InternString(cx, ps->name));
if (!str)
jsid id;
if (!PropertySpecNameToPermanentId(cx, ps->name, &id))
return false;
if (!props.append(INTERNED_STRING_TO_JSID(cx, str)))
if (!MaybeAppend(id, flags, props))
return false;
}
@ -1978,7 +1981,7 @@ XrayWrapper<Base, Traits>::ownPropertyKeys(JSContext *cx, HandleObject wrapper,
AutoIdVector &props) const
{
assertEnteredPolicy(cx, wrapper, JSID_VOID, BaseProxyHandler::ENUMERATE);
return enumerate(cx, wrapper, JSITER_OWNONLY | JSITER_HIDDEN, props);
return enumerate(cx, wrapper, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, props);
}
template <typename Base, typename Traits>