Bug 1054630 - Collect telemetry on usage of SpiderMonkey's deprecated language extensions: for-each, destructuring for-in, legacy generators, and expression closures. r=till
authorChris Peterson <cpeterson@mozilla.com>
Sun, 24 Aug 2014 11:56:08 -0700
changeset 201258 efece5e33d8252b42104dc435d82516119d43d3f
parent 201257 eb09f73f2582d99dff99cdd6d497dc311772345d
child 201259 277bf1aa01075c9c0f0a077c1e7990e6fd94ea85
push id48135
push usercpeterson@mozilla.com
push dateSun, 24 Aug 2014 20:49:58 +0000
treeherdermozilla-inbound@efece5e33d82 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerstill
bugs1054630
milestone34.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1054630 - Collect telemetry on usage of SpiderMonkey's deprecated language extensions: for-each, destructuring for-in, legacy generators, and expression closures. r=till
browser/base/content/abouthome/aboutHome.js
browser/base/content/browser.xul
js/src/frontend/Parser.cpp
js/src/frontend/Parser.h
js/src/jsfriendapi.h
js/xpconnect/src/XPCJSRuntime.cpp
toolkit/components/telemetry/Histograms.json
--- a/browser/base/content/abouthome/aboutHome.js
+++ b/browser/base/content/abouthome/aboutHome.js
@@ -1,12 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * 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/. */
 
+"use strict";
+
 const SEARCH_ENGINES = {
   "Google": {
     // This is the "2x" image designed for OS X retina resolution, Windows at 192dpi, etc.;
     // it will be scaled down as necessary on lower-dpi displays.
     // This needs to be defined in a single line to keep the JS parser from creating many
     // intermediate strings in memory.  See bug 986672.
     image: "data:image/png;base64,\
 iVBORw0KGgoAAAANSUhEUgAAAIwAAAA4CAYAAAAvmxBdAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJ\
@@ -264,34 +266,34 @@ function ensureSnippetsMapThen(aCallback
       if (cursor) {
         cache.set(cursor.key, cursor.value);
         cursor.continue();
         return;
       }
 
       // The cache has been filled up, create the snippets map.
       gSnippetsMap = Object.freeze({
-        get: function (aKey) cache.get(aKey),
+        get: (aKey) => cache.get(aKey),
         set: function (aKey, aValue) {
           db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
             .objectStore(SNIPPETS_OBJECTSTORE_NAME).put(aValue, aKey);
           return cache.set(aKey, aValue);
         },
-        has: function (aKey) cache.has(aKey),
+        has: (aKey) => cache.has(aKey),
         delete: function (aKey) {
           db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
             .objectStore(SNIPPETS_OBJECTSTORE_NAME).delete(aKey);
           return cache.delete(aKey);
         },
         clear: function () {
           db.transaction(SNIPPETS_OBJECTSTORE_NAME, "readwrite")
             .objectStore(SNIPPETS_OBJECTSTORE_NAME).clear();
           return cache.clear();
         },
-        get size() cache.size
+        get size() { return cache.size; },
       });
 
       setTimeout(invokeCallbacks, 0);
     }
   }
 }
 
 function onSearchSubmit(aEvent)
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -707,17 +707,17 @@
                      enablehistory="true"
                      maxrows="6"
                      newlines="stripsurroundingwhitespace"
                      oninput="gBrowser.userTypedValue = this.value;"
                      ontextentered="this.handleCommand(param);"
                      ontextreverted="return this.handleRevert();"
                      pageproxystate="invalid"
                      onfocus="document.getElementById('identity-box').style.MozUserFocus= 'normal'"
-                     onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);">
+                     onblur="setTimeout(() => { document.getElementById('identity-box').style.MozUserFocus = ''; }, 0);">
               <box id="notification-popup-box" hidden="true" align="center">
                 <image id="default-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="identity-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/>
--- a/js/src/frontend/Parser.cpp
+++ b/js/src/frontend/Parser.cpp
@@ -425,16 +425,20 @@ Parser<ParseHandler>::Parser(ExclusiveCo
     traceListHead(nullptr),
     pc(nullptr),
     sct(nullptr),
     ss(nullptr),
     keepAtoms(cx->perThreadData),
     foldConstants(foldConstants),
     abortedSyntaxParse(false),
     isUnexpectedEOF_(false),
+    sawDeprecatedForEach(false),
+    sawDeprecatedDestructuringForIn(false),
+    sawDeprecatedLegacyGenerator(false),
+    sawDeprecatedExpressionClosure(false),
     handler(cx, *alloc, tokenStream, foldConstants, syntaxParser, lazyOuterFunction)
 {
     {
         AutoLockForExclusiveAccess lock(cx);
         cx->perThreadData->addActiveCompilation();
     }
 
     // The Mozilla specific JSOPTION_EXTRA_WARNINGS option adds extra warnings
@@ -444,16 +448,18 @@ Parser<ParseHandler>::Parser(ExclusiveCo
         handler.disableSyntaxParser();
 
     tempPoolMark = alloc->mark();
 }
 
 template <typename ParseHandler>
 Parser<ParseHandler>::~Parser()
 {
+    accumulateTelemetry();
+
     alloc.release(tempPoolMark);
 
     /*
      * The parser can allocate enormous amounts of memory for large functions.
      * Eagerly free the memory now (which otherwise won't be freed until the
      * next GC) to avoid unnecessary OOMs.
      */
     alloc.freeAllIfHugeAndUnused();
@@ -2254,16 +2260,20 @@ Parser<ParseHandler>::functionArgsAndBod
 
     // Parse the function body.
     FunctionBodyType bodyType = StatementListBody;
     if (tokenStream.getToken(TokenStream::Operand) != TOK_LC) {
         if (funbox->isStarGenerator()) {
             report(ParseError, false, null(), JSMSG_CURLY_BEFORE_BODY);
             return false;
         }
+
+        if (kind != Arrow)
+            sawDeprecatedExpressionClosure = true;
+
         tokenStream.ungetToken();
         bodyType = ExpressionBody;
         fun->setIsExprClosure();
     }
 
     Node body = functionBody(kind, bodyType);
     if (!body)
         return false;
@@ -4078,16 +4088,17 @@ Parser<FullParseHandler>::forStatement()
     PushStatementPC(pc, &forStmt, STMT_FOR_LOOP);
 
     bool isForEach = false;
     unsigned iflags = 0;
 
     if (allowsForEachIn() && tokenStream.matchContextualKeyword(context->names().each)) {
         iflags = JSITER_FOREACH;
         isForEach = true;
+        sawDeprecatedForEach = true;
     }
 
     MUST_MATCH_TOKEN(TOK_LP, JSMSG_PAREN_AFTER_FOR);
 
     /*
      * True if we have 'for (var/let/const ...)', except in the oddball case
      * where 'let' begins a let-expression in 'for (let (...) ...)'.
      */
@@ -4295,18 +4306,20 @@ Parser<FullParseHandler>::forStatement()
 
           case PNK_ARRAY:
           case PNK_OBJECT:
             if (versionNumber() == JSVERSION_1_7) {
                 /*
                  * Destructuring for-in requires [key, value] enumeration
                  * in JS1.7.
                  */
-                if (!isForEach && headKind == PNK_FORIN)
+                if (!isForEach && headKind == PNK_FORIN) {
                     iflags |= JSITER_FOREACH | JSITER_KEYVALUE;
+                    sawDeprecatedDestructuringForIn = true;
+                }
             }
             break;
 
           default:;
         }
     } else {
         if (isForEach) {
             reportWithOffset(ParseError, false, begin, JSMSG_BAD_FOR_EACH_LOOP);
@@ -4798,16 +4811,17 @@ Parser<ParseHandler>::yieldExpression()
             return null();
 
         if (!pc->sc->isFunctionBox()) {
             report(ParseError, false, null(), JSMSG_BAD_RETURN_OR_YIELD, js_yield_str);
             return null();
         }
 
         pc->sc->asFunctionBox()->setGeneratorKind(LegacyGenerator);
+        sawDeprecatedLegacyGenerator = true;
 
         if (pc->funHasReturnExpr) {
             /* As in Python (see PEP-255), disallow return v; in generators. */
             reportBadReturn(null(), ParseError, JSMSG_BAD_GENERATOR_RETURN,
                             JSMSG_BAD_ANON_GENERATOR_RETURN);
             return null();
         }
         // Fall through.
@@ -7568,13 +7582,54 @@ Parser<ParseHandler>::exprInParens()
             return null();
         handler.setBeginPosition(pn, begin);
     }
 #endif /* JS_HAS_GENERATOR_EXPRS */
 
     return pn;
 }
 
+template <typename ParseHandler>
+void
+Parser<ParseHandler>::accumulateTelemetry()
+{
+    JSContext* cx = context->maybeJSContext();
+    if (!cx)
+        return;
+    JSAccumulateTelemetryDataCallback cb = cx->runtime()->telemetryCallback;
+    if (!cb)
+        return;
+    const char* filename = getFilename();
+    if (!filename)
+        return;
+
+    bool isHTTP = strncmp(filename, "http://", 7) == 0 || strncmp(filename, "https://", 8) == 0;
+
+    // Only report telemetry for web content, not add-ons or chrome JS.
+    if (!isHTTP)
+        return;
+
+    enum DeprecatedLanguageExtensions {
+        DeprecatedForEach = 0,            // JS 1.6+
+        DeprecatedDestructuringForIn = 1, // JS 1.7 only
+        DeprecatedLegacyGenerator = 2,    // JS 1.7+
+        DeprecatedExpressionClosure = 3,  // Added in JS 1.8, but not version-gated
+    };
+
+    // Hazard analysis can't tell that the telemetry callbacks don't GC.
+    JS::AutoSuppressGCAnalysis nogc;
+
+    // Call back into Firefox's Telemetry reporter.
+    if (sawDeprecatedForEach)
+        (*cb)(JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, DeprecatedForEach);
+    if (sawDeprecatedDestructuringForIn)
+        (*cb)(JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, DeprecatedDestructuringForIn);
+    if (sawDeprecatedLegacyGenerator)
+        (*cb)(JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, DeprecatedLegacyGenerator);
+    if (sawDeprecatedExpressionClosure)
+        (*cb)(JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, DeprecatedExpressionClosure);
+}
+
 template class Parser<FullParseHandler>;
 template class Parser<SyntaxParseHandler>;
 
 } /* namespace frontend */
 } /* namespace js */
--- a/js/src/frontend/Parser.h
+++ b/js/src/frontend/Parser.h
@@ -345,16 +345,22 @@ class Parser : private JS::AutoGCRooter,
      * is not known whether the parse succeeds or fails, this bit is set and
      * the parse will return false.
      */
     bool abortedSyntaxParse:1;
 
     /* Unexpected end of input, i.e. TOK_EOF not at top-level. */
     bool isUnexpectedEOF_:1;
 
+    /* Used for collecting telemetry on SpiderMonkey's deprecated language extensions. */
+    bool sawDeprecatedForEach:1;
+    bool sawDeprecatedDestructuringForIn:1;
+    bool sawDeprecatedLegacyGenerator:1;
+    bool sawDeprecatedExpressionClosure:1;
+
     typedef typename ParseHandler::Node Node;
     typedef typename ParseHandler::DefinitionNode DefinitionNode;
 
   public:
     /* State specific to the kind of parse being performed. */
     ParseHandler handler;
 
   private:
@@ -653,16 +659,18 @@ class Parser : private JS::AutoGCRooter,
 
     bool leaveFunction(Node fn, ParseContext<ParseHandler> *outerpc,
                        FunctionSyntaxKind kind = Expression);
 
     TokenPos pos() const { return tokenStream.currentToken().pos; }
 
     bool asmJS(Node list);
 
+    void accumulateTelemetry();
+
     friend class LegacyCompExprTransplanter;
     friend struct BindData<ParseHandler>;
 };
 
 /* Declare some required template specializations. */
 
 template <>
 bool
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -114,17 +114,18 @@ enum {
     JS_TELEMETRY_GC_MARK_ROOTS_MS,
     JS_TELEMETRY_GC_MARK_GRAY_MS,
     JS_TELEMETRY_GC_SLICE_MS,
     JS_TELEMETRY_GC_MMU_50,
     JS_TELEMETRY_GC_RESET,
     JS_TELEMETRY_GC_INCREMENTAL_DISABLED,
     JS_TELEMETRY_GC_NON_INCREMENTAL,
     JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS,
-    JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS
+    JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS,
+    JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT
 };
 
 typedef void
 (* JSAccumulateTelemetryDataCallback)(int id, uint32_t sample);
 
 extern JS_FRIEND_API(void)
 JS_SetAccumulateTelemetryCallback(JSRuntime *rt, JSAccumulateTelemetryDataCallback callback);
 
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -2924,16 +2924,22 @@ AccumulateTelemetryCallback(int id, uint
         Telemetry::Accumulate(Telemetry::GC_NON_INCREMENTAL, sample);
         break;
       case JS_TELEMETRY_GC_SCC_SWEEP_TOTAL_MS:
         Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_TOTAL_MS, sample);
         break;
       case JS_TELEMETRY_GC_SCC_SWEEP_MAX_PAUSE_MS:
         Telemetry::Accumulate(Telemetry::GC_SCC_SWEEP_MAX_PAUSE_MS, sample);
         break;
+      case JS_TELEMETRY_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT:
+        MOZ_ASSERT(sample <= 3);
+        Telemetry::Accumulate(Telemetry::JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT, sample);
+        break;
+      default:
+        MOZ_ASSERT_UNREACHABLE("Unexpected JS_TELEMETRY id");
     }
 }
 
 static void
 CompartmentNameCallback(JSRuntime *rt, JSCompartment *comp,
                         char *buf, size_t bufsize)
 {
     nsCString name;
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -313,16 +313,22 @@
     "n_buckets": 200,
     "description": "Location accuracy"
   },
   "GEOLOCATION_ERROR": {
     "expires_in_version": "never",
     "kind": "flag",
     "description": "Has seen location error"
   },
+  "JS_DEPRECATED_LANGUAGE_EXTENSIONS_IN_CONTENT": {
+    "expires_in_version": "never",
+    "kind": "enumerated",
+    "n_values": 10,
+    "description": "Use of SpiderMonkey's deprecated language extensions in web content: ForEach, DestructuringForIn, LegacyGenerator, ExpressionClosure"
+  },
   "TELEMETRY_PING": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": "3000",
     "n_buckets": 10,
     "extended_statistics_ok": true,
     "description": "Time taken to submit telemetry info (ms)"
   },