Bug 1111243 - Implement ES6 proxy behavior for IsArray. r=efaust, a=sledru
authorTom Schuster <evilpies@gmail.com>
Sun, 25 Jan 2015 21:42:10 +0100
changeset 243041 bf8644a5c52a
parent 243040 7f44816c0449
child 243042 508190797a80
push id4370
push userryanvm@gmail.com
push date2015-01-26 15:57 +0000
treeherdermozilla-beta@bf8644a5c52a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersefaust, sledru
bugs1111243
milestone36.0
Bug 1111243 - Implement ES6 proxy behavior for IsArray. r=efaust, a=sledru
browser/devtools/app-manager/app-projects.js
js/public/Class.h
js/src/jsarray.cpp
js/src/jsobjinlines.h
js/src/json.cpp
js/src/proxy/ScriptedDirectProxyHandler.cpp
js/src/proxy/ScriptedDirectProxyHandler.h
js/src/vm/RegExpObject.h
--- a/browser/devtools/app-manager/app-projects.js
+++ b/browser/devtools/app-manager/app-projects.js
@@ -79,16 +79,18 @@ const IDB = {
     };
 
     return deferred.promise;
   },
 
   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.");
     } else {
       let transaction = IDB._db.transaction(["projects"], "readwrite");
       let objectStore = transaction.objectStore("projects");
       let request = objectStore.add(project);
       request.onerror = function(event) {
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -591,17 +591,21 @@ Valueify(const JSClass *c)
 
 /*
  * Enumeration describing possible values of the [[Class]] internal property
  * value of objects.
  */
 enum ESClassValue {
     ESClass_Object, ESClass_Array, ESClass_Number, ESClass_String,
     ESClass_Boolean, ESClass_RegExp, ESClass_ArrayBuffer, ESClass_SharedArrayBuffer,
-    ESClass_Date, ESClass_Set, ESClass_Map
+    ESClass_Date, ESClass_Set, ESClass_Map,
+
+    // Special snowflake for the ES6 IsArray method.
+    // Please don't use it without calling that function.
+    ESClass_IsArray
 };
 
 /*
  * Return whether the given object has the given [[Class]] internal property
  * value. Beware, this query says nothing about the js::Class of the JSObject
  * so the caller must not assume anything about obj's representation (e.g., obj
  * may be a proxy).
  */
--- a/js/src/jsarray.cpp
+++ b/js/src/jsarray.cpp
@@ -2728,17 +2728,18 @@ js::array_concat(JSContext *cx, unsigned
 
     /* Loop over [0, argc] to concat args into narr, expanding all Arrays. */
     for (unsigned i = 0; i <= argc; i++) {
         if (!CheckForInterrupt(cx))
             return false;
         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;
                 RootedValue tmp(cx);
                 for (uint32_t slot = 0; slot < alength; slot++) {
                     bool hole;
                     if (!CheckForInterrupt(cx) || !GetElement(cx, obj, slot, &hole, &tmp))
                         return false;
@@ -3096,17 +3097,21 @@ array_filter(JSContext *cx, unsigned arg
     args.rval().setObject(*arr);
     return true;
 }
 
 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;
 }
 
 static bool
 IsArrayConstructor(const Value &v)
 {
     // This must only return true if v is *the* Array constructor for the
--- a/js/src/jsobjinlines.h
+++ b/js/src/jsobjinlines.h
@@ -764,17 +764,20 @@ GuessArrayGCKind(size_t numSlots)
 inline bool
 ObjectClassIs(HandleObject obj, ESClassValue classValue, JSContext *cx)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::objectClassIs(obj, classValue, cx);
 
     switch (classValue) {
       case ESClass_Object: return obj->is<JSObject>();
-      case ESClass_Array: return obj->is<ArrayObject>();
+      case ESClass_Array:
+      case ESClass_IsArray:
+        // There difference between those is only relevant for proxies.
+        return obj->is<ArrayObject>();
       case ESClass_Number: return obj->is<NumberObject>();
       case ESClass_String: return obj->is<StringObject>();
       case ESClass_Boolean: return obj->is<BooleanObject>();
       case ESClass_RegExp: return obj->is<RegExpObject>();
       case ESClass_ArrayBuffer: return obj->is<ArrayBufferObject>();
       case ESClass_SharedArrayBuffer: return obj->is<SharedArrayBufferObject>();
       case ESClass_Date: return obj->is<DateObject>();
       case ESClass_Set: return obj->is<SetObject>();
@@ -787,16 +790,26 @@ inline bool
 IsObjectWithClass(const Value &v, ESClassValue classValue, JSContext *cx)
 {
     if (!v.isObject())
         return false;
     RootedObject obj(cx, &v.toObject());
     return ObjectClassIs(obj, classValue, cx);
 }
 
+// ES6 7.2.2
+inline bool
+IsArray(HandleObject obj, JSContext *cx)
+{
+    if (obj->is<ArrayObject>())
+        return true;
+
+    return ObjectClassIs(obj, ESClass_IsArray, cx);
+}
+
 inline bool
 Unbox(JSContext *cx, HandleObject obj, MutableHandleValue vp)
 {
     if (MOZ_UNLIKELY(obj->is<ProxyObject>()))
         return Proxy::boxedValue_unbox(cx, obj, vp);
 
     if (obj->is<BooleanObject>())
         vp.setBoolean(obj->as<BooleanObject>().unbox());
--- a/js/src/json.cpp
+++ b/js/src/json.cpp
@@ -324,17 +324,17 @@ JO(JSContext *cx, HandleObject obj, Stri
 
     if (!scx->sb.append('{'))
         return false;
 
     /* Steps 5-7. */
     Maybe<AutoIdVector> ids;
     const AutoIdVector *props;
     if (scx->replacer && !scx->replacer->isCallable()) {
-        MOZ_ASSERT(JS_IsArrayObject(cx, scx->replacer));
+        MOZ_ASSERT(IsArray(scx->replacer, cx));
         props = &scx->propertyList;
     } else {
         MOZ_ASSERT_IF(scx->replacer, scx->propertyList.length() == 0);
         ids.emplace(cx);
         if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY, ids.ptr()))
             return false;
         props = ids.ptr();
     }
@@ -509,17 +509,17 @@ Str(JSContext *cx, const Value &v, Strin
     }
 
     /* Step 10. */
     MOZ_ASSERT(v.isObject());
     RootedObject obj(cx, &v.toObject());
 
     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);
     scx->depth--;
 
     return ok;
 }
 
@@ -531,17 +531,17 @@ js_Stringify(JSContext *cx, MutableHandl
     RootedObject replacer(cx, replacer_);
     RootedValue space(cx, space_);
 
     /* Step 4. */
     AutoIdVector propertyList(cx);
     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
              * sequence of [[Get]] calls for the array's elements, when its
              * overall length is calculated, whether own or own plus inherited
              * properties are considered, and so on.  A rewrite was proposed in
              * <https://mail.mozilla.org/pipermail/es5-discuss/2011-April/003976.html>,
              * whose steps are copied below, and which are implemented here.
@@ -562,17 +562,18 @@ js_Stringify(JSContext *cx, MutableHandl
              *      6. If item is not undefined and item is not currently an
              *         element of PropertyList then,
              *         a. Append item to the end of PropertyList.
              *      7. Let i be i + 1.
              */
 
             /* Step 4b(ii). */
             uint32_t len;
-            JS_ALWAYS_TRUE(GetLengthProperty(cx, replacer, &len));
+            if (!GetLengthProperty(cx, replacer, &len))
+                return false;
             if (replacer->is<ArrayObject>() && !replacer->isIndexed())
                 len = Min(len, replacer->as<ArrayObject>().getDenseInitializedLength());
 
             // Cap the initial size to a moderately small value.  This avoids
             // ridiculous over-allocation if an array with bogusly-huge length
             // is passed in.  If we end up having to add elements past this
             // size, the set will naturally resize to accommodate them.
             const uint32_t MaxInitialSize = 1024;
@@ -698,17 +699,17 @@ Walk(JSContext *cx, HandleObject holder,
     RootedValue val(cx);
     if (!JSObject::getGeneric(cx, holder, holder, name, &val))
         return false;
 
     /* Step 2. */
     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))
                 return false;
 
             /* Step 2a(i), 2a(iii-iv). */
             RootedId id(cx);
             RootedValue newElement(cx);
--- a/js/src/proxy/ScriptedDirectProxyHandler.cpp
+++ b/js/src/proxy/ScriptedDirectProxyHandler.cpp
@@ -1119,16 +1119,60 @@ ScriptedDirectProxyHandler::construct(JS
     if (!args.rval().isObject()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_PROXY_CONSTRUCT_OBJECT);
         return false;
     }
     return true;
 }
 
 bool
+ScriptedDirectProxyHandler::nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
+                                       CallArgs args) const
+{
+    ReportIncompatible(cx, args);
+    return false;
+}
+
+bool
+ScriptedDirectProxyHandler::objectClassIs(HandleObject proxy, ESClassValue classValue,
+                                          JSContext *cx) const
+{
+    // 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<ProxyObject>().target());
+    if (!target)
+        return false;
+
+    return IsArray(target, cx);
+}
+
+bool
+ScriptedDirectProxyHandler::regexp_toShared(JSContext *cx, HandleObject proxy,
+                                            RegExpGuard *g) const
+{
+    MOZ_CRASH("Should not end up in ScriptedDirectProxyHandler::regexp_toShared");
+    return false;
+}
+
+bool
+ScriptedDirectProxyHandler::boxedValue_unbox(JSContext *cx, HandleObject proxy,
+                                             MutableHandleValue vp) const
+{
+    MOZ_CRASH("Should not end up in ScriptedDirectProxyHandler::boxedValue_unbox");
+    return false;
+}
+
+bool
 ScriptedDirectProxyHandler::isCallable(JSObject *obj) const
 {
     MOZ_ASSERT(obj->as<ProxyObject>().handler() == &ScriptedDirectProxyHandler::singleton);
     return obj->as<ProxyObject>().extra(IS_CALLABLE_EXTRA).toBoolean();
 }
 
 const char ScriptedDirectProxyHandler::family = 0;
 const ScriptedDirectProxyHandler ScriptedDirectProxyHandler::singleton;
--- a/js/src/proxy/ScriptedDirectProxyHandler.h
+++ b/js/src/proxy/ScriptedDirectProxyHandler.h
@@ -62,16 +62,26 @@ class ScriptedDirectProxyHandler : publi
                                               AutoIdVector &props) const MOZ_OVERRIDE {
         return BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props);
     }
     virtual bool getEnumerablePropertyKeys(JSContext *cx, HandleObject proxy,
                                            AutoIdVector &props) const MOZ_OVERRIDE;
     virtual bool iterate(JSContext *cx, HandleObject proxy, unsigned flags,
                          MutableHandleObject objp) const MOZ_OVERRIDE;
 
+    // A scripted proxy should not be treated as generic in most contexts.
+    virtual bool nativeCall(JSContext *cx, IsAcceptableThis test, NativeImpl impl,
+                            CallArgs args) const MOZ_OVERRIDE;
+    virtual bool objectClassIs(HandleObject obj, ESClassValue classValue,
+                               JSContext *cx) const MOZ_OVERRIDE;
+    virtual bool regexp_toShared(JSContext *cx, HandleObject proxy,
+                                 RegExpGuard *g) const MOZ_OVERRIDE;
+    virtual bool boxedValue_unbox(JSContext *cx, HandleObject proxy,
+                                  MutableHandleValue vp) const MOZ_OVERRIDE;
+
     virtual bool isCallable(JSObject *obj) const MOZ_OVERRIDE;
     virtual bool isConstructor(JSObject *obj) const MOZ_OVERRIDE {
         // For now we maintain the broken behavior that a scripted proxy is constructable if it's
         // callable. See bug 929467.
         return isCallable(obj);
     }
     virtual bool isScripted() const MOZ_OVERRIDE { return true; }
 
--- a/js/src/vm/RegExpObject.h
+++ b/js/src/vm/RegExpObject.h
@@ -484,16 +484,17 @@ bool
 ParseRegExpFlags(JSContext *cx, JSString *flagStr, RegExpFlag *flagsOut);
 
 /* Assuming ObjectClassIs(obj, ESClass_RegExp), return a RegExpShared for obj. */
 inline bool
 RegExpToShared(JSContext *cx, HandleObject obj, RegExpGuard *g)
 {
     if (obj->is<RegExpObject>())
         return obj->as<RegExpObject>().getShared(cx, g);
+    MOZ_ASSERT(Proxy::objectClassIs(obj, ESClass_RegExp, cx));
     return Proxy::regexp_toShared(cx, obj, g);
 }
 
 template<XDRMode mode>
 bool
 XDRScriptRegExpObject(XDRState<mode> *xdr, MutableHandle<RegExpObject*> objp);
 
 extern JSObject *