Matt Bierner
Matt Bierner

Reputation: 65603

this Object on Error 'message' getter in Chrome

I am seeing different behavior in Chrome vs Firefox and Safari for this code:

var FancyError = function(x) { this.x = x; };
FancyError.prototype = new Error;

Object.defineProperties(FancyError.prototype, {
   'message': {
      'get': function(){ return "fancy:" + this.x; }
   }
});

new FancyError("hi") + '';

No errors are generated in strict mode. The error is caused by chrome calling the message getter with a different this object than expected.

Firefox/Safari output: "Error: fancy:hi"

Chrome output : "Error: fancy:undefined"

Any idea what is going on here?

Versions Tested

Chrome: 30.0.1599.69 OSX

Firefox: 24 OSX

Safari: 6.0.5 OSX

Upvotes: 2

Views: 345

Answers (1)

apsillers
apsillers

Reputation: 116020

This definitely appears to be a bug in Chrome's Error.prototype.toString:

var FancyError = function(x) { this.x = x; };
FancyError.prototype = new Error;
FancyError.prototype.x = "I'm the prototype";

Object.defineProperties(FancyError.prototype, {
   'message': {
      'get': function(){ return "fancy:" + this.x; }
   }
});

var fe = new FancyError("I'm the instance");

With this setup in Chrome:

  • fe.message produces fancy:I'm the instance
  • fe.toString() produces Error: fancy:I'm the prototype

What happens in the first case is easy to understand:

  1. fe.message prompts a call to [[Get]] on fe with the argument "message"
  2. [[Get]] Step 1 gets the accessor descriptor set on fe's prototype (since fe does't have it's own message property).
  3. [[Get]] Step 6 calls the getter on the accessor descriptor, with this set as the original object that called [[Get]] (here, the fe object).

The second case is strange. It appears that fe.toString() performs the [[Get]] for "message" on the Error instance in fe's prototype. This appears to be wrong, since Step 5 of Error.prototype.toString specifies getting the message from the this value in the toString function.

EDIT

I previously assumed that the problem was caused by fe having Error in its prototype chain. However, consider this case:

var FancyError = function(x) { this.x = x; };
FancyError.prototype.x = "I'm the prototype";

Object.defineProperties(FancyError.prototype, {
   'message': {
      'get': function(){ return "fancy:" + this.x; }
   }
});

fe = new FancyError("I'm the instance");

In this case:

  • fe.message is fancy:I'm the instance as above
  • Error.prototype.toString.call(fe) is Error: fancy:I'm the prototype

therefore, we must conclude that Chrome's Error.prototype.toString mistakenly uses the prototype that contains the getter as the this value of the getter, which appears to contradict the normal behavior of [[Get]] and/or Step 5 of Error.prototype.toString.

Upvotes: 2

Related Questions