Bug 1319926 - Part 1: Warn when deprecated String generics methods are used. r=jandem
authorAndré Bargull <andre.bargull@gmail.com>
Thu, 24 Nov 2016 08:58:02 -0800
changeset 325586 07d6bf74b7a2552da91b5e2fce0fa0bc3b457394
parent 325585 e83eaf2380c65400dc03c6f3615d4b2cef669af3
child 325587 5a110ad242ead60e71d2186bae78b1fb766ad5ff
push id31061
push userphilringnalda@gmail.com
push dateSat, 10 Dec 2016 16:28:08 +0000
treeherdermozilla-central@c51e7406d7b2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1319926
milestone53.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 1319926 - Part 1: Warn when deprecated String generics methods are used. r=jandem
js/src/builtin/SelfHostingDefines.h
js/src/builtin/String.js
js/src/js.msg
js/src/jscompartment.cpp
js/src/jscompartment.h
js/src/tests/js1_6/String/generics-deprecated.js
js/src/vm/SelfHosting.cpp
--- a/js/src/builtin/SelfHostingDefines.h
+++ b/js/src/builtin/SelfHostingDefines.h
@@ -95,9 +95,35 @@
 
 #define MODULE_OBJECT_ENVIRONMENT_SLOT 2
 
 #define MODULE_STATE_FAILED       0
 #define MODULE_STATE_PARSED       1
 #define MODULE_STATE_INSTANTIATED 2
 #define MODULE_STATE_EVALUATED    3
 
+#define STRING_GENERICS_CHAR_AT               0
+#define STRING_GENERICS_CHAR_CODE_AT          1
+#define STRING_GENERICS_CONCAT                2
+#define STRING_GENERICS_ENDS_WITH             3
+#define STRING_GENERICS_INCLUDES              4
+#define STRING_GENERICS_INDEX_OF              5
+#define STRING_GENERICS_LAST_INDEX_OF         6
+#define STRING_GENERICS_LOCALE_COMPARE        7
+#define STRING_GENERICS_MATCH                 8
+#define STRING_GENERICS_NORMALIZE             9
+#define STRING_GENERICS_REPLACE               10
+#define STRING_GENERICS_SEARCH                11
+#define STRING_GENERICS_SLICE                 12
+#define STRING_GENERICS_SPLIT                 13
+#define STRING_GENERICS_STARTS_WITH           14
+#define STRING_GENERICS_SUBSTR                15
+#define STRING_GENERICS_SUBSTRING             16
+#define STRING_GENERICS_TO_LOWER_CASE         17
+#define STRING_GENERICS_TO_LOCALE_LOWER_CASE  18
+#define STRING_GENERICS_TO_LOCALE_UPPER_CASE  19
+#define STRING_GENERICS_TO_UPPER_CASE         20
+#define STRING_GENERICS_TRIM                  21
+#define STRING_GENERICS_TRIM_LEFT             22
+#define STRING_GENERICS_TRIM_RIGHT            23
+#define STRING_GENERICS_METHODS_LIMIT         24
+
 #endif
--- a/js/src/builtin/String.js
+++ b/js/src/builtin/String.js
@@ -53,16 +53,17 @@ function String_match(regexp) {
     if (IsStringMatchOptimizable())
         return RegExpMatcher(rx, S, 0);
 
     // Step 5.
     return callContentFunction(GetMethod(rx, std_match), rx, S);
 }
 
 function String_generic_match(thisValue, regexp) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_MATCH, 'match');
     if (thisValue === undefined)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.match');
     return callFunction(String_match, thisValue, regexp);
 }
 
 /**
  * A helper function implementing the logic for both String.prototype.padStart
  * and String.prototype.padEnd as described in ES7 Draft March 29, 2016
@@ -187,16 +188,17 @@ function String_replace(searchValue, rep
     if (tailPos < stringLength)
         newString += Substring(string, tailPos, stringLength - tailPos);
 
     // Step 12.
     return newString;
 }
 
 function String_generic_replace(thisValue, searchValue, replaceValue) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_REPLACE, 'replace');
     if (thisValue === undefined)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.replace');
     return callFunction(String_replace, thisValue, searchValue, replaceValue);
 }
 
 function StringProtoHasNoSearch() {
     var ObjectProto = GetBuiltinPrototype("Object");
     var StringProto = GetBuiltinPrototype("String");
@@ -242,16 +244,17 @@ function String_search(regexp) {
     // Step 4.
     var rx = RegExpCreate(regexp);
 
     // Step 5.
     return callContentFunction(GetMethod(rx, std_search), rx, string);
 }
 
 function String_generic_search(thisValue, regexp) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_SEARCH, 'search');
     if (thisValue === undefined)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.search');
     return callFunction(String_search, thisValue, regexp);
 }
 
 function StringProtoHasNoSplit() {
     var ObjectProto = GetBuiltinPrototype("Object");
     var StringProto = GetBuiltinPrototype("String");
@@ -323,16 +326,17 @@ function String_split(separator, limit) 
         return [S];
 
     // Optimized path.
     // Steps 4, 8, 12-18.
     return StringSplitString(S, R);
 }
 
 function String_generic_split(thisValue, separator, limit) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_SPLIT, 'split');
     if (thisValue === undefined)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.split');
     return callFunction(String_split, thisValue, separator, limit);
 }
 
 /* ES6 Draft Oct 14, 2014 21.1.3.19 */
 function String_substring(start, end) {
     // Steps 1-3.
@@ -367,16 +371,17 @@ function String_substring(start, end) {
     // Step 11.
     // While |from| and |to - from| are bounded to the length of |str| and this
     // and thus definitely in the int32 range, they can still be typed as
     // double. Eagerly truncate since SubstringKernel only accepts int32.
     return SubstringKernel(str, from | 0, (to - from) | 0);
 }
 
 function String_static_substring(string, start, end) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_SUBSTRING, 'substring');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.substring');
     return callFunction(String_substring, string, start, end);
 }
 
 /* ES6 Draft Oct 14, 2014 B.2.3.1 */
 function String_substr(start, length) {
     // Steps 1-2.
@@ -406,16 +411,17 @@ function String_substr(start, length) {
     // Step 11.
     // While |intStart| and |resultLength| are bounded to the length of |str|
     // and thus definitely in the int32 range, they can still be typed as
     // double. Eagerly truncate since SubstringKernel only accepts int32.
     return SubstringKernel(str, intStart | 0, resultLength | 0);
 }
 
 function String_static_substr(string, start, length) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_SUBSTR, 'substr');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.substr');
     return callFunction(String_substr, string, start, length);
 }
 
 /* ES6 Draft Oct 14, 2014 21.1.3.16 */
 function String_slice(start, end) {
     // Steps 1-3.
@@ -443,16 +449,17 @@ function String_slice(start, end) {
     // Step 10.
     // While |from| and |span| are bounded to the length of |str|
     // and thus definitely in the int32 range, they can still be typed as
     // double. Eagerly truncate since SubstringKernel only accepts int32.
     return SubstringKernel(str, from | 0, span | 0);
 }
 
 function String_static_slice(string, start, end) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_SLICE, 'slice');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.slice');
     return callFunction(String_slice, string, start, end);
 }
 
 /* ES6 Draft September 5, 2013 21.1.3.3 */
 function String_codePointAt(pos) {
     // Steps 1-3.
@@ -650,16 +657,17 @@ function String_static_raw(callSite, ...
 /**
  * Compare String str1 against String str2, using the locale and collation
  * options provided.
  *
  * Mozilla proprietary.
  * Spec: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/String#String_generic_methods
  */
 function String_static_localeCompare(str1, str2) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_LOCALE_COMPARE, 'localeCompare');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, "String.localeCompare");
     var locales = arguments.length > 2 ? arguments[2] : undefined;
     var options = arguments.length > 3 ? arguments[3] : undefined;
 #if EXPOSE_INTL_API
     return callFunction(String_localeCompare, str1, str2, locales, options);
 #else
     return callFunction(std_String_localeCompare, str1, str2, locales, options);
@@ -762,111 +770,127 @@ function String_fontsize(size) {
 // ES6 draft 2014-04-27 B.2.3.10
 function String_link(url) {
     RequireObjectCoercible(this);
     var S = ToString(this);
     return '<a href="' + EscapeAttributeValue(url) + '">' + S + "</a>";
 }
 
 function String_static_toLowerCase(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_TO_LOWER_CASE, 'toLowerCase');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLowerCase');
     return callFunction(std_String_toLowerCase, string);
 }
 
 function String_static_toUpperCase(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_TO_UPPER_CASE, 'toUpperCase');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toUpperCase');
     return callFunction(std_String_toUpperCase, string);
 }
 
 function String_static_charAt(string, pos) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_CHAR_AT, 'charAt');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.charAt');
     return callFunction(std_String_charAt, string, pos);
 }
 
 function String_static_charCodeAt(string, pos) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_CHAR_CODE_AT, 'charCodeAt');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.charCodeAt');
     return callFunction(std_String_charCodeAt, string, pos);
 }
 
 function String_static_includes(string, searchString) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_INCLUDES, 'includes');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.includes');
     var position = arguments.length > 2 ? arguments[2] : undefined;
     return callFunction(std_String_includes, string, searchString, position);
 }
 
 function String_static_indexOf(string, searchString) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_INDEX_OF, 'indexOf');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.indexOf');
     var position = arguments.length > 2 ? arguments[2] : undefined;
     return callFunction(std_String_indexOf, string, searchString, position);
 }
 
 function String_static_lastIndexOf(string, searchString) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_LAST_INDEX_OF, 'lastIndexOf');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.lastIndexOf');
     var position = arguments.length > 2 ? arguments[2] : undefined;
     return callFunction(std_String_lastIndexOf, string, searchString, position);
 }
 
 function String_static_startsWith(string, searchString) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_STARTS_WITH, 'startsWith');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.startsWith');
     var position = arguments.length > 2 ? arguments[2] : undefined;
     return callFunction(std_String_startsWith, string, searchString, position);
 }
 
 function String_static_endsWith(string, searchString) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_ENDS_WITH, 'endsWith');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.endsWith');
     var endPosition = arguments.length > 2 ? arguments[2] : undefined;
     return callFunction(std_String_endsWith, string, searchString, endPosition);
 }
 
 function String_static_trim(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_TRIM, 'trim');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trim');
     return callFunction(std_String_trim, string);
 }
 
 function String_static_trimLeft(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_TRIM_LEFT, 'trimLeft');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trimLeft');
     return callFunction(std_String_trimLeft, string);
 }
 
 function String_static_trimRight(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_TRIM_RIGHT, 'trimRight');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.trimRight');
     return callFunction(std_String_trimRight, string);
 }
 
 function String_static_toLocaleLowerCase(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_TO_LOCALE_LOWER_CASE, 'toLocaleLowerCase');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleLowerCase');
     return callFunction(std_String_toLocaleLowerCase, string);
 }
 
 function String_static_toLocaleUpperCase(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_TO_LOCALE_UPPER_CASE, 'toLocaleUpperCase');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.toLocaleUpperCase');
     return callFunction(std_String_toLocaleUpperCase, string);
 }
 
 #if EXPOSE_INTL_API
 function String_static_normalize(string) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_NORMALIZE, "normalize");
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.normalize');
     var form = arguments.length > 1 ? arguments[1] : undefined;
     return callFunction(std_String_normalize, string, form);
 }
 #endif
 
 function String_static_concat(string, arg1) {
+    WarnDeprecatedStringMethod(STRING_GENERICS_CONCAT, 'concat');
     if (arguments.length < 1)
         ThrowTypeError(JSMSG_MISSING_FUN_ARG, 0, 'String.concat');
     var args = callFunction(std_Array_slice, arguments, 1);
     return callFunction(std_Function_apply, std_String_concat, string, args);
 }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -128,16 +128,17 @@ MSG_DEF(JSMSG_INVALID_DATE,            0
 MSG_DEF(JSMSG_BAD_TOISOSTRING_PROP,    0, JSEXN_TYPEERR, "toISOString property is not callable")
 
 // String
 MSG_DEF(JSMSG_BAD_URI,                 0, JSEXN_URIERR, "malformed URI sequence")
 MSG_DEF(JSMSG_INVALID_NORMALIZE_FORM,  0, JSEXN_RANGEERR, "form must be one of 'NFC', 'NFD', 'NFKC', or 'NFKD'")
 MSG_DEF(JSMSG_NEGATIVE_REPETITION_COUNT, 0, JSEXN_RANGEERR, "repeat count must be non-negative")
 MSG_DEF(JSMSG_NOT_A_CODEPOINT,         1, JSEXN_RANGEERR, "{0} is not a valid code point")
 MSG_DEF(JSMSG_RESULTING_STRING_TOO_LARGE, 0, JSEXN_RANGEERR, "repeat count must be less than infinity and not overflow maximum string size")
+MSG_DEF(JSMSG_DEPRECATED_STRING_METHOD, 2, JSEXN_WARN, "String.{0} is deprecated; use String.prototype.{1} instead")
 
 // Number
 MSG_DEF(JSMSG_BAD_RADIX,               0, JSEXN_RANGEERR, "radix must be an integer at least 2 and no greater than 36")
 MSG_DEF(JSMSG_PRECISION_RANGE,         1, JSEXN_RANGEERR, "precision {0} out of range")
 
 // Function
 MSG_DEF(JSMSG_BAD_APPLY_ARGS,          1, JSEXN_TYPEERR, "second argument to Function.prototype.{0} must be an array")
 MSG_DEF(JSMSG_BAD_FORMAL,              0, JSEXN_SYNTAXERR, "malformed formal parameter")
--- a/js/src/jscompartment.cpp
+++ b/js/src/jscompartment.cpp
@@ -50,16 +50,17 @@ JSCompartment::JSCompartment(Zone* zone,
     runtime_(zone->runtimeFromMainThread()),
     principals_(nullptr),
     isSystem_(false),
     isAtomsCompartment_(false),
     isSelfHosting(false),
     marked(true),
     warnedAboutExprClosure(false),
     warnedAboutForEach(false),
+    warnedAboutStringGenericsMethods(0),
 #ifdef DEBUG
     firedOnNewGlobalObject(false),
 #endif
     global_(nullptr),
     enterCompartmentDepth(0),
     performanceMonitoring(runtime_),
     data(nullptr),
     allocationMetadataBuilder(nullptr),
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -356,16 +356,17 @@ struct JSCompartment
     bool                         isSystem_;
     bool                         isAtomsCompartment_;
 
   public:
     bool                         isSelfHosting;
     bool                         marked;
     bool                         warnedAboutExprClosure;
     bool                         warnedAboutForEach;
+    uint32_t                     warnedAboutStringGenericsMethods;
 
 #ifdef DEBUG
     bool                         firedOnNewGlobalObject;
 #endif
 
     void mark() { marked = true; }
 
   private:
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_6/String/generics-deprecated.js
@@ -0,0 +1,63 @@
+// |reftest| skip-if(!xulRuntime.shell)
+/* 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/. */
+
+// Warn once for each String generics method.
+
+var methods = {
+    charAt: ["", 0],
+    charCodeAt: ["", 0],
+    concat: ["", ""],
+    endsWith: ["", ""],
+    includes: ["", ""],
+    indexOf: ["", ""],
+    lastIndexOf: ["", ""],
+    localeCompare: ["", ""],
+    match: ["", ""],
+    normalize: [""],
+    replace: ["", ""],
+    search: ["", ""],
+    slice: ["", 0],
+    split: ["", ""],
+    startsWith: ["", ""],
+    substr: ["", 0],
+    substring: ["", 0],
+    toLowerCase: [""],
+    toLocaleLowerCase: [""],
+    toLocaleUpperCase: [""],
+    toUpperCase: [""],
+    trim: [""],
+    trimLeft: [""],
+    trimRight: [""]
+};
+
+for (var name in methods) {
+    var args = methods[name];
+
+    // String.normalize not available on every platform.
+    if (name === "normalize" && !(name in String.prototype))
+        continue;
+
+    enableLastWarning();
+
+    String[name].apply(null, args);
+
+    var warning = getLastWarning();
+    assertEq(warning !== null, true, "warning should be emitted for " + name);
+    assertEq(warning.name, "Warning");
+    assertEq(warning.message.indexOf(name) !== -1, true,
+             "warning should mention " + name);
+
+    clearLastWarning();
+
+    String[name].apply(null, args);
+
+    warning = getLastWarning();
+    assertEq(warning, null, "warning shouldn't be emitted for 2nd call to " + name);
+
+    disableLastWarning();
+}
+
+if (typeof reportCompare === 'function')
+    reportCompare(0, 0);
--- a/js/src/vm/SelfHosting.cpp
+++ b/js/src/vm/SelfHosting.cpp
@@ -1900,16 +1900,50 @@ intrinsic_AddContentTelemetry(JSContext*
     if (!cx->compartment()->isProbablySystemOrAddonCode())
         cx->runtime()->addTelemetry(id, args[1].toInt32());
 
     args.rval().setUndefined();
     return true;
 }
 
 static bool
+intrinsic_WarnDeprecatedStringMethod(JSContext* cx, unsigned argc, Value* vp)
+{
+    CallArgs args = CallArgsFromVp(argc, vp);
+    MOZ_ASSERT(args.length() == 2);
+    MOZ_ASSERT(args[0].isInt32());
+    MOZ_ASSERT(args[1].isString());
+
+    uint32_t id = uint32_t(args[0].toInt32());
+    MOZ_ASSERT(id < STRING_GENERICS_METHODS_LIMIT);
+
+    uint32_t mask = (1 << id);
+    if (!(cx->compartment()->warnedAboutStringGenericsMethods & mask)) {
+        JSFlatString* name = args[1].toString()->ensureFlat(cx);
+        if (!name)
+            return false;
+
+        AutoStableStringChars stableChars(cx);
+        if (!stableChars.initTwoByte(cx, name))
+            return false;
+        const char16_t* nameChars = stableChars.twoByteRange().begin().get();
+
+        if (!JS_ReportErrorFlagsAndNumberUC(cx, JSREPORT_WARNING, GetErrorMessage, nullptr,
+                                            JSMSG_DEPRECATED_STRING_METHOD, nameChars, nameChars))
+        {
+            return false;
+        }
+        cx->compartment()->warnedAboutStringGenericsMethods |= mask;
+    }
+
+    args.rval().setUndefined();
+    return true;
+}
+
+static bool
 intrinsic_ConstructFunction(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     MOZ_ASSERT(args.length() == 3);
     MOZ_ASSERT(IsConstructor(args[0]));
     MOZ_ASSERT(IsConstructor(args[1]));
     MOZ_ASSERT(args[2].toObject().is<ArrayObject>());
 
@@ -2508,16 +2542,17 @@ static const JSFunctionSpec intrinsic_fu
 
     JS_FN("FlatStringMatch", FlatStringMatch, 2,0),
     JS_FN("FlatStringSearch", FlatStringSearch, 2,0),
     JS_INLINABLE_FN("StringReplaceString", intrinsic_StringReplaceString, 3, 0,
                     IntrinsicStringReplaceString),
     JS_INLINABLE_FN("StringSplitString", intrinsic_StringSplitString, 2, 0,
                     IntrinsicStringSplitString),
     JS_FN("StringSplitStringLimit", intrinsic_StringSplitStringLimit, 3, 0),
+    JS_FN("WarnDeprecatedStringMethod", intrinsic_WarnDeprecatedStringMethod, 2, 0),
 
     // See builtin/RegExp.h for descriptions of the regexp_* functions.
     JS_FN("regexp_exec_no_statics", regexp_exec_no_statics, 2,0),
     JS_FN("regexp_test_no_statics", regexp_test_no_statics, 2,0),
     JS_FN("regexp_construct_raw_flags", regexp_construct_raw_flags, 2,0),
     JS_FN("regexp_clone", regexp_clone, 1,0),
 
     JS_FN("IsModule", intrinsic_IsInstanceOfBuiltin<ModuleObject>, 1, 0),