Skip to content

Instantly share code, notes, and snippets.

@nzakas
Created February 27, 2014 19:13
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nzakas/9257017 to your computer and use it in GitHub Desktop.
Save nzakas/9257017 to your computer and use it in GitHub Desktop.
Is this a Node.js bug?
value1: {"stack":"MyStack","type":"MyType"} true
value2: {"stack":"MyStack","message":"MyError"} true
value3: {"stack":"MyStack","message":"MyError"} true
value4: {"stack":"MyStack","type":"MyType"} true
value1: {} false
value2: {"stack":"MyStack","message":"MyError"} true
value3: {"message":"MyError"} false
value4: {} false
/*
* Help me figure out if this is a bug in Node.js, V8, or someplace else.
*
* Situation: There's an object that inherits from Error via prototype chaining.
* We then overwrite stack and type (effectively shadowing the prototype properties)
* because they aren't enumerable by default and we want them to be. Doing this
* should create an enumerable stack and enumerable type on the new object.
*
* In Node.js, however, this is not the case. The properties seem to maintain their
* enumerability, and in fact, the properties don't get added to the new object.
*
* In Chrome, the properties are added as enumerable to the new object.
*
* I've included output from both Node.js and Chrome in this gist for reference.
*/
var err = new Error('MyError');
err.stack = 'MyStack';
err.type = 'MyType';
var value = {};
for (var i in err) {
value[i] = err[i];
}
console.log('value1:', JSON.stringify(value), value.hasOwnProperty('stack'));
value = {};
value.stack = err.stack;
value.message = err.message;
console.log('value2:', JSON.stringify(value), value.hasOwnProperty('stack'));
value = Object.create(err);
value.stack = err.stack;
value.message = err.message;
console.log('value3:', JSON.stringify(value), value.hasOwnProperty('stack'));
value = Object.create(err);
for (var i in err) {
value[i] = err[i];
}
console.log('value4:', JSON.stringify(value), value.hasOwnProperty('stack'));
@nzakas
Copy link
Author

nzakas commented Feb 27, 2014

I'm using Node 0.10.26, FWIW.

@seabre
Copy link

seabre commented Feb 27, 2014

It if helps at all, I get the same output you got in node_output.txt in Node v0.8.16 and v0.10.25.

@SomeKittens
Copy link

Replicated here. console.log(Object.keys(err)); -> [] in node, ["stack", "type"] in Chrome. Logging the properties in Node (err.stack) does print MyStack.

@SomeKittens
Copy link

Deleting the property before assigning it seems to work:

var err = new Error('MyError');
delete err.stack;
delete err.type;
err.stack = 'MyStack';
err.type = 'MyType';

var value = {};
for (var i in err) {
    value[i] = err[i];
}
console.log('value1:', JSON.stringify(value), value.hasOwnProperty('stack'));

Logs: value1: {"stack":"MyStack","type":"MyType"} true.

This looks like something to do with overwriting the enumeration flag on the property.

@karlbohlmark
Copy link

node 0.11.11:

value1: {"stack":"MyStack","type":"MyType"} true
value2: {"stack":"MyStack","message":"MyError"} true
value3: {"stack":"MyStack","message":"MyError"} true
value4: {"stack":"MyStack","type":"MyType"} true

@nzakas
Copy link
Author

nzakas commented Feb 27, 2014

Thanks everyone, I've filed the bug here:
nodejs/node-v0.x-archive#7202

@CrypticSwarm
Copy link

I think it is a inconvenience in the 'set' not a bug in 'enumerable'.

var foo = {};
Object.defineProperty(foo, 'abc', {
  get: function () { return 123 },
  set: function (a) { return a },
  configurable: true,
  enumerable: true
});
var bar = Object.create(foo);
bar.abc = "hello";
Object.getOwnPropertyDescriptor(bar, 'abc');
// No own propertyDescriptor for "abc"
var foo = {};
Object.defineProperty(foo, 'abc', {
  get: function () { return 123 },
  set: function (a) { return Object.defineProperty(this, 'abc', { value: a, writable: true, configurable: true }) },
  configurable: true,
  enumerable: true
});
var bar = Object.create(foo);
bar.abc = "hello";
Object.getOwnPropertyDescriptor(bar, 'abc');
// Has own propertyDescriptor for 'abc'

@nzakas
Copy link
Author

nzakas commented Feb 27, 2014

I dug in a bit, couldn't find a V8 bug, but I'm convinced it's not an enumerability issue but rather an issue with V8's implementation of [[Put]] (ref: http://www.ecma-international.org/ecma-262/5.1/#sec-8.12.5).

@CrypticSwarm
Copy link

I agree that its in the [[Put]]. In V8 the stack property has an get and set accessors.

Object.getOwnPropertyDescriptor(new Error(), 'stack');
// {get: function, set: function, enumerable: false, configurable: true} 

In the spec listed above it should hit step 5 and do whatever should be done in the property descriptors set

Also quoted from MDN

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty

set
A function which serves as a setter for the property, or undefined if there is no setter. The function will receive as only argument the new value being assigned to the property.
Defaults to undefined.
Bear in mind that these options are not necessarily own properties so, if inherited, will be considered too. In order to ensure these defaults are preserved you might freeze the Object.prototype upfront, specify all options explicitly, or point to null as proto property.

Shows that setters in the proto chain are also considered.

In my previous comment shows two side by side in the first bar.abc === 123 in all browsers that I checked.
Whereas in second bar.abc === "hello".

This implies to me that Error in older versions of V8 Error did something like in the first example above.

@nzakas
Copy link
Author

nzakas commented Feb 28, 2014

Ah so you're saying the problem is in the implementation of Error rather than the implementation of [[Put]]. Error does seem like a special case.

var obj1 = {

    _name: ""
};

Object.defineProperty(obj1, "name", {
    get: function() {
        return this._name;
    },
    set: function(value) {
        this._name = value;
    },
    configurable: true
});

var obj2 = Object.create(obj1);

obj2.name = "bar";

console.log(obj2.hasOwnProperty("name"));   // false everywhere

The above works as defined in the spec. But if you try this with Error:

var err1 = new Error("foo");
var err2 = Object.create(err1);

err2.stack = "bar";

console.log(err2.hasOwnProperty("stack"));  // Node: false, FF/Chrome: true

So you're surmising that when the stack setter is called, calls Object.defineProperty() to change stack into a value property?

@nzakas
Copy link
Author

nzakas commented Feb 28, 2014

Confirmed. Here's what happens in Chrome:

var err = new Error("foo");
var descriptor = Object.getOwnPropertyDescriptor(err, "stack");
console.log(descriptor);  // Object { get: function, set: function, enumerable: false, configurable: true }

err.stack = "foo";
console.log(descriptor);  // Object { value: "foo", writable: true, enumerable: false, configurable: true }

In Node 0.10, the second log statement outputs the same as the first. So it looks like the only real bug is a compatibility issue across browsers (Firefox implements stack as a data property only).

@CrypticSwarm
Copy link

So it turns out that creating objects that violate [[Get]] what you [[Put]] cause really tricky non-intuitive situations.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment