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 325619 07d6bf74b7a2552da91b5e2fce0fa0bc3b457394
parent 325618 e83eaf2380c65400dc03c6f3615d4b2cef669af3
child 325620 5a110ad242ead60e71d2186bae78b1fb766ad5ff
push id24
push usermaklebus@msu.edu
push dateTue, 20 Dec 2016 03:11:33 +0000
reviewersjandem
bugs1319926
milestone53.0a1
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),