From 4e4e34238e5bb5af83a645a5f4d2097e3b30e9dd Mon Sep 17 00:00:00 2001 From: Tom Schuster Date: Sun, 25 Jan 2015 21:42:10 +0100 Subject: [PATCH] Bug 1111243 - Implement ES6 proxy behavior for IsArray. r=efaust, a=abillings --- browser/devtools/app-manager/app-projects.js | 2 ++ js/public/Class.h | 5 +++- js/src/jsarray.cpp | 9 ++++-- js/src/jsobjinlines.h | 15 +++++++++- js/src/json.cpp | 11 +++---- js/src/jsproxy.cpp | 45 ++++++++++++++++++++++++++++ 6 files changed, 78 insertions(+), 9 deletions(-) diff --git a/browser/devtools/app-manager/app-projects.js b/browser/devtools/app-manager/app-projects.js index d09f72f..77ca67b 100644 --- a/browser/devtools/app-manager/app-projects.js +++ b/browser/devtools/app-manager/app-projects.js @@ -61,6 +61,8 @@ const IDB = { add: function(project) { let deferred = promise.defer(); + project = JSON.parse(JSON.stringify(project)); + if (!project.location) { // We need to make sure this object has a `.location` property. deferred.reject("Missing location property on project object."); diff --git a/js/public/Class.h b/js/public/Class.h index ff864b1..46f7d39 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -521,7 +521,10 @@ Valueify(const JSClass *c) */ enum ESClassValue { ESClass_Array, ESClass_Number, ESClass_String, ESClass_Boolean, - ESClass_RegExp, ESClass_ArrayBuffer, ESClass_Date + ESClass_RegExp, ESClass_ArrayBuffer, ESClass_Date, + // Special snowflake for the ES6 IsArray method. + // Please don't use it without calling that function. + ESClass_IsArray }; /* diff --git a/js/src/jsarray.cpp b/js/src/jsarray.cpp index 24da176..46f1c20 100644 --- a/js/src/jsarray.cpp +++ b/js/src/jsarray.cpp @@ -2645,7 +2645,8 @@ js::array_concat(JSContext *cx, unsigned argc, Value *vp) HandleValue v = HandleValue::fromMarkedLocation(&p[i]); if (v.isObject()) { RootedObject obj(cx, &v.toObject()); - if (ObjectClassIs(obj, ESClass_Array, cx)) { + // This should be IsConcatSpreadable + if (IsArray(obj, cx)) { uint32_t alength; if (!GetLengthProperty(cx, obj, &alength)) return false; @@ -2870,7 +2871,11 @@ static bool array_isArray(JSContext *cx, unsigned argc, Value *vp) { CallArgs args = CallArgsFromVp(argc, vp); - bool isArray = args.length() > 0 && IsObjectWithClass(args[0], ESClass_Array, cx); + bool isArray = false; + if (args.get(0).isObject()) { + RootedObject obj(cx, &args[0].toObject()); + isArray = IsArray(obj, cx); + } args.rval().setBoolean(isArray); return true; } diff --git a/js/src/jsobjinlines.h b/js/src/jsobjinlines.h index e848ba7..557dd26 100644 --- a/js/src/jsobjinlines.h +++ b/js/src/jsobjinlines.h @@ -1032,7 +1032,10 @@ ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx) return Proxy::objectClassIs(obj, classValue, cx); switch (classValue) { - case ESClass_Array: return obj->is(); + case ESClass_Array: + case ESClass_IsArray: + // There difference between those is only relevant for proxies. + return obj->is(); case ESClass_Number: return obj->is(); case ESClass_String: return obj->is(); case ESClass_Boolean: return obj->is(); @@ -1053,6 +1056,16 @@ IsObjectWithClass(const Value &v, ESClassValue classValue, JSContext *cx) return ObjectClassIs(obj, classValue, cx); } +// ES6 7.2.2 +inline bool +IsArray(HandleObject obj, JSContext *cx) +{ + if (obj->is()) + return true; + + return ObjectClassIs(obj, ESClass_IsArray, cx); +} + static MOZ_ALWAYS_INLINE bool NewObjectMetadata(ExclusiveContext *cxArg, JSObject **pmetadata) { diff --git a/js/src/json.cpp b/js/src/json.cpp index 6e45bfd..81a99a6 100644 --- a/js/src/json.cpp +++ b/js/src/json.cpp @@ -300,7 +300,7 @@ JO(JSContext *cx, HandleObject obj, StringifyContext *scx) Maybe ids; const AutoIdVector *props; if (scx->replacer && !scx->replacer->isCallable()) { - JS_ASSERT(JS_IsArrayObject(cx, scx->replacer)); + JS_ASSERT(IsArray(scx->replacer, cx)); props = &scx->propertyList; } else { JS_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0); @@ -488,7 +488,7 @@ Str(JSContext *cx, const Value &v, StringifyContext *scx) scx->depth++; bool ok; - if (ObjectClassIs(obj, ESClass_Array, cx)) + if (IsArray(obj, cx)) ok = JA(cx, obj, scx); else ok = JO(cx, obj, scx); @@ -510,7 +510,7 @@ js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value sp if (replacer) { if (replacer->isCallable()) { /* Step 4a(i): use replacer to transform values. */ - } else if (ObjectClassIs(replacer, ESClass_Array, cx)) { + } else if (IsArray(replacer, cx)) { /* * Step 4b: The spec algorithm is unhelpfully vague about the exact * steps taken when the replacer is an array, regarding the exact @@ -541,7 +541,8 @@ js_Stringify(JSContext *cx, MutableHandleValue vp, JSObject *replacer_, Value sp /* Step 4b(ii). */ uint32_t len; - JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len)); + if (!GetLengthProperty(cx, replacer, &len)) + return false; if (replacer->is() && !replacer->isIndexed()) len = Min(len, replacer->getDenseInitializedLength()); @@ -678,7 +679,7 @@ Walk(JSContext *cx, HandleObject holder, HandleId name, HandleValue reviver, Mut if (val.isObject()) { RootedObject obj(cx, &val.toObject()); - if (ObjectClassIs(obj, ESClass_Array, cx)) { + if (IsArray(obj, cx)) { /* Step 2a(ii). */ uint32_t length; if (!GetLengthProperty(cx, obj, &length)) diff --git a/js/src/jsproxy.cpp b/js/src/jsproxy.cpp index 7644da1..7453103 100644 --- a/js/src/jsproxy.cpp +++ b/js/src/jsproxy.cpp @@ -1108,6 +1108,14 @@ class ScriptedDirectProxyHandler : public DirectProxyHandler { virtual bool isExtensible(JSContext *cx, HandleObject proxy, bool *extensible) MOZ_OVERRIDE; /* Spidermonkey extensions. */ + // A scripted proxy should not be treated as generic in most contexts. + virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, + CallArgs args) MOZ_OVERRIDE; + virtual bool objectClassIs(HandleObject obj, ESClassValue classValue, + JSContext *cx) MOZ_OVERRIDE; + virtual bool regexp_toShared(JSContext *cx, HandleObject proxy, + RegExpGuard *g) MOZ_OVERRIDE; + virtual bool call(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; virtual bool construct(JSContext *cx, HandleObject proxy, const CallArgs &args) MOZ_OVERRIDE; virtual bool isScripted() MOZ_OVERRIDE { return true; } @@ -2350,6 +2358,43 @@ ScriptedDirectProxyHandler::construct(JSContext *cx, HandleObject proxy, const C return true; } +bool +ScriptedDirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl, + CallArgs args) +{ + ReportIncompatible(cx, args); + return false; +} + +bool +ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue, + JSContext *cx) +{ + // Special case IsArray. In every other instance ES wants to have exactly + // one object type and not a proxy around it, so return false. + if (classValue != ESClass_IsArray) + return false; + + // In ES6 IsArray is supposed to poke at the Proxy target, instead we do this here. + // The reason for this is that we have proxies for which looking at the target might + // be impossible. So instead we use our little objectClassIs function that just works + // already across different wrappers. + RootedObject target(cx, proxy->as().target()); + if (!target) + return false; + + return IsArray(target, cx); +} + +bool +ScriptedDirectProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy, + RegExpGuard *g) +{ + MOZ_CRASH("Should not end up in ScriptedDirectProxyHandler::regexp_toShared"); + return false; +} + + ScriptedDirectProxyHandler ScriptedDirectProxyHandler::singleton; #define INVOKE_ON_PROTOTYPE(cx, handler, proxy, protoCall) \ -- 2.2.1