06.03.11

JavaScript change in Firefox 5 (not 4), and in other browsers: regular expressions can’t be called like functions

Callable regular expressions

Way back in the day when Netscape implemented regular expressions in JavaScript, it made them callable. If you slapped an argument list after a regular expression, it’d act as if you called RegExp.prototype.exec on it with the provided arguments.

var r = /abc/, res;

res = r("abc");
assert(res.length === 1);

res = r("def");
assert(res === null);

Why? Beats me. I’d have thought .exec was easy enough to type and clearer to boot, myself. Hopefully readers familiar with the history can explain in comments.

Problems

Callable regular expressions present one immediate problem to a “naive” implementation: their behavior with typeof. According to ECMAScript, the typeof for any object which is callable should be "function", and Netscape and Mozilla for a long time faithfully implemented this. This tended to cause much confusion in practice, so browsers that implemented callable regular expressions eventually changed typeof to arguably “lie” for regular expressions and return "object". In SpiderMonkey the “fix” was an utterly inelegant hack which distinguished callables as either regular expressions or not, to determine typeof behavior.

Past this, callable regular expressions complicate implementing callability and optimizations of it. Implementations supporting getters and setters (once purely as an extension, now standardized in ES5) must consider the case where the getter or setter is a regular expression and do something appropriate. And of course they must handle regular old calls, qualified (/a/()) and unqualified (({ p: /a/ }).p()) both. Mozilla’s had a solid trickle of bugs involving callable regular expressions, almost always filed as a result of Jesse‘s evil fuzzers (and not due to actual sites breaking).

It’s also hard to justify callable regular expressions as an extension. While ECMAScript explicitly permits extensions, it generally prefers extensions to be new methods or properties of existing objects. Regular expression callability is neither of these: instead it’s adding an internal hook to regular expressions to make them callable. This might not technically be contrary to the spec, but it goes against its spirit.

Regular expressions won’t be callable in Firefox 5

No one’s ever really used callable regular expressions. They’re non-standard, not all browsers implement them, and they unnecessarily complicate implementations. So, in concert with other browser engines like WebKit, we’re making regular expressions non-callable in Firefox 5. (Regular expressions are callable in Firefox 4, but of course don’t rely on this.)

You can experiment with a version of Firefox with these changes by downloading a TraceMonkey nightly build. Trunk’s still locked down for Firefox 4, so it won’t pick up the change until Firefox 4 branches and trunk reopens for changes targeted at the next release. (Don’t forget to use the profile manager if you want to keep the settings you use with your primary Firefox installation pristine.)

6 Comments »

  1. Sad to see all the various nice bits of JS (especially for extensions, where web-compat isn’t a concern) go because optimizing things is Hard. This means we lose nice things like [...].some(/^\w+$/) (hey, it’s callable, of course it can stand in for callbacks).

    I would consider that to be a perfectly valid extension: it’s extending the [[Call]] internal property of RegExp.prototype :)

    In the mean time, I guess I need to go figure out how to work with proxies. (I think I’m doing it wrong – wrapping and toString()ing Boolean, Number, String, RegExp, or Date breaks with “TypeError on line ???: ???.prototype.toString called on incompatible Proxy”, but Array, Object, and Function are fine; wrapping window works for some things and not others.)

    Comment by Mook — 06.03.11 @ 21:32

  2. Is it such a burden to do [...].some(function(v) { return /^\w+$/.exec(v); })? It’s certainly much clearer. And of course, this only happens to work because exec ignores the trailing index and this parameters some passes to it.

    Another thought: isn’t this sort of playing with fire, in that a regular expression literal like this implicitly carries around a lastIndex within it?

    It’s not so much about the validity of the extension. It’s about its nature. I think the typeof snafu demonstrates why making a specified-uncallable object callable is not a good idea.

    Your proxy examples don’t work because the spec requires in those places that the this object have a particular class, and proxies aren’t transparent for that. You could raise the concern on es-discuss if you wanted. I believe others have done this for arrays ([].concat has special behavior conditioned on class-is Array) in the past. This issue irritates me a bit, because the only way I see to address it is to have every place that does a class-check do…something other than an exact class-check. I don’t know what that would be. And requiring every place to do it is just asking for one place or another to be missed. Maybe this is just the rough edges of trying to retrofit a (new, different) layer of extensibility atop an older system not designed with that in mind.

    Comment by Jeff — 06.03.11 @ 22:17

  3. …although I should say, since regular expression callability uses exec and not test, your hack is not going to be as efficient as [...].some(function(v) { return /^\w+$/.test(v); }), which is even better than my suggestion, and better than yours as well.

    Comment by Jeff — 06.03.11 @ 22:21

  4. Then extend some() to take a regular expression as an argument. Everybody happy. Constructive thinking, guys :).

    Comment by Laurens Holst — 07.03.11 @ 11:25

  5. Sorry for the off topic, and I am sorry to point it, but your website is absolutely not scrollable. Or, it scrolls, but is exceptionally slow — on a top-end machine with Google Chrome.

    Comment by Martin — 07.03.11 @ 13:27

  6. Strange, works fine for me, in Firefox, Chrome, Opera, and Epiphany all. I think I’ve seen it slightly laggy to scroll in older versions, at times, but not so horribly as to seriously interfere with usability.

    Do note, however, that any code I write for the site (in posts, in the theme I use, etc.) I write primarily for “reasonably” “good” browsers. And I’m not averse to using the site to “nudge” viewers toward better browsers (for example, if I embed my own video, I use <video> to do it, without fallback beyond a “get a new browser” link, and I only provide “good” video formats like Ogg Theora [historically] or WebM [in the future]). So if something doesn’t work, for my own personal site, I’m as likely to recommend a browser upgrade as I am to fix whatever’s broken. I absolutely wouldn’t recommend this for most sites, and indeed I’d usually strongly discourage doing so. But let’s face it: nothing I say is so incredibly important that the site absolutely must work for everyone. So I’ll keep using it as my own little sandbox where I play with cool new stuff (like a fixed background with background-size: cover, which I’m guessing is what causes woes for you), to some extent, for the foreseeable future.

    Comment by Jeff — 07.03.11 @ 20:03

RSS feed for comments on this post. TrackBack URI

Leave a comment

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>