Bug 1150297 - Support RegExp source property in Xray. r=bholley, a=lmandel
authorTooru Fujisawa <arai_a@mac.com>
Wed, 17 Jun 2015 21:50:23 +0900
changeset 275500 4e36b11687f75fabd6f6e0e976e198fe48787d52
parent 275499 60c25113956ee5207d0fcd18d2b2ea3409146e98
child 275501 34b95d9ef07ef0dc6079d72b9eb40bb4701b9722
push id863
push userraliiev@mozilla.com
push dateMon, 03 Aug 2015 13:22:43 +0000
treeherdermozilla-release@f6321b14228d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbholley, lmandel
bugs1150297
milestone40.0
Bug 1150297 - Support RegExp source property in Xray. r=bholley, a=lmandel
browser/devtools/webconsole/test/browser_webconsole_output_regexp.js
browser/devtools/webconsole/test/test-console-output-regexp.html
js/xpconnect/src/XPCJSRuntime.cpp
js/xpconnect/src/xpcprivate.h
js/xpconnect/tests/chrome/test_xrayToJS.xul
js/xpconnect/wrappers/XrayWrapper.cpp
--- a/browser/devtools/webconsole/test/browser_webconsole_output_regexp.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_regexp.js
@@ -9,17 +9,17 @@ const TEST_URI = "http://example.com/bro
 
 let dateNow = Date.now();
 
 let inputTests = [
   // 0
   {
     input: "/foo/igym",
     output: "/foo/gimy",
-    printOutput: "Error: source called",
+    printOutput: "Error: flags called",
     inspectable: true,
   },
 ];
 
 function test() {
   requestLongerTimeout(2);
   Task.spawn(function*() {
     let {tab} = yield loadTab(TEST_URI);
--- a/browser/devtools/webconsole/test/test-console-output-regexp.html
+++ b/browser/devtools/webconsole/test/test-console-output-regexp.html
@@ -10,14 +10,11 @@
 </head>
 <body>
   <p>hello world!</p>
 
   <script type="text/javascript">
 Object.defineProperty(RegExp.prototype, "flags", {
   get: function() { throw Error("flags called"); }
 })
-Object.defineProperty(RegExp.prototype, "source", {
-  get: function() { throw Error("source called"); },
-})
   </script>
 </body>
 </html>
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -87,17 +87,18 @@ const char* const XPCJSRuntime::mStrings
     "name",                 // IDX_NAME
     "undefined",            // IDX_UNDEFINED
     "",                     // IDX_EMPTYSTRING
     "fileName",             // IDX_FILENAME
     "lineNumber",           // IDX_LINENUMBER
     "columnNumber",         // IDX_COLUMNNUMBER
     "stack",                // IDX_STACK
     "message",              // IDX_MESSAGE
-    "lastIndex"             // IDX_LASTINDEX
+    "lastIndex",            // IDX_LASTINDEX
+    "source"                // IDX_SOURCE
 };
 
 /***************************************************************************/
 
 static mozilla::Atomic<bool> sDiscardSystemSource(false);
 
 bool
 xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
--- a/js/xpconnect/src/xpcprivate.h
+++ b/js/xpconnect/src/xpcprivate.h
@@ -534,16 +534,17 @@ public:
         IDX_UNDEFINED               ,
         IDX_EMPTYSTRING             ,
         IDX_FILENAME                ,
         IDX_LINENUMBER              ,
         IDX_COLUMNNUMBER            ,
         IDX_STACK                   ,
         IDX_MESSAGE                 ,
         IDX_LASTINDEX               ,
+        IDX_SOURCE                  ,
         IDX_TOTAL_COUNT // just a count of the above
     };
 
     JS::HandleId GetStringID(unsigned index) const
     {
         MOZ_ASSERT(index < IDX_TOTAL_COUNT, "index out of range");
         // fromMarkedLocation() is safe because the string is interned.
         return JS::HandleId::fromMarkedLocation(&mStrIDs[index]);
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -611,17 +611,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 
       isnot(re.toSource, Cu.unwaiveXrays(re.wrappedJSObject.toSource), "Different function identities");
       is(Cu.getGlobalForObject(re.toSource), window, "Xray global is correct");
       is(Cu.getGlobalForObject(re.wrappedJSObject.toSource), iwin, "Underlying global is correct");
       is(re.toSource(), re.wrappedJSObject.toSource(), "Results match");
 
       // Test with modified flags accessors
       iwin.eval(`
-var props = ["global", "ignoreCase", "multiline", "sticky", "source"];
+var props = ["global", "ignoreCase", "multiline", "sticky"];
 var origDescs = {};
 for (var prop of props) {
   origDescs[prop] = Object.getOwnPropertyDescriptor(RegExp.prototype, prop);
   Object.defineProperty(RegExp.prototype, prop, {
     get: function() {
       throw new Error("modified accessor is called");
     }
   });
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -451,16 +451,34 @@ JSXrayTraits::resolveOwnProperty(JSConte
                                         (isErrorStringProperty && desc.value().isString());
                 if (desc.hasGetterOrSetter() || !valueMatchesType)
                     FillPropertyDescriptor(desc, nullptr, 0, UndefinedValue());
                 return true;
             }
         } else if (key == JSProto_RegExp) {
             if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_LASTINDEX))
                 return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc);
+            if (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_SOURCE)) {
+                // Given that 'source' property is a non-configurable string,
+                // always exists both in prototype and instance, and they have
+                // no relation, we can get the own property safely, regardless
+                // of the *shadowing*.  Validate here to avoid adding extra
+                // complication to the logic of getOwnPropertyFromTargetIfSafe.
+                {
+                    JSAutoCompartment ac(cx, target);
+                    if (!JS_GetOwnPropertyDescriptorById(cx, target, id, desc))
+                        return false;
+
+                    MOZ_ASSERT(desc.object());
+                    MOZ_ASSERT(!desc.hasGetterOrSetter());
+                    MOZ_ASSERT(!desc.configurable());
+                    MOZ_RELEASE_ASSERT(desc.value().isString());
+                }
+                return JS_WrapPropertyDescriptor(cx, desc);
+            }
         }
 
         // The rest of this function applies only to prototypes.
         return true;
     }
 
     // The non-HasPrototypes semantics implemented by traditional Xrays are kind
     // of broken with respect to |own|-ness and the holder. The common code
@@ -495,19 +513,23 @@ JSXrayTraits::resolveOwnProperty(JSConte
     // Handle the 'name' property for error prototypes.
     if (IsErrorObjectKey(key) && id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_NAME)) {
         RootedId className(cx);
         ProtoKeyToId(cx, key, &className);
         FillPropertyDescriptor(desc, wrapper, 0, UndefinedValue());
         return JS_IdToValue(cx, className, desc.value());
     }
 
-    // Handle the 'lastIndex' property for RegExp prototypes.
-    if (key == JSProto_RegExp && id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_LASTINDEX))
+    // Handle the 'lastIndex' and 'source' properties for RegExp prototypes.
+    if (key == JSProto_RegExp &&
+        (id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_LASTINDEX) ||
+         id == GetRTIdByIndex(cx, XPCJSRuntime::IDX_SOURCE)))
+    {
         return getOwnPropertyFromWrapperIfSafe(cx, wrapper, id, desc);
+    }
 
     // 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. Indexed array properties are handled above.
     const JSFunctionSpec* fsMatch = nullptr;
     for (const JSFunctionSpec* fs = clasp->spec.prototypeFunctions; fs && fs->name; ++fs) {
@@ -741,35 +763,42 @@ JSXrayTraits::enumerateNames(JSContext* 
                 !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_LINENUMBER)) ||
                 !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_COLUMNNUMBER)) ||
                 !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_STACK)) ||
                 !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_MESSAGE)))
             {
                 return false;
             }
         } else if (key == JSProto_RegExp) {
-            if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_LASTINDEX)))
+            if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_LASTINDEX)) ||
+                !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_SOURCE)))
+            {
                 return false;
+            }
         }
 
         // The rest of this function applies only to prototypes.
         return true;
     }
 
     // Add the 'constructor' property.
     if (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_CONSTRUCTOR)))
         return false;
 
     // For Error protoypes, add the 'name' property.
     if (IsErrorObjectKey(key) && !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_NAME)))
         return false;
 
-    // For RegExp protoypes, add the 'lastIndex' property.
-    if (key == JSProto_RegExp && !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_LASTINDEX)))
+    // For RegExp protoypes, add the 'lastIndex' and 'source' properties.
+    if (key == JSProto_RegExp &&
+        (!props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_LASTINDEX)) ||
+         !props.append(GetRTIdByIndex(cx, XPCJSRuntime::IDX_SOURCE))))
+    {
         return false;
+    }
 
     // Grab the JSClass. We require all Xrayable classes to have a ClassSpec.
     const js::Class* clasp = js::GetObjectClass(target);
     MOZ_ASSERT(clasp->spec.defined());
 
     // 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) {
         jsid id;