Bug 1317400 - Part 2: Generate better source in Object.prototype.toSource. r=till
authorTooru Fujisawa <arai_a@mac.com>
Sat, 04 Mar 2017 20:37:13 +0900
changeset 375041 7397faeecc701270fd8f10d12bd0cc1efb7fc48c
parent 375040 2e84f5f909ff1d1bfa1a0dc864951079be59facd
child 375042 9c0990f1b73b188c82fd712c55a0d3c19af7ee52
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1317400
milestone54.0a1
Bug 1317400 - Part 2: Generate better source in Object.prototype.toSource. r=till
js/src/builtin/Object.cpp
js/src/jit-test/tests/latin1/assorted.js
js/src/tests/ecma_2017/Function/Object-toSource.js
js/src/tests/ecma_2017/Function/browser.js
js/src/tests/ecma_2017/Function/shell.js
--- a/js/src/builtin/Object.cpp
+++ b/js/src/builtin/Object.cpp
@@ -4,21 +4,23 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "builtin/Object.h"
 
 #include "mozilla/ArrayUtils.h"
 
 #include "jscntxt.h"
+#include "jsstr.h"
 
 #include "builtin/Eval.h"
 #include "frontend/BytecodeCompiler.h"
 #include "jit/InlinableNatives.h"
 #include "js/UniquePtr.h"
+#include "vm/AsyncFunction.h"
 #include "vm/StringBuffer.h"
 
 #include "jsobjinlines.h"
 
 #include "vm/NativeObject-inl.h"
 #include "vm/Shape-inl.h"
 
 #ifdef FUZZING
@@ -125,56 +127,107 @@ obj_toSource(JSContext* cx, unsigned arg
     JSString* str = ObjectToSource(cx, obj);
     if (!str)
         return false;
 
     args.rval().setString(str);
     return true;
 }
 
+template <typename CharT>
+static bool
+Consume(const CharT*& s, const CharT* e, const char *chars)
+{
+    size_t len = strlen(chars);
+    if (s + len >= e)
+        return false;
+    if (!EqualChars(s, chars, len))
+        return false;
+    s += len;
+    return true;
+}
+
+template <typename CharT>
+static void
+ConsumeSpaces(const CharT*& s, const CharT* e)
+{
+    while (*s == ' ' && s < e)
+        s++;
+}
+
 /*
  * Given a function source string, return the offset and length of the part
  * between '(function $name' and ')'.
  */
 template <typename CharT>
 static bool
 ArgsAndBodySubstring(mozilla::Range<const CharT> chars, size_t* outOffset, size_t* outLen)
 {
     const CharT* const start = chars.begin().get();
-    const CharT* const end = chars.end().get();
     const CharT* s = start;
+    const CharT* e = chars.end().get();
 
-    uint8_t parenChomp = 0;
-    if (s[0] == '(') {
-        s++;
-        parenChomp = 1;
-    }
-
-    /* Try to jump "function" keyword. */
-    s = js_strchr_limit(s, ' ', end);
-    if (!s)
+    if (s == e)
         return false;
 
-    /*
-     * Jump over the function's name: it can't be encoded as part
-     * of an ECMA getter or setter.
-     */
-    s = js_strchr_limit(s, '(', end);
-    if (!s)
-        return false;
+    // Remove enclosing parentheses.
+    if (*s == '(' && *(e - 1) == ')') {
+        s++;
+        e--;
+    }
+
+    // Support the following cases, with spaces between tokens:
+    //
+    //   -+---------+-+------------+-+-----+-+- [ - <any> - ] - ( -+-
+    //    |         | |            | |     | |                     |
+    //    +- async -+ +- function -+ +- * -+ +- <any> - ( ---------+
+    //                |            |
+    //                +- get ------+
+    //                |            |
+    //                +- set ------+
+    //
+    // This accepts some invalid syntax, but we don't care, since it's only
+    // used by the non-standard toSource, and we're doing a best-effort attempt
+    // here.
 
-    if (*s == ' ')
+    (void) Consume(s, e, "async");
+    ConsumeSpaces(s, e);
+    (void) (Consume(s, e, "function") || Consume(s, e, "get") || Consume(s, e, "set"));
+    ConsumeSpaces(s, e);
+    (void) Consume(s, e, "*");
+    ConsumeSpaces(s, e);
+
+    // Jump over the function's name.
+    if (Consume(s, e, "[")) {
+        s = js_strchr_limit(s, ']', e);
+        if (!s)
+            return false;
         s++;
+        ConsumeSpaces(s, e);
+        if (*s != '(')
+            return false;
+    } else {
+        s = js_strchr_limit(s, '(', e);
+        if (!s)
+            return false;
+    }
 
     *outOffset = s - start;
-    *outLen = end - s - parenChomp;
+    *outLen = e - s;
     MOZ_ASSERT(*outOffset + *outLen <= chars.length());
     return true;
 }
 
+enum class PropertyKind {
+    Getter,
+    Setter,
+    Method,
+    Normal
+};
+
 JSString*
 js::ObjectToSource(JSContext* cx, HandleObject obj)
 {
     /* If outermost, we need parentheses to be an expression, not a block. */
     bool outermost = cx->cycleDetectorVector().empty();
 
     AutoCycleDetector detector(cx, obj);
     if (!detector.init())
@@ -183,128 +236,196 @@ js::ObjectToSource(JSContext* cx, Handle
         return NewStringCopyZ<CanGC>(cx, "{}");
 
     StringBuffer buf(cx);
     if (outermost && !buf.append('('))
         return nullptr;
     if (!buf.append('{'))
         return nullptr;
 
-    RootedValue v0(cx), v1(cx);
-    MutableHandleValue val[2] = {&v0, &v1};
-
-    RootedString str0(cx), str1(cx);
-    MutableHandleString gsop[2] = {&str0, &str1};
-
     AutoIdVector idv(cx);
     if (!GetPropertyKeys(cx, obj, JSITER_OWNONLY | JSITER_SYMBOLS, &idv))
         return nullptr;
 
     bool comma = false;
-    for (size_t i = 0; i < idv.length(); ++i) {
-        RootedId id(cx, idv[i]);
-        Rooted<PropertyDescriptor> desc(cx);
-        if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
-            return nullptr;
 
-        int valcnt = 0;
-        if (desc.object()) {
-            if (desc.isAccessorDescriptor()) {
-                if (desc.hasGetterObject() && desc.getterObject()) {
-                    val[valcnt].setObject(*desc.getterObject());
-                    gsop[valcnt].set(cx->names().get);
-                    valcnt++;
-                }
-                if (desc.hasSetterObject() && desc.setterObject()) {
-                    val[valcnt].setObject(*desc.setterObject());
-                    gsop[valcnt].set(cx->names().set);
-                    valcnt++;
-                }
-            } else {
-                valcnt = 1;
-                val[0].set(desc.value());
-                gsop[0].set(nullptr);
-            }
-        }
-
+    auto AddProperty = [cx, &comma, &buf](HandleId id, HandleValue val, PropertyKind kind) -> bool {
         /* Convert id to a string. */
         RootedString idstr(cx);
         if (JSID_IS_SYMBOL(id)) {
             RootedValue v(cx, SymbolValue(JSID_TO_SYMBOL(id)));
             idstr = ValueToSource(cx, v);
             if (!idstr)
-                return nullptr;
+                return false;
         } else {
             RootedValue idv(cx, IdToValue(id));
             idstr = ToString<CanGC>(cx, idv);
             if (!idstr)
-                return nullptr;
+                return false;
 
             /*
-             * If id is a string that's not an identifier, or if it's a negative
-             * integer, then it must be quoted.
+             * If id is a string that's not an identifier, or if it's a
+             * negative integer, then it must be quoted.
              */
             if (JSID_IS_ATOM(id)
                 ? !IsIdentifier(JSID_TO_ATOM(id))
                 : JSID_TO_INT(id) < 0)
             {
                 idstr = QuoteString(cx, idstr, char16_t('\''));
                 if (!idstr)
-                    return nullptr;
+                    return false;
             }
         }
 
-        for (int j = 0; j < valcnt; j++) {
-            /* Convert val[j] to its canonical source form. */
-            JSString* valsource = ValueToSource(cx, val[j]);
-            if (!valsource)
-                return nullptr;
+        RootedString valsource(cx, ValueToSource(cx, val));
+        if (!valsource)
+            return false;
+
+        RootedLinearString valstr(cx, valsource->ensureLinear(cx));
+        if (!valstr)
+            return false;
+
+        if (comma && !buf.append(", "))
+            return false;
+        comma = true;
+
+        size_t voffset, vlength;
 
-            RootedLinearString valstr(cx, valsource->ensureLinear(cx));
-            if (!valstr)
-                return nullptr;
+        // Methods and accessors can return exact syntax of source, that fits
+        // into property without adding property name or "get"/"set" prefix.
+        // Use the exact syntax when the following conditions are met:
+        //
+        //   * It's a function object
+        //     (exclude proxies)
+        //   * Function's kind and property's kind are same
+        //     (this can be false for dynamically defined properties)
+        //   * Function has explicit name
+        //     (this can be false for computed property and dynamically defined
+        //      properties)
+        //   * Function's name and property's name are same
+        //     (this can be false for dynamically defined properties)
+        if (kind == PropertyKind::Getter || kind == PropertyKind::Setter ||
+            kind == PropertyKind::Method)
+        {
+            RootedFunction fun(cx);
+            if (val.toObject().is<JSFunction>()) {
+                fun = &val.toObject().as<JSFunction>();
+                // Method's case should be checked on caller.
+                if (((fun->isGetter() && kind == PropertyKind::Getter) ||
+                     (fun->isSetter() && kind == PropertyKind::Setter) ||
+                     kind == PropertyKind::Method) &&
+                    fun->explicitName())
+                {
+                    bool result;
+                    if (!EqualStrings(cx, fun->explicitName(), idstr, &result))
+                        return false;
 
-            size_t voffset = 0;
-            size_t vlength = valstr->length();
+                    if (result)  {
+                        if (!buf.append(valstr))
+                            return false;
+                        return true;
+                    }
+                }
+            }
 
-            /*
-             * Remove '(function ' from the beginning of valstr and ')' from the
-             * end so that we can put "get" in front of the function definition.
-             */
-            if (gsop[j] && IsFunctionObject(val[j])) {
+            {
+                // When falling back try to generate a better string
+                // representation by skipping the prelude, and also removing
+                // the enclosing parentheses.
                 bool success;
                 JS::AutoCheckCannotGC nogc;
                 if (valstr->hasLatin1Chars())
                     success = ArgsAndBodySubstring(valstr->latin1Range(nogc), &voffset, &vlength);
                 else
                     success = ArgsAndBodySubstring(valstr->twoByteRange(nogc), &voffset, &vlength);
                 if (!success)
-                    gsop[j].set(nullptr);
+                    kind = PropertyKind::Normal;
             }
 
-            if (comma && !buf.append(", "))
-                return nullptr;
-            comma = true;
+            if (kind == PropertyKind::Getter) {
+                if (!buf.append("get "))
+                    return false;
+            } else if (kind == PropertyKind::Setter) {
+                if (!buf.append("set "))
+                    return false;
+            } else if (kind == PropertyKind::Method && fun) {
+                if (IsWrappedAsyncFunction(fun)) {
+                    if (!buf.append("async "))
+                        return false;
+                }
+
+                if (fun->isStarGenerator()) {
+                    if (!buf.append('*'))
+                        return false;
+                }
+            }
+        }
+
+        bool needsBracket = JSID_IS_SYMBOL(id);
+        if (needsBracket && !buf.append('['))
+            return false;
+        if (!buf.append(idstr))
+            return false;
+        if (needsBracket && !buf.append(']'))
+            return false;
 
-            if (gsop[j]) {
-                if (!buf.append(gsop[j]) || !buf.append(' '))
+        if (kind == PropertyKind::Getter || kind == PropertyKind::Setter ||
+            kind == PropertyKind::Method)
+        {
+            if (!buf.appendSubstring(valstr, voffset, vlength))
+                return false;
+        } else {
+            if (!buf.append(':'))
+                return false;
+            if (!buf.append(valstr))
+                return false;
+        }
+        return true;
+    };
+
+    RootedId id(cx);
+    Rooted<PropertyDescriptor> desc(cx);
+    RootedValue val(cx);
+    RootedFunction fun(cx);
+    for (size_t i = 0; i < idv.length(); ++i) {
+        id = idv[i];
+        if (!GetOwnPropertyDescriptor(cx, obj, id, &desc))
+            return nullptr;
+
+        if (!desc.object())
+            continue;
+
+        if (desc.isAccessorDescriptor()) {
+            if (desc.hasGetterObject() && desc.getterObject()) {
+                val.setObject(*desc.getterObject());
+                if (!AddProperty(id, val, PropertyKind::Getter))
                     return nullptr;
             }
-            if (JSID_IS_SYMBOL(id) && !buf.append('['))
-                return nullptr;
-            if (!buf.append(idstr))
-                return nullptr;
-            if (JSID_IS_SYMBOL(id) && !buf.append(']'))
-                return nullptr;
-            if (!buf.append(gsop[j] ? ' ' : ':'))
-                return nullptr;
+            if (desc.hasSetterObject() && desc.setterObject()) {
+                val.setObject(*desc.setterObject());
+                if (!AddProperty(id, val, PropertyKind::Setter))
+                    return nullptr;
+            }
+            continue;
+        }
 
-            if (!buf.appendSubstring(valstr, voffset, vlength))
-                return nullptr;
+        val.set(desc.value());
+        if (IsFunctionObject(val, fun.address())) {
+            if (IsWrappedAsyncFunction(fun))
+                fun = GetUnwrappedAsyncFunction(fun);
+
+            if (fun->isMethod()) {
+                if (!AddProperty(id, val, PropertyKind::Method))
+                    return nullptr;
+                continue;
+            }
         }
+
+        if (!AddProperty(id, val, PropertyKind::Normal))
+            return nullptr;
     }
 
     if (!buf.append('}'))
         return nullptr;
     if (outermost && !buf.append(')'))
         return nullptr;
 
     return buf.finishString();
--- a/js/src/jit-test/tests/latin1/assorted.js
+++ b/js/src/jit-test/tests/latin1/assorted.js
@@ -18,17 +18,17 @@ assertEq(o.toSource(), "({get prop() { r
 Object.defineProperty(o, "prop", {get: function() { return "\u1200"; },
                                   set: function() { return "\u1200"; },
                                   enumerable: true});
 assertEq(o.toSource(), '({get prop() { return "\\u1200"; }, set prop() { return "\\u1200"; }})');
 
 var ff = function() { return 10; };
 ff.toSource = function() { return "((11))"; }
 Object.defineProperty(o, "prop", {get: ff, set: ff, enumerable: true});
-assertEq(o.toSource(), "({prop:((11)), prop:((11))})");
+assertEq(o.toSource(), "({get prop(11), set prop(11)})");
 
 // XDR
 load(libdir + 'bytecode-cache.js');
 
 // Latin1 string constant
 test = "'string123';";
 evalWithCache(test, { assertEqBytecode: true, assertEqResult : true });
 
new file mode 100644
--- /dev/null
+++ b/js/src/tests/ecma_2017/Function/Object-toSource.js
@@ -0,0 +1,370 @@
+var BUGNUMBER = 1317400;
+var summary = "Function string representation in Object.prototype.toSource";
+
+print(BUGNUMBER + ": " + summary);
+
+// Methods.
+
+assertEq(({ foo(){} }).toSource(),
+         "({foo(){}})");
+assertEq(({ *foo(){} }).toSource(),
+         "({*foo(){}})");
+assertEq(({ async foo(){} }).toSource(),
+         "({async foo(){}})");
+
+assertEq(({ 1(){} }).toSource(),
+         "({1(){}})");
+
+// Methods with more spacing.
+// Spacing is kept.
+
+assertEq(({ foo (){} }).toSource(),
+         "({foo (){}})");
+assertEq(({ foo () {} }).toSource(),
+         "({foo () {}})");
+
+// Methods with computed name.
+// Method syntax is composed.
+
+let name = "foo";
+assertEq(({ [name](){} }).toSource(),
+         "({foo(){}})");
+assertEq(({ *[name](){} }).toSource(),
+         "({*foo(){}})");
+assertEq(({ async [name](){} }).toSource(),
+         "({async foo(){}})");
+
+assertEq(({ [ Symbol.iterator ](){} }).toSource(),
+         "({[Symbol.iterator](){}})");
+
+// Accessors.
+
+assertEq(({ get foo(){} }).toSource(),
+         "({get foo(){}})");
+assertEq(({ set foo(v){} }).toSource(),
+         "({set foo(v){}})");
+
+// Accessors with computed name.
+// Method syntax is composed.
+
+assertEq(({ get [name](){} }).toSource(),
+         "({get foo(){}})");
+assertEq(({ set [name](v){} }).toSource(),
+         "({set foo(v){}})");
+
+assertEq(({ get [ Symbol.iterator ](){} }).toSource(),
+         "({get [Symbol.iterator](){}})");
+assertEq(({ set [ Symbol.iterator ](v){} }).toSource(),
+         "({set [Symbol.iterator](v){}})");
+
+// Getter and setter with same name.
+// Getter always comes before setter.
+
+assertEq(({ get foo(){}, set foo(v){} }).toSource(),
+         "({get foo(){}, set foo(v){}})");
+assertEq(({ set foo(v){}, get foo(){} }).toSource(),
+         "({get foo(){}, set foo(v){}})");
+
+// Normal properties.
+
+assertEq(({ foo: function(){} }).toSource(),
+         "({foo:(function(){})})");
+assertEq(({ foo: function bar(){} }).toSource(),
+         "({foo:(function bar(){})})");
+assertEq(({ foo: function*(){} }).toSource(),
+         "({foo:(function*(){})})");
+assertEq(({ foo: async function(){} }).toSource(),
+         "({foo:(async function(){})})");
+
+// Normal properties with computed name.
+
+assertEq(({ [ Symbol.iterator ]: function(){} }).toSource(),
+         "({[Symbol.iterator]:(function(){})})");
+
+// Dynamically defined properties with function expression.
+// Never become a method syntax.
+
+let obj = {};
+obj.foo = function() {};
+assertEq(obj.toSource(),
+         "({foo:(function() {})})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: function() {}});
+assertEq(obj.toSource(),
+         "({})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: function() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({foo:(function() {})})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: function bar() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({foo:(function bar() {})})");
+
+obj = {};
+Object.defineProperty(obj, Symbol.iterator, {value: function() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({[Symbol.iterator]:(function() {})})");
+
+// Dynamically defined property with other object's method.
+// Method syntax is composed.
+
+let method = ({foo() {}}).foo;
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: method, enumerable: true});
+assertEq(obj.toSource(),
+         "({foo() {}})");
+
+obj = {};
+Object.defineProperty(obj, "bar", {value: method, enumerable: true});
+assertEq(obj.toSource(),
+         "({bar() {}})");
+
+method = ({*foo() {}}).foo;
+
+obj = {};
+Object.defineProperty(obj, "bar", {value: method, enumerable: true});
+assertEq(obj.toSource(),
+         "({*bar() {}})");
+
+method = ({async foo() {}}).foo;
+
+obj = {};
+Object.defineProperty(obj, "bar", {value: method, enumerable: true});
+assertEq(obj.toSource(),
+         "({async bar() {}})");
+
+// Dynamically defined accessors.
+// Accessor syntax is composed.
+
+obj = {};
+Object.defineProperty(obj, "foo", {get: function() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo() {}})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {set: function() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo() {}})");
+
+obj = {};
+Object.defineProperty(obj, Symbol.iterator, {get: function() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({get [Symbol.iterator]() {}})");
+
+obj = {};
+Object.defineProperty(obj, Symbol.iterator, {set: function() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({set [Symbol.iterator]() {}})");
+
+// Dynamically defined accessors with other object's accessors.
+// Accessor syntax is composed.
+
+let accessor = Object.getOwnPropertyDescriptor({ get foo() {} }, "foo").get;
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo() {}})");
+
+accessor = Object.getOwnPropertyDescriptor({ get bar() {} }, "bar").get;
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo() {}})");
+
+accessor = Object.getOwnPropertyDescriptor({ set foo(v) {} }, "foo").set;
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo(v) {}})");
+
+accessor = Object.getOwnPropertyDescriptor({ set bar(v) {} }, "bar").set;
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo(v) {}})");
+
+accessor = Object.getOwnPropertyDescriptor({ get foo() {} }, "foo").get;
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo() {}})");
+
+accessor = Object.getOwnPropertyDescriptor({ get bar() {} }, "bar").get;
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo() {}})");
+
+accessor = Object.getOwnPropertyDescriptor({ set foo(v) {} }, "foo").set;
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo(v) {}})");
+
+accessor = Object.getOwnPropertyDescriptor({ set bar(v) {} }, "bar").set;
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo(v) {}})");
+
+// Methods with proxy.
+// Treated as normal property.
+
+method = ({foo() {}}).foo;
+let handler = {
+  get(that, name) {
+    if (name == "toSource") {
+      return function() {
+        return that.toSource();
+      };
+    }
+    return that[name];
+  }
+};
+let proxy = new Proxy(method, handler);
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: proxy, enumerable: true});
+assertEq(obj.toSource(),
+         "({foo:foo() {}})");
+
+// Accessors with proxy.
+// Accessor syntax is composed.
+
+accessor = Object.getOwnPropertyDescriptor({ get foo() {} }, "foo").get;
+proxy = new Proxy(accessor, handler);
+
+obj = {};
+Object.defineProperty(obj, "foo", {get: proxy, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo() {}})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {set: proxy, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo() {}})");
+
+// Methods from other global.
+// Treated as normal property.
+
+let g = newGlobal();
+
+method = g.eval("({ foo() {} }).foo");
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: method, enumerable: true});
+assertEq(obj.toSource(),
+         "({foo:foo() {}})");
+
+// Accessors from other global.
+// Accessor syntax is composed.
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ get foo() {} }, 'foo').get");
+
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo() {}})");
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ get bar() {} }, 'bar').get");
+
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo() {}})");
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ set foo(v) {} }, 'foo').set");
+
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo(v) {}})");
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ set bar(v) {} }, 'bar').set");
+
+obj = {};
+Object.defineProperty(obj, "foo", {get: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo(v) {}})");
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ get foo() {} }, 'foo').get");
+
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo() {}})");
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ get bar() {} }, 'bar').get");
+
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo() {}})");
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ set foo(v) {} }, 'foo').set");
+
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo(v) {}})");
+
+accessor = g.eval("Object.getOwnPropertyDescriptor({ set bar(v) {} }, 'bar').set");
+
+obj = {};
+Object.defineProperty(obj, "foo", {set: accessor, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo(v) {}})");
+
+// **** Some weird cases ****
+
+// Accessors with generator or async.
+
+obj = {};
+Object.defineProperty(obj, "foo", {get: function*() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({get foo() {}})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {set: async function() {}, enumerable: true});
+assertEq(obj.toSource(),
+         "({set foo() {}})");
+
+// Modified toSource.
+
+obj = { foo() {} };
+obj.foo.toSource = () => "hello";
+assertEq(obj.toSource(),
+         "({hello})");
+
+obj = { foo() {} };
+obj.foo.toSource = () => "bar() {}";
+assertEq(obj.toSource(),
+         "({bar() {}})");
+
+// Modified toSource with different method name.
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: function bar() {}, enumerable: true});
+obj.foo.toSource = () => "hello";
+assertEq(obj.toSource(),
+         "({foo:hello})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: function* bar() {}, enumerable: true});
+obj.foo.toSource = () => "hello";
+assertEq(obj.toSource(),
+         "({foo:hello})");
+
+obj = {};
+Object.defineProperty(obj, "foo", {value: async function bar() {}, enumerable: true});
+obj.foo.toSource = () => "hello";
+assertEq(obj.toSource(),
+         "({foo:hello})");
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
new file mode 100644
new file mode 100644