Bug 1259822 - Part 2: Show property key in the error message when target object value is null or undefined. r=jorendorff
authorTooru Fujisawa <arai_a@mac.com>
Sat, 11 Jan 2020 05:10:54 +0000
changeset 509855 e8d220dbe0299d90441b3dce6587a5b174f24644
parent 509854 03939cd2d0ab6cba212721a285b59034050e29a2
child 509856 ba5a1a2c62f6354e56f4be5af7346d3f17a4457a
push id37005
push usernerli@mozilla.com
push dateSat, 11 Jan 2020 21:51:30 +0000
treeherdermozilla-central@408f6c0f9814 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff
bugs1259822
milestone74.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 1259822 - Part 2: Show property key in the error message when target object value is null or undefined. r=jorendorff Differential Revision: https://phabricator.services.mozilla.com/D58104
browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
dom/u2f/tests/frame_appid_facet_insecure.html
js/src/jit-test/tests/basic/bug827104.js
js/src/jit-test/tests/basic/expression-autopsy.js
js/src/jit-test/tests/basic/iterable-error-messages.js
js/src/jit-test/tests/basic/property-error-message-fix-disabled.js
js/src/jit-test/tests/basic/property-error-message-fix.js
js/src/jit-test/tests/basic/testBug604210.js
js/src/jit-test/tests/binast/lazy/basic/bug827104.binjs
js/src/jit-test/tests/binast/lazy/basic/testBug604210.binjs
js/src/jit-test/tests/binast/nonlazy/basic/bug827104.binjs
js/src/jit-test/tests/binast/nonlazy/basic/testBug604210.binjs
js/src/jit-test/tests/debug/bug1275001.js
js/src/jit-test/tests/ion/bug913749.js
js/src/jit/BaselineIC.cpp
js/src/js.msg
js/src/jsapi-tests/testErrorInterceptor.cpp
js/src/tests/non262/extensions/regress-353116.js
js/src/tests/non262/regress/regress-328664.js
js/src/tests/non262/regress/regress-372364.js
js/src/tests/non262/regress/regress-420919.js
js/src/tests/non262/regress/regress-469625-03.js
js/src/tests/non262/regress/regress-469758.js
js/src/vm/Interpreter-inl.h
js/src/vm/Interpreter.cpp
js/src/vm/JSContext.cpp
js/src/vm/JSContext.h
js/src/vm/JSObject.cpp
js/src/vm/JSObject.h
services/settings/test/unit/test_remote_settings_worker.js
toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js
toolkit/components/extensions/test/xpcshell/test_ext_l10n.js
--- a/browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
+++ b/browser/extensions/report-site-issue/test/browser/browser_report_site_issue.js
@@ -155,17 +155,17 @@ add_task(async function test_opened_page
       "undefined",
       "Reports function inside array as undefined due to security reasons"
     );
     is(loggedArray[6], "(4)[...]", "Reports array inside array");
     is(loggedArray[7], "(8)[...]", "Reports circular array inside array");
 
     const log5 = details.consoleLog[4];
     ok(
-      log5.log[0] === "TypeError: document.access is undefined",
+      log5.log[0].match(/TypeError: .*document\.access is undefined/),
       "Script errors are logged"
     );
     ok(log5.level === "error", "Reports correct log level");
     ok(log5.uri === URL, "Reports correct url");
     ok(log5.pos === "35:5", "Reports correct line and column");
 
     ok(typeof details.buildID == "string", "Details has a buildID string.");
     ok(typeof details.channel == "string", "Details has a channel string.");
--- a/dom/u2f/tests/frame_appid_facet_insecure.html
+++ b/dom/u2f/tests/frame_appid_facet_insecure.html
@@ -28,17 +28,20 @@ async function doTests() {
     local_ok(err == "ReferenceError: u2f is not defined", "calling u2f should have thrown from an insecure origin");
   }
 
   try {
     window.u2f.register(null, [], [], function(res) {
       local_ok(false, "Callbacks should not be called.");
     });
   } catch (err) {
-    local_ok(err == "TypeError: window.u2f is undefined", "accessing window.u2f should have thrown from an insecure origin");
+    local_is(err.constructor.name, 'TypeError',
+             "accessing window.u2f should have thrown from an insecure origin");
+    local_ok(err.message.endsWith("window.u2f is undefined"),
+             "accessing window.u2f should have thrown from an insecure origin");
   }
 
   try {
     await promiseU2FRegister(null, [{
       version,
       challenge: bytesToBase64UrlSafe(challenge),
     }], [], function(res){
       local_ok(false, "Shouldn't have gotten here on an insecure origin");
@@ -49,9 +52,9 @@ async function doTests() {
 
   local_finished();
 };
 
 doTests();
 
 </script>
 </body>
-</html>
\ No newline at end of file
+</html>
--- a/js/src/jit-test/tests/basic/bug827104.js
+++ b/js/src/jit-test/tests/basic/bug827104.js
@@ -5,9 +5,10 @@ function f() {
     }
     a[i][0] = 0;
 }
 
 var e;
 try {
     f();
 } catch (error) {e = error;}
-assertEq(e.toString(), 'TypeError: a[i] is undefined');
\ No newline at end of file
+assertEq(e.constructor.name, "TypeError");
+assertEq(e.message.includes("a[i] is undefined"), true);
--- a/js/src/jit-test/tests/basic/expression-autopsy.js
+++ b/js/src/jit-test/tests/basic/expression-autopsy.js
@@ -2,20 +2,20 @@ load(libdir + "asserts.js");
 load(libdir + "iteration.js");
 
 function check_one(expected, f, err) {
     var failed = true;
     try {
         f();
         failed = false;
     } catch (ex) {
-        var s = ex.toString();
-        assertEq(s.slice(0, 11), "TypeError: ");
-        assertEq(s.slice(-err.length), err, "" + f);
-        assertEq(s.slice(11, -err.length), expected);
+        assertEq(ex.constructor.name, "TypeError");
+        var s = ex.message;
+        assertEq(s.slice(-err.length), err);
+        assertEq(s.slice(-(err.length + expected.length), -err.length), expected);
     }
     if (!failed)
         throw new Error("didn't fail");
 }
 ieval = eval;
 function check(expr, expected=expr, testStrict=true) {
     var end, err;
     for ([end, err] of [[".random_prop", " is undefined"], ["()", " is not a function"]]) {
--- a/js/src/jit-test/tests/basic/iterable-error-messages.js
+++ b/js/src/jit-test/tests/basic/iterable-error-messages.js
@@ -1,39 +1,39 @@
-function assertThrowsMsg(f, msg) {
+function assertThrowsMsgEndsWith(f, msg) {
     try {
         f();
         assertEq(0, 1);
     } catch(e) {
         assertEq(e instanceof TypeError, true);
-        assertEq(e.message, msg);
+        assertEq(e.message.endsWith(msg), true);
     }
 }
 
 // For-of
 function testForOf(val) {
     for (var x of val) {}
 }
 for (v of [{}, Math, new Proxy({}, {})]) {
-    assertThrowsMsg(() => testForOf(v), "val is not iterable");
+    assertThrowsMsgEndsWith(() => testForOf(v), "val is not iterable");
 }
-assertThrowsMsg(() => testForOf(null), "val is null");
-assertThrowsMsg(() => { for (var x of () => 1) {}}, "() => 1 is not iterable");
+assertThrowsMsgEndsWith(() => testForOf(null), "val is null");
+assertThrowsMsgEndsWith(() => { for (var x of () => 1) {}}, "() => 1 is not iterable");
 
 // Destructuring
 function testDestr(val) {
     var [a, b] = val;
 }
 for (v of [{}, Math, new Proxy({}, {})]) {
-    assertThrowsMsg(() => testDestr(v), "val is not iterable");
+    assertThrowsMsgEndsWith(() => testDestr(v), "val is not iterable");
 }
-assertThrowsMsg(() => testDestr(null), "val is null");
-assertThrowsMsg(() => { [a, b] = () => 1; }, "() => 1 is not iterable");
+assertThrowsMsgEndsWith(() => testDestr(null), "val is null");
+assertThrowsMsgEndsWith(() => { [a, b] = () => 1; }, "() => 1 is not iterable");
 
 // Spread
 function testSpread(val) {
     [...val];
 }
 for (v of [{}, Math, new Proxy({}, {})]) {
-    assertThrowsMsg(() => testSpread(v), "val is not iterable");
+    assertThrowsMsgEndsWith(() => testSpread(v), "val is not iterable");
 }
-assertThrowsMsg(() => testSpread(null), "val is null");
-assertThrowsMsg(() => { [...() => 1]; }, "() => 1 is not iterable");
+assertThrowsMsgEndsWith(() => testSpread(null), "val is null");
+assertThrowsMsgEndsWith(() => { [...() => 1]; }, "() => 1 is not iterable");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/property-error-message-fix-disabled.js
@@ -0,0 +1,36 @@
+// |jit-test| --disable-property-error-message-fix
+
+function check(f, message) {
+  let caught = false;
+  try {
+    f();
+  } catch (e) {
+    assertEq(e.message, message);
+    caught = true;
+  }
+  assertEq(caught, true);
+}
+
+check(() => {
+  let obj = {
+    prop: undefined
+  };
+  obj.prop.prop2();
+}, "obj.prop is undefined");
+
+check(() => {
+  let obj = {
+    prop: null
+  };
+  obj.prop.prop2();
+}, "obj.prop is null");
+
+check(() => {
+  let prop = "prop";
+  undefined[prop]();
+}, "undefined has no properties");
+
+check(() => {
+  let prop = "prop";
+  null[prop]();
+}, "null has no properties");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/property-error-message-fix.js
@@ -0,0 +1,34 @@
+function check(f, message) {
+  let caught = false;
+  try {
+    f();
+  } catch (e) {
+    assertEq(e.message, message);
+    caught = true;
+  }
+  assertEq(caught, true);
+}
+
+check(() => {
+  let obj = {
+    prop: undefined
+  };
+  obj.prop.prop2();
+}, "can't access property \"prop2\", obj.prop is undefined");
+
+check(() => {
+  let obj = {
+    prop: null
+  };
+  obj.prop.prop2();
+}, "can't access property \"prop2\", obj.prop is null");
+
+check(() => {
+  let prop = "prop";
+  undefined[prop]();
+}, "can't access property \"prop\" of undefined");
+
+check(() => {
+  let prop = "prop";
+  null[prop]();
+}, "can't access property \"prop\" of null");
--- a/js/src/jit-test/tests/basic/testBug604210.js
+++ b/js/src/jit-test/tests/basic/testBug604210.js
@@ -1,11 +1,11 @@
 function f() {
-    var msg = '';
+    var ex;
     try {
         var x = undefined;
         print(x.foo);
     } catch (e) {
-        msg = '' + e;
+        ex = e;
     }
-    assertEq(msg, "TypeError: x is undefined");
+    assertEq(ex.message.endsWith(`x is undefined`), true);
 }
 f();
deleted file mode 100644
index 7b405c45cf952877b6e07e07eee3bcb7142f8c56..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 3556483a2aec6876650b7190770fd662af720ab7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f1df075dbdb55c026ec9edd4df537e96a06f7717..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 74fbc5cb1ec2d76872ec8648daff98676bb2f2d7..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/js/src/jit-test/tests/debug/bug1275001.js
+++ b/js/src/jit-test/tests/debug/bug1275001.js
@@ -5,24 +5,24 @@ g.eval("(" + function() {
     Debugger(parent).onExceptionUnwind = function(frame) {
         frame.older
     }
 } + ")()")
 function check_one(expected, f, err) {
     try {
         f()
     } catch (ex) {
-        s = ex.toString()
-        assertEq(s.slice(11, -err.length), expected)
+        s = ex.message;
+        assertEq(s.slice(-(err.length + expected.length), -err.length), expected)
     }
 }
 ieval = eval
 function check(expr, expected = expr) {
     var end, err
-    for ([end, err] of[[".random_prop", " is undefined" ]]) 
+    for ([end, err] of[[".random_prop", ` is undefined` ]]) 
          statement = "o = {};" + expr + end;
          cases = [
             function() { return ieval("var undef;" + statement); },
             Function(statement)
         ]
         for (f of cases) 
             check_one(expected, f, err)
 }
--- a/js/src/jit-test/tests/ion/bug913749.js
+++ b/js/src/jit-test/tests/ion/bug913749.js
@@ -10,16 +10,17 @@ JSON.stringify(this);
 
 y = undefined;
 
 // The exact error message varies nondeterministically. Accept several
 // variations on the theme.
 var variations = [
     `y is undefined`,
     `can't access property "length" of undefined`,
+    `can't access property "length", y is undefined`,
     `undefined has no properties`,
 ];
 
 var hits = 0;
 for (var i = 0; i < 3; i++) {
     try {
         x.toString();
     } catch (e) {
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -2035,17 +2035,17 @@ bool DoSetElemFallback(JSContext* cx, Ba
   jsbytecode* pc = stub->icEntry()->pc(script);
   JSOp op = JSOp(*pc);
   FallbackICSpew(cx, stub, "SetElem(%s)", CodeName[JSOp(*pc)]);
 
   MOZ_ASSERT(op == JSOP_SETELEM || op == JSOP_STRICTSETELEM ||
              op == JSOP_INITELEM || op == JSOP_INITHIDDENELEM ||
              op == JSOP_INITELEM_ARRAY || op == JSOP_INITELEM_INC);
 
-  RootedObject obj(cx, ToObjectFromStack(cx, objv));
+  RootedObject obj(cx, ToObjectFromStackForPropertyAccess(cx, objv, index));
   if (!obj) {
     return false;
   }
 
   RootedShape oldShape(cx, obj->shape());
   RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
   if (!oldGroup) {
     return false;
@@ -2623,17 +2623,17 @@ bool DoSetPropFallback(JSContext* cx, Ba
              op == JSOP_SETNAME || op == JSOP_STRICTSETNAME ||
              op == JSOP_SETGNAME || op == JSOP_STRICTSETGNAME ||
              op == JSOP_INITPROP || op == JSOP_INITLOCKEDPROP ||
              op == JSOP_INITHIDDENPROP || op == JSOP_INITGLEXICAL);
 
   RootedPropertyName name(cx, script->getName(pc));
   RootedId id(cx, NameToId(name));
 
-  RootedObject obj(cx, ToObjectFromStack(cx, lhs));
+  RootedObject obj(cx, ToObjectFromStackForPropertyAccess(cx, lhs, id));
   if (!obj) {
     return false;
   }
   RootedShape oldShape(cx, obj->shape());
   RootedObjectGroup oldGroup(cx, JSObject::getGroup(cx, obj));
   if (!oldGroup) {
     return false;
   }
--- a/js/src/js.msg
+++ b/js/src/js.msg
@@ -50,16 +50,18 @@ MSG_DEF(JSMSG_CANT_DELETE,             1
 MSG_DEF(JSMSG_CANT_TRUNCATE_ARRAY,     0, JSEXN_TYPEERR, "can't delete non-configurable array element")
 MSG_DEF(JSMSG_NOT_FUNCTION,            1, JSEXN_TYPEERR, "{0} is not a function")
 MSG_DEF(JSMSG_NOT_CONSTRUCTOR,         1, JSEXN_TYPEERR, "{0} is not a constructor")
 MSG_DEF(JSMSG_BOGUS_CONSTRUCTOR,       1, JSEXN_TYPEERR, "{0} constructor can't be used directly")
 MSG_DEF(JSMSG_CANT_CONVERT_TO,         2, JSEXN_TYPEERR, "can't convert {0} to {1}")
 MSG_DEF(JSMSG_TOPRIMITIVE_NOT_CALLABLE, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] property is not a function")
 MSG_DEF(JSMSG_TOPRIMITIVE_RETURNED_OBJECT, 2, JSEXN_TYPEERR, "can't convert {0} to {1}: its [Symbol.toPrimitive] method returned an object")
 MSG_DEF(JSMSG_NO_PROPERTIES,           1, JSEXN_TYPEERR, "{0} has no properties")
+MSG_DEF(JSMSG_PROPERTY_FAIL,           2, JSEXN_TYPEERR, "can't access property {0} of {1}")
+MSG_DEF(JSMSG_PROPERTY_FAIL_EXPR,      3, JSEXN_TYPEERR, "can't access property {0}, {1} is {2}")
 MSG_DEF(JSMSG_BAD_REGEXP_FLAG,         1, JSEXN_SYNTAXERR, "invalid regular expression flag {0}")
 MSG_DEF(JSMSG_INVALID_DATA_VIEW_LENGTH, 0, JSEXN_RANGEERR, "invalid data view length")
 MSG_DEF(JSMSG_OFFSET_LARGER_THAN_FILESIZE, 0, JSEXN_RANGEERR, "offset is larger than filesize")
 MSG_DEF(JSMSG_OFFSET_OUT_OF_BUFFER,    0, JSEXN_RANGEERR, "start offset is outside the bounds of the buffer")
 MSG_DEF(JSMSG_OFFSET_OUT_OF_DATAVIEW,  0, JSEXN_RANGEERR, "offset is outside the bounds of the DataView")
 MSG_DEF(JSMSG_SPREAD_TOO_LARGE,        0, JSEXN_RANGEERR, "array too large due to spread operand(s)")
 MSG_DEF(JSMSG_BAD_WEAKMAP_KEY,         0, JSEXN_TYPEERR, "cannot use the given object as a weak map key")
 MSG_DEF(JSMSG_BAD_GETTER_OR_SETTER,    1, JSEXN_TYPEERR, "invalid {0} usage")
--- a/js/src/jsapi-tests/testErrorInterceptor.cpp
+++ b/js/src/jsapi-tests/testErrorInterceptor.cpp
@@ -35,28 +35,26 @@ bool equalStrings(JSContext* cx, JSStrin
 BEGIN_TEST(testErrorInterceptor) {
   // Run the following snippets.
   const char* SAMPLES[] = {
       "throw new Error('I am an Error')\0",
       "throw new TypeError('I am a TypeError')\0",
       "throw new ReferenceError('I am a ReferenceError')\0",
       "throw new SyntaxError('I am a SyntaxError')\0",
       "throw 5\0",
-      "undefined[0]\0",
       "foo[0]\0",
       "b[\0",
   };
   // With the simpleInterceptor, we should end up with the following error:
   const char* TO_STRING[] = {
       "Error: I am an Error\0",
       "TypeError: I am a TypeError\0",
       "ReferenceError: I am a ReferenceError\0",
       "SyntaxError: I am a SyntaxError\0",
       "5\0",
-      "TypeError: undefined has no properties\0",
       "ReferenceError: foo is not defined\0",
       "SyntaxError: expected expression, got end of script\0",
   };
   MOZ_ASSERT(mozilla::ArrayLength(SAMPLES) == mozilla::ArrayLength(TO_STRING));
 
   // Save original callback.
   JSErrorInterceptor* original = JS_GetErrorInterceptorCallback(cx->runtime());
   gLatestMessage.init(cx);
--- a/js/src/tests/non262/extensions/regress-353116.js
+++ b/js/src/tests/non262/extensions/regress-353116.js
@@ -14,62 +14,62 @@ var expect = '';
 test();
 //-----------------------------------------------------------------------------
 
 function test()
 {
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
-  expect = 'TypeError: undefined has no properties';
+  expect = /TypeError: (undefined has no properties|can't access property "y" of undefined)/;
   actual = 'No Error';
 
   try
   {
     undefined.y;
   }
   catch(ex)
   {
     actual = ex + '';
   }
-  reportCompare(expect, actual, summary);
+  reportMatch(expect, actual, summary);
 
-  expect = 'TypeError: null has no properties';
+  expect = /TypeError: (null has no properties|can't access property "y" of null)/;
   actual = 'No Error';
 
   try
   {
     null.y;
   }
   catch(ex)
   {
     actual = ex + '';
   }
-  reportCompare(expect, actual, summary);
+  reportMatch(expect, actual, summary);
 
-  expect = 'TypeError: x is undefined';
+  expect = /TypeError: .*x is undefined/;
   actual = 'No Error';
 
   try
   {
     x = undefined; 
     x.y;
   }
   catch(ex)
   {
     actual = ex + '';
   }
-  reportCompare(expect, actual, summary);
+  reportMatch(expect, actual, summary);
 
-  expect = 'TypeError: x is null';
+  expect = /TypeError: .*x is null/;
   actual = 'No Error';
 
   try
   {
     x = null; 
     x.y;
   }
   catch(ex)
   {
     actual = ex + '';
   }
-  reportCompare(expect, actual, summary);
+  reportMatch(expect, actual, summary);
 }
--- a/js/src/tests/non262/regress/regress-328664.js
+++ b/js/src/tests/non262/regress/regress-328664.js
@@ -2,17 +2,17 @@
 /* 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/. */
 
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 328664;
 var summary = 'Correct error message for funccall(undefined, undefined.prop)';
 var actual = '';
-var expect = /TypeError: value.parameters (has no properties|is undefined)/;
+var expect = /TypeError: .*value.parameters (has no properties|is undefined)/;
 
 printBugNumber(BUGNUMBER);
 printStatus (summary);
  
 var value = {};
 
 function funccall(a,b)
 {
--- a/js/src/tests/non262/regress/regress-372364.js
+++ b/js/src/tests/non262/regress/regress-372364.js
@@ -15,28 +15,28 @@ test();
 //-----------------------------------------------------------------------------
 
 function test()
 {
   printBugNumber(BUGNUMBER);
   printStatus (summary);
 
   print('See Also bug 365891');
-  expect = /TypeError: a\(.+\) (has no properties|is null)/;
+  expect = /TypeError: .*a\(.+\) (has no properties|is null)/;
   try
   {
     function a(){return null;} a(1)[0];
   }
   catch(ex)
   {
     actual = ex + '';
   }
   reportMatch(expect, actual, summary);
 
-  expect = /TypeError: \/a\/.exec\(.+\) (has no properties|is null)/;
+  expect = /TypeError: .*\/a\/.exec\(.+\) (has no properties|is null)/;
   try
   {
     /a/.exec("b")[0];
   }
   catch(ex)
   {
     actual = ex + '';
   }
--- a/js/src/tests/non262/regress/regress-420919.js
+++ b/js/src/tests/non262/regress/regress-420919.js
@@ -14,18 +14,17 @@ var expect = '';
 test();
 //-----------------------------------------------------------------------------
 
 function test()
 {
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
-  // 1.8 branch reports no properties, trunk reports undefined
-  expect = /TypeError: this.u is undefined|TypeError: this.u has no properties/;
+  expect = /TypeError: (this.u is undefined|can't access property "v", this.u is undefined)/;
 
   try
   {
     this.u.v = 1;
   }
   catch(ex)
   {
     actual = ex + '';
--- a/js/src/tests/non262/regress/regress-469625-03.js
+++ b/js/src/tests/non262/regress/regress-469625-03.js
@@ -6,34 +6,33 @@
  */
 
 //-----------------------------------------------------------------------------
 var BUGNUMBER = 469625;
 var summary = 'Do not assert: script->objectsOffset != 0';
 var actual = '';
 var expect = '';
 
-
 //-----------------------------------------------------------------------------
 test();
 //-----------------------------------------------------------------------------
 
 function test()
 {
   printBugNumber(BUGNUMBER);
   printStatus (summary);
  
   function f(x) {
     var [a, b, [c0, c1]] = [x, x, x];
   }
 
-  expect = `TypeError: [...][Symbol.iterator](...).next(...).value is null`;
+  expect = /TypeError: .*\[\.\.\.\]\[Symbol.iterator\]\(\.\.\.\)\.next\(\.\.\.\)\.value is null/;
   actual = 'No Error';
   try
   {
     f(null);
   }
   catch(ex)
   {
     actual = ex + '';
   }
-  reportCompare(expect, actual, summary);
+  reportMatch(expect, actual, summary);
 }
--- a/js/src/tests/non262/regress/regress-469758.js
+++ b/js/src/tests/non262/regress/regress-469758.js
@@ -4,11 +4,11 @@
 var err;
 try {
     {let i=1}
     {let j=1; [][j][2]}
 } catch (e) {
     err = e;
 }
 assertEq(err instanceof TypeError, true);
-assertEq(err.message, "[][j] is undefined");
+assertEq(err.message.endsWith("[][j] is undefined"), true);
 
 reportCompare(0, 0, 'ok');
--- a/js/src/vm/Interpreter-inl.h
+++ b/js/src/vm/Interpreter-inl.h
@@ -493,17 +493,17 @@ static MOZ_ALWAYS_INLINE bool GetObjectE
 }
 
 static MOZ_ALWAYS_INLINE bool GetPrimitiveElementOperation(
     JSContext* cx, JSOp op, JS::HandleValue receiver, HandleValue key,
     MutableHandleValue res) {
   MOZ_ASSERT(op == JSOP_GETELEM || op == JSOP_CALLELEM);
 
   // FIXME: Bug 1234324 We shouldn't be boxing here.
-  RootedObject boxed(cx, ToObjectFromStack(cx, receiver));
+  RootedObject boxed(cx, ToObjectFromStackForPropertyAccess(cx, receiver, key));
   if (!boxed) {
     return false;
   }
 
   do {
     uint32_t index;
     if (IsDefinitelyIndex(key, &index)) {
       if (GetElementNoGC(cx, boxed, receiver, index, res.address())) {
--- a/js/src/vm/Interpreter.cpp
+++ b/js/src/vm/Interpreter.cpp
@@ -257,17 +257,17 @@ bool js::GetImportOperation(JSContext* c
   MOZ_ASSERT(env->as<ModuleEnvironmentObject>().hasImportBinding(name));
   return FetchName<GetNameMode::Normal>(cx, env, pobj, name, prop, vp);
 }
 
 static bool SetPropertyOperation(JSContext* cx, JSOp op, HandleValue lval,
                                  HandleId id, HandleValue rval) {
   MOZ_ASSERT(op == JSOP_SETPROP || op == JSOP_STRICTSETPROP);
 
-  RootedObject obj(cx, ToObjectFromStack(cx, lval));
+  RootedObject obj(cx, ToObjectFromStackForPropertyAccess(cx, lval, id));
   if (!obj) {
     return false;
   }
 
   ObjectOpResult result;
   return SetProperty(cx, obj, id, rval, lval, result) &&
          result.checkStrictErrorOrWarning(cx, obj, id,
                                           op == JSOP_STRICTSETPROP);
@@ -1319,21 +1319,21 @@ again:
   do {                               \
     REGS.sp++->setObjectOrNull(obj); \
     cx->debugOnlyCheck(REGS.sp[-1]); \
   } while (0)
 #define PUSH_MAGIC(magic) REGS.sp++->setMagic(magic)
 #define POP_COPY_TO(v) (v) = *--REGS.sp
 #define POP_RETURN_VALUE() REGS.fp()->setReturnValue(*--REGS.sp)
 
-#define FETCH_OBJECT(cx, n, obj)             \
-  JS_BEGIN_MACRO                             \
-    HandleValue val = REGS.stackHandleAt(n); \
-    obj = ToObjectFromStack((cx), (val));    \
-    if (!obj) goto error;                    \
+#define FETCH_OBJECT(cx, n, obj, key)                             \
+  JS_BEGIN_MACRO                                                  \
+    HandleValue val = REGS.stackHandleAt(n);                      \
+    obj = ToObjectFromStackForPropertyAccess((cx), (val), (key)); \
+    if (!(obj)) goto error;                                       \
   JS_END_MACRO
 
 /*
  * Same for JSOP_SETNAME and JSOP_SETPROP, which differ only slightly but
  * remain distinct for the decompiler.
  */
 JS_STATIC_ASSERT(JSOP_SETNAME_LENGTH == JSOP_SETPROP_LENGTH);
 
@@ -2586,17 +2586,17 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     END_CASE(JSOP_DELNAME)
 
     CASE(JSOP_DELPROP)
     CASE(JSOP_STRICTDELPROP) {
       static_assert(JSOP_DELPROP_LENGTH == JSOP_STRICTDELPROP_LENGTH,
                     "delprop and strictdelprop must be the same size");
       ReservedRooted<jsid> id(&rootId0, NameToId(script->getName(REGS.pc)));
       ReservedRooted<JSObject*> obj(&rootObject0);
-      FETCH_OBJECT(cx, -1, obj);
+      FETCH_OBJECT(cx, -1, obj, id);
 
       ObjectOpResult result;
       if (!DeleteProperty(cx, obj, id, result)) {
         goto error;
       }
       if (!result && JSOp(*REGS.pc) == JSOP_STRICTDELPROP) {
         result.reportError(cx, obj, id);
         goto error;
@@ -2607,19 +2607,19 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     END_CASE(JSOP_DELPROP)
 
     CASE(JSOP_DELELEM)
     CASE(JSOP_STRICTDELELEM) {
       static_assert(JSOP_DELELEM_LENGTH == JSOP_STRICTDELELEM_LENGTH,
                     "delelem and strictdelelem must be the same size");
       /* Fetch the left part and resolve it to a non-null object. */
       ReservedRooted<JSObject*> obj(&rootObject0);
-      FETCH_OBJECT(cx, -2, obj);
 
       ReservedRooted<Value> propval(&rootValue0, REGS.sp[-1]);
+      FETCH_OBJECT(cx, -2, obj, propval);
 
       ObjectOpResult result;
       ReservedRooted<jsid> id(&rootId0);
       if (!ToPropertyKey(cx, propval, &id)) {
         goto error;
       }
       if (!DeleteProperty(cx, obj, id, result)) {
         goto error;
@@ -2875,17 +2875,18 @@ static MOZ_NEVER_INLINE JS_HAZ_JSNATIVE_
     END_CASE(JSOP_GETELEM_SUPER)
 
     CASE(JSOP_SETELEM)
     CASE(JSOP_STRICTSETELEM) {
       static_assert(JSOP_SETELEM_LENGTH == JSOP_STRICTSETELEM_LENGTH,
                     "setelem and strictsetelem must be the same size");
       HandleValue receiver = REGS.stackHandleAt(-3);
       ReservedRooted<JSObject*> obj(&rootObject0);
-      obj = ToObjectFromStack(cx, receiver);
+      obj = ToObjectFromStackForPropertyAccess(cx, receiver,
+                                               REGS.stackHandleAt(-2));
       if (!obj) {
         goto error;
       }
       ReservedRooted<jsid> id(&rootId0);
       FETCH_ELEMENT_ID(-2, id);
       HandleValue value = REGS.stackHandleAt(-1);
       if (!SetObjectElementOperation(cx, obj, id, value, receiver,
                                      *REGS.pc == JSOP_STRICTSETELEM)) {
@@ -4440,17 +4441,17 @@ bool js::GetProperty(JSContext* cx, Hand
     }
 
     if (GetPropertyPure(cx, proto, NameToId(name), vp.address())) {
       return true;
     }
   }
 
   RootedValue receiver(cx, v);
-  RootedObject obj(cx, ToObjectFromStack(cx, v));
+  RootedObject obj(cx, ToObjectFromStackForPropertyAccess(cx, v, name));
   if (!obj) {
     return false;
   }
 
   return GetProperty(cx, obj, receiver, name, vp);
 }
 
 bool js::GetValueProperty(JSContext* cx, HandleValue value,
@@ -4715,17 +4716,17 @@ bool js::GetAndClearExceptionAndStack(JS
 bool js::GetAndClearException(JSContext* cx, MutableHandleValue res) {
   RootedSavedFrame stack(cx);
   return GetAndClearExceptionAndStack(cx, res, &stack);
 }
 
 template <bool strict>
 bool js::DeletePropertyJit(JSContext* cx, HandleValue v,
                            HandlePropertyName name, bool* bp) {
-  RootedObject obj(cx, ToObjectFromStack(cx, v));
+  RootedObject obj(cx, ToObjectFromStackForPropertyAccess(cx, v, name));
   if (!obj) {
     return false;
   }
 
   RootedId id(cx, NameToId(name));
   ObjectOpResult result;
   if (!DeleteProperty(cx, obj, id, result)) {
     return false;
@@ -4745,17 +4746,17 @@ bool js::DeletePropertyJit(JSContext* cx
 template bool js::DeletePropertyJit<true>(JSContext* cx, HandleValue val,
                                           HandlePropertyName name, bool* bp);
 template bool js::DeletePropertyJit<false>(JSContext* cx, HandleValue val,
                                            HandlePropertyName name, bool* bp);
 
 template <bool strict>
 bool js::DeleteElementJit(JSContext* cx, HandleValue val, HandleValue index,
                           bool* bp) {
-  RootedObject obj(cx, ToObjectFromStack(cx, val));
+  RootedObject obj(cx, ToObjectFromStackForPropertyAccess(cx, val, index));
   if (!obj) {
     return false;
   }
 
   RootedId id(cx);
   if (!ToPropertyKey(cx, index, &id)) {
     return false;
   }
--- a/js/src/vm/JSContext.cpp
+++ b/js/src/vm/JSContext.cpp
@@ -56,16 +56,17 @@
 #include "vm/HelperThreads.h"
 #include "vm/Iteration.h"
 #include "vm/JSAtom.h"
 #include "vm/JSFunction.h"
 #include "vm/JSObject.h"
 #include "vm/JSScript.h"
 #include "vm/Realm.h"
 #include "vm/Shape.h"
+#include "vm/StringType.h"  // StringToNewUTF8CharsZ
 
 #include "vm/Compartment-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/JSScript-inl.h"
 #include "vm/Stack-inl.h"
 
 using namespace js;
 
@@ -875,37 +876,90 @@ void js::ReportIsNotDefined(JSContext* c
   }
 }
 
 void js::ReportIsNotDefined(JSContext* cx, HandlePropertyName name) {
   RootedId id(cx, NameToId(name));
   ReportIsNotDefined(cx, id);
 }
 
-void js::ReportIsNullOrUndefined(JSContext* cx, int spindex, HandleValue v) {
+const char* NullOrUndefinedToCharZ(HandleValue v) {
+  MOZ_ASSERT(v.isNullOrUndefined());
+  return v.isNull() ? js_null_str : js_undefined_str;
+}
+
+void js::ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, HandleValue v,
+                                                  bool reportScanStack) {
   MOZ_ASSERT(v.isNullOrUndefined());
 
-  UniqueChars bytes = DecompileValueGenerator(cx, spindex, v, nullptr);
+  if (!reportScanStack) {
+    JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                              JSMSG_CANT_CONVERT_TO,
+                              NullOrUndefinedToCharZ(v), "object");
+    return;
+  }
+
+  UniqueChars bytes =
+      DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, nullptr);
   if (!bytes) {
     return;
   }
 
   if (strcmp(bytes.get(), js_undefined_str) == 0 ||
       strcmp(bytes.get(), js_null_str) == 0) {
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_NO_PROPERTIES,
                              bytes.get());
-  } else if (v.isUndefined()) {
+  } else {
     JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
                              JSMSG_UNEXPECTED_TYPE, bytes.get(),
-                             js_undefined_str);
-  } else {
-    MOZ_ASSERT(v.isNull());
-    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
-                             JSMSG_UNEXPECTED_TYPE, bytes.get(), js_null_str);
+                             NullOrUndefinedToCharZ(v));
+  }
+}
+
+void js::ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx, HandleValue v,
+                                                  HandleId key,
+                                                  bool reportScanStack) {
+  MOZ_ASSERT(v.isNullOrUndefined());
+
+  if (!cx->realm()->creationOptions().getPropertyErrorMessageFixEnabled()) {
+    ReportIsNullOrUndefinedForPropertyAccess(cx, v, reportScanStack);
+    return;
+  }
+
+  RootedValue idVal(cx, IdToValue(key));
+  RootedString idStr(cx, ValueToSource(cx, idVal));
+  if (!idStr) {
+    return;
   }
+
+  UniqueChars keyStr = StringToNewUTF8CharsZ(cx, *idStr);
+
+  if (!reportScanStack) {
+    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_FAIL,
+                             keyStr.get(),
+                             NullOrUndefinedToCharZ(v));
+    return;
+  }
+
+  UniqueChars bytes =
+      DecompileValueGenerator(cx, JSDVG_SEARCH_STACK, v, nullptr);
+  if (!bytes) {
+    return;
+  }
+
+  if (strcmp(bytes.get(), js_undefined_str) == 0 ||
+      strcmp(bytes.get(), js_null_str) == 0) {
+    JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr, JSMSG_PROPERTY_FAIL,
+                             keyStr.get(), bytes.get());
+    return;
+  }
+
+  JS_ReportErrorNumberUTF8(cx, GetErrorMessage, nullptr,
+                           JSMSG_PROPERTY_FAIL_EXPR, keyStr.get(),
+                           bytes.get(), NullOrUndefinedToCharZ(v));
 }
 
 bool js::ReportValueErrorFlags(JSContext* cx, unsigned flags,
                                const unsigned errorNumber, int spindex,
                                HandleValue v, HandleString fallback,
                                const char* arg1, const char* arg2) {
   MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount >= 1);
   MOZ_ASSERT(js_ErrorFormatString[errorNumber].argCount <= 3);
--- a/js/src/vm/JSContext.h
+++ b/js/src/vm/JSContext.h
@@ -1099,17 +1099,23 @@ extern bool PrintError(JSContext* cx, FI
 
 extern void ReportIsNotDefined(JSContext* cx, HandlePropertyName name);
 
 extern void ReportIsNotDefined(JSContext* cx, HandleId id);
 
 /*
  * Report an attempt to access the property of a null or undefined value (v).
  */
-extern void ReportIsNullOrUndefined(JSContext* cx, int spindex, HandleValue v);
+extern void ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx,
+                                                     HandleValue v,
+                                                     bool reportScanStack);
+extern void ReportIsNullOrUndefinedForPropertyAccess(JSContext* cx,
+                                                     HandleValue v,
+                                                     HandleId key,
+                                                     bool reportScanStack);
 
 /*
  * Report error using js_DecompileValueGenerator(cx, spindex, v, fallback) as
  * the first argument for the error message. If the error message has less
  * then 3 arguments, use null for arg1 or arg2.
  */
 extern bool ReportValueErrorFlags(JSContext* cx, unsigned flags,
                                   const unsigned errorNumber, int spindex,
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -3325,22 +3325,67 @@ JSObject* js::PrimitiveToObject(JSContex
  * Callers must handle the already-object case.
  */
 JSObject* js::ToObjectSlow(JSContext* cx, JS::HandleValue val,
                            bool reportScanStack) {
   MOZ_ASSERT(!val.isMagic());
   MOZ_ASSERT(!val.isObject());
 
   if (val.isNullOrUndefined()) {
-    if (reportScanStack) {
-      ReportIsNullOrUndefined(cx, JSDVG_SEARCH_STACK, val);
+    ReportIsNullOrUndefinedForPropertyAccess(cx, val, reportScanStack);
+    return nullptr;
+  }
+
+  return PrimitiveToObject(cx, val);
+}
+
+JSObject* js::ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val,
+                                            HandleId key,
+                                            bool reportScanStack) {
+  MOZ_ASSERT(!val.isMagic());
+  MOZ_ASSERT(!val.isObject());
+
+  if (val.isNullOrUndefined()) {
+    ReportIsNullOrUndefinedForPropertyAccess(cx, val, key, reportScanStack);
+    return nullptr;
+  }
+
+  return PrimitiveToObject(cx, val);
+}
+
+JSObject* js::ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val,
+                                            HandlePropertyName key,
+                                            bool reportScanStack) {
+  MOZ_ASSERT(!val.isMagic());
+  MOZ_ASSERT(!val.isObject());
+
+  if (val.isNullOrUndefined()) {
+    RootedId keyId(cx, NameToId(key));
+    ReportIsNullOrUndefinedForPropertyAccess(cx, val, keyId, reportScanStack);
+    return nullptr;
+  }
+
+  return PrimitiveToObject(cx, val);
+}
+
+JSObject* js::ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val,
+                                            HandleValue keyValue,
+                                            bool reportScanStack) {
+  MOZ_ASSERT(!val.isMagic());
+  MOZ_ASSERT(!val.isObject());
+
+  if (val.isNullOrUndefined()) {
+    RootedId key(cx);
+    if (keyValue.isPrimitive()) {
+      if (!ValueToId<CanGC>(cx, keyValue, &key)) {
+        return nullptr;
+      }
+      ReportIsNullOrUndefinedForPropertyAccess(cx, val, key, reportScanStack);
     } else {
-      JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
-                                JSMSG_CANT_CONVERT_TO,
-                                val.isNull() ? "null" : "undefined", "object");
+      ReportIsNullOrUndefinedForPropertyAccess(cx, val, reportScanStack);
     }
     return nullptr;
   }
 
   return PrimitiveToObject(cx, val);
 }
 
 Value js::GetThisValue(JSObject* obj) {
--- a/js/src/vm/JSObject.h
+++ b/js/src/vm/JSObject.h
@@ -996,16 +996,48 @@ namespace js {
 /* For converting stack values to objects. */
 MOZ_ALWAYS_INLINE JSObject* ToObjectFromStack(JSContext* cx, HandleValue vp) {
   if (vp.isObject()) {
     return &vp.toObject();
   }
   return js::ToObjectSlow(cx, vp, true);
 }
 
+JSObject* ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val,
+                                        HandleId key, bool reportScanStack);
+JSObject* ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val,
+                                        HandlePropertyName key,
+                                        bool reportScanStack);
+JSObject* ToObjectSlowForPropertyAccess(JSContext* cx, JS::HandleValue val,
+                                        HandleValue keyValue,
+                                        bool reportScanStack);
+
+MOZ_ALWAYS_INLINE JSObject* ToObjectFromStackForPropertyAccess(JSContext* cx,
+                                                               HandleValue vp,
+                                                               HandleId key) {
+  if (vp.isObject()) {
+    return &vp.toObject();
+  }
+  return js::ToObjectSlowForPropertyAccess(cx, vp, key, true);
+}
+MOZ_ALWAYS_INLINE JSObject* ToObjectFromStackForPropertyAccess(
+    JSContext* cx, HandleValue vp, HandlePropertyName key) {
+  if (vp.isObject()) {
+    return &vp.toObject();
+  }
+  return js::ToObjectSlowForPropertyAccess(cx, vp, key, true);
+}
+MOZ_ALWAYS_INLINE JSObject* ToObjectFromStackForPropertyAccess(
+    JSContext* cx, HandleValue vp, HandleValue key) {
+  if (vp.isObject()) {
+    return &vp.toObject();
+  }
+  return js::ToObjectSlowForPropertyAccess(cx, vp, key, true);
+}
+
 template <XDRMode mode>
 XDRResult XDRObjectLiteral(XDRState<mode>* xdr, MutableHandleObject obj);
 
 /*
  * Report a TypeError: "so-and-so is not an object".
  * Using NotNullObject is usually less code.
  */
 extern void ReportNotObject(JSContext* cx, const Value& v);
--- a/services/settings/test/unit/test_remote_settings_worker.js
+++ b/services/settings/test/unit/test_remote_settings_worker.js
@@ -62,17 +62,17 @@ add_task(async function test_import_json
 
 add_task(async function test_throws_error_if_worker_fails() {
   let error;
   try {
     await RemoteSettingsWorker.canonicalStringify(null, [], 42);
   } catch (e) {
     error = e;
   }
-  Assert.equal(error.message, "TypeError: localRecords is null");
+  Assert.equal(error.message.endsWith("localRecords is null"), true);
 });
 
 add_task(async function test_stops_worker_after_timeout() {
   // Change the idle time.
   Services.prefs.setIntPref(
     "services.settings.worker_idle_max_milliseconds",
     1
   );
--- a/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_contentscript_create_iframe.js
@@ -149,18 +149,23 @@ add_task(async function test_contentscri
       manifest = win.browser.runtime.getManifest();
     } catch (e) {
       manifestException = e;
     }
 
     Assert.ok(!manifest, "manifest should be undefined");
 
     Assert.equal(
-      String(manifestException),
-      "TypeError: win.browser.runtime is undefined",
+      manifestException.constructor.name,
+      "TypeError",
+      "expected exception received"
+    );
+
+    Assert.ok(
+      manifestException.message.endsWith("win.browser.runtime is undefined"),
       "expected exception received"
     );
 
     let getManifestException = win.testGetManifestException();
 
     Assert.equal(
       getManifestException,
       "TypeError: can't access dead object",
--- a/toolkit/components/extensions/test/xpcshell/test_ext_l10n.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_l10n.js
@@ -95,18 +95,18 @@ add_task(async function test_l10n_dom() 
   let results = await runTest(true);
   equal(results.success, true, "Translation succeeded in privileged extension");
   equal(results.result, "value", "Translation got the right value");
 
   // In an unprivleged extension, document.l10n shouldn't show up
   results = await runTest(false);
   equal(results.success, false, "Translation failed in unprivileged extension");
   equal(
-    results.msg,
-    "document.l10n is undefined",
+    results.msg.endsWith("document.l10n is undefined"),
+    true,
     "Translation failed due to missing document.l10n"
   );
 });
 
 add_task(async function test_l10n_manifest() {
   // Fluent can't be used to localize properties that the AddonManager
   // reads (see comment inside ExtensionData.parseManifest for details)
   // so test by localizing a property that only the extension framework