Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

errors thrown from a vm aren't recognized as errors #1770

Closed
boneskull opened this issue Jun 30, 2015 · 25 comments
Closed

errors thrown from a vm aren't recognized as errors #1770

boneskull opened this issue Jun 30, 2015 · 25 comments

Comments

@boneskull
Copy link
Member

As you can read in the comments on commit 02d421b, it seems that if code under test uses vm.runInNewContext() and throws an Error:

try {
  vm.runInNewContext('throw new TypeError()');
} catch (e) {
  e instanceof Error; // false
}

I think we can safeguard against this by duck-typing instead of an instanceof check.

@boneskull
Copy link
Member Author

@danielstjules
Copy link
Contributor

I think this is fixed in master, from #1758: https://github.com/mochajs/mocha/blob/master/lib/runner.js#L205-L207

try {
  vm.runInNewContext('throw new TypeError()');
} catch (err) {
  console.log(err instanceof Error || err && typeof err.message == 'string');
}
// => true

@boneskull
Copy link
Member Author

@danielstjules OK. That "works", but checking the log, the intent was to solve cross-frame issues, not open it up to throwing plain-jane objects.

I like lodash.isError() for this:

var errorTag = '[object Error]';
var objectProto = Object.prototype;
var objToString = objectProto.toString;

function isError(value) {
  return isObjectLike(value) && typeof value.message == 'string' && objToString.call(value) == errorTag;
}

No instanceof to be found. Don't think it's necessary.

boneskull added a commit to boneskull/mocha that referenced this issue Jun 30, 2015
@danielstjules
Copy link
Contributor

But that wouldn't work with custom error objects cross-frame (or in the same context, depending on how it's used)

var errorTag = '[object Error]';
var objectProto = Object.prototype;
var objToString = objectProto.toString;

function isError(value) {
  return typeof value.message == 'string' && objToString.call(value) == errorTag;
}

function FooBarError(message) { this.message = message; }
FooBarError.prototype = new TypeError();

isError(new FooBarError('test'));
// => false

@boneskull
Copy link
Member Author

@danielstjules No, it wouldn't; not like that, anyway. That isn't an Error instance either way you look at it...

@boneskull
Copy link
Member Author

there are comments in #1770 but let's keep the discussion here.

@danielstjules
Copy link
Contributor

How is it not an error instance any way you look at it? It's an error-like object?

var utils = require('util');
var errorTag = '[object Error]';
var objectProto = Object.prototype;
var objToString = objectProto.toString;

function isError(value) {
  return typeof value.message == 'string' && objToString.call(value) == errorTag;
}

function FooBarError(message) {
  this.message = message;
}

util.inherits(FooBarError, TypeError);

isError(new FooBarError('test'));
// => false
var utils = require('util');
var errorTag = '[object Error]';
var objectProto = Object.prototype;
var objToString = objectProto.toString;

function isError(value) {
  return typeof value.message == 'string' && objToString.call(value) == errorTag;
}

function FooBarError(message) {
  this.message = message;
}

util.inherits(FooBarError, Error);

isError(new FooBarError('test'));
// => false

@danielstjules
Copy link
Contributor

I think we're stuck just checking for the presence of err.message

@boneskull
Copy link
Member Author

@danielstjules I think maybe I haven't made my intent clear. Above, I wrote:

the intent was to solve cross-frame issues, not open it up to throwing plain-jane objects.

The code you wrote does not result in an Error instance--it's an Error-like object--but if you throw a TypeError from another frame, isError() will recognize it. That's the intent of the original patch--not to support "custom" classes which happen to have the same prototype as TypeError.

At any rate, there's no good way to "subclass" Error outside of ES6. I've done a fair amount of research on the topic, and can only deduce that if you're not using ES6, you're better off putting custom properties on an Error instance.

If you're using ES6, you can of course extend TypeError if you wish.

@boneskull
Copy link
Member Author

(I'm unsure where the idea that throwing {message: 'foo'} was ok came from)

@boneskull
Copy link
Member Author

So, that's why I targeted the #1771 to v3.0.0--because currently, you can throw a function with a message property if you want--but I'm pretty sure that's not something we should be supporting.

@boneskull
Copy link
Member Author

How is it not an error instance any way you look at it?

because

new FooBarError('test') instanceof Error  // false

thus not an Error instance

@danielstjules
Copy link
Contributor

thus not an Error instance

That's not true. It is an instance of Error, and it's a common practice for defining custom error objects. See below:

var utils = require('util');
var errorTag = '[object Error]';
var objectProto = Object.prototype;
var objToString = objectProto.toString;

function isError(value) {
  return typeof value.message == 'string' && objToString.call(value) == errorTag;
}

function FooBarError(message) {
  this.message = message;
}

util.inherits(FooBarError, Error);
(new FooBarError('test') instanceof Error);
// true

Tested with iojs v1.6.2 and node 0.12.

@boneskull
Copy link
Member Author

@danielstjules my bad. I got like 3 hours of sleep, sorry.

@boneskull
Copy link
Member Author

it's a common practice for defining custom error objects

I would vehemently argue it's a poor one

@danielstjules
Copy link
Contributor

No worries haha I know the feeling.

@danielstjules
Copy link
Contributor

I would vehemently argue it's a poor one

I don't think inheriting the prototype from Error is odd at all. That's exactly how you should be able to define custom errors in JS. I agree that defining plain old objects with a message property alone is a terrible approach though. But for a catch-all solution, I think we're stuck with only checking for a message property. Which I understand def sucks lol

@boneskull
Copy link
Member Author

I guess we're getting philosophical.

You can do it, but look at the AssertionError constructor, and all the garbage you need in there to make it work properly. Of course, the same code won't necessarily work in any given browser, or against any given JVM. at the top of the file:

// THIS IS NOT TESTED NOR LIKELY TO WORK OUTSIDE V8!

I don't think inheriting the prototype from Error is odd at all. That's exactly how you should be able to define custom errors in JS. I agree that defining plain old objects with a message property alone is a terrible solution. But for a catch-all solution, I think we're stuck with only checking for a message property.

It's not "odd"--it's commonplace, as you said--it's just completely unnecessary. Find the usages of AssertionError in the io.js source. now, pretend the thing which threw the AssertionError threw this instead:

var assertionError = new Error();
assertionError.type = 'AssertionError';
throw assertionError;

Then, look at those usages again and tell me where they couldn't simply compare type instead of name. My point is that there's absolutely no reason to "subclass" Error, especially considering how weak the catch keyword is.

@boneskull
Copy link
Member Author

(even in ES6, the value is marginal is probably performance-related, because the language doesn't have things like try/catch/catch/catch blocks)

@danielstjules
Copy link
Contributor

Fair enough. :) I agree that "sub-classing" Error doesn't get you much given how weak try/catch is in JS.

As for AssertionError - that constructor seems over-complicated. Just inheriting the proto works in v8 for capturing the stack trace. SpiderMonkey doesn't seem to care for it though.

function FooBarError(message) { this.message = message; }
FooBarError.prototype = new Error();

function test() {
  throw new FooBarError('oh no!');
}

test();

@danielstjules
Copy link
Contributor

As a result of the behavior shown in #1770 (comment) and #1770 (comment), do you agree that we'll have to resort to solely checking for a message property on the object?

@boneskull
Copy link
Member Author

Yeah I guess. It depends how opinionated we want to get. As a testing framework we probably shouldn't be dictating how people write their code. My point stands that subclassing Error in JS is a fairly worthless exercise, but I don't think we need to punish users if they choose to do it.

@boneskull
Copy link
Member Author

On the flip side though, I'm 99.9999% sure throwing a string has no use case and shouldn't be supported. 😄

@danielstjules
Copy link
Contributor

Agreed!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants