When JavaScript regex can surprise you - a silly bug story

elwin013

Kamil

Posted on February 15, 2023

When JavaScript regex can surprise you - a silly bug story

Recently one of my customers let me know that they have probably a bug in their app. They want me to look into it - so I jump into “Sherlock, the bug hunter” mode and start snooping!

The misbehaving feature was some kind of search. It takes an input, a Javascript regular expression and as an output returns true if regex was satisfied - and false otherwise.

The feature was pretty simple on the code side - the search function looked similar to the one below:

var search = function(regex, text) {
    if (regex && regex.test(text)) {
        return true;
    }
    return false;
}
Enter fullscreen mode Exit fullscreen mode

So we can see that the heart of it was a simple regex.test call.

The bug report stated that misbehaving example was regex /pony/g and text this is a pony and not a horse - we have 100% sure that pony is in this sentence. To check if everything was working correctly I opened console in Chrome Developer Tools, loaded the search function and check its result:

// Below returns true
search(/pony/g, "this is a pony and not a horse"); 
// this also returns true!
search(/pony/g, "this is a pony and not a horse"); 
// ... five runs later
// eh? still returns true!
search(/pony/g, "this is a pony and not a horse"); 
Enter fullscreen mode Exit fullscreen mode

What is going on?! Every run of this function returns the correct and expected value.

But wait… the regex and text was passed using variables from somewhere else. Let’s put them in the variables and try to run the function multiple times again:

var regex = /pony/g
var text = "this is a pony and not a horse";
search(regex, pony); // returns true as expected
search(regex, pony); // returns false :O
search(regex, pony); // returns true - what a trickery!
search(regex, pony); // returns false - wut?!
Enter fullscreen mode Exit fullscreen mode

And now we have a problem - despite the fact we should get true every time we sometimes get false instead.

The answer to this weird behaviour is in the… documentation. Quick look at the RegExp.prototype.test() on mdn web docsand we have found the culprit:

JavaScript RegExp objects are stateful when they have the global or sticky flags set (e.g., /foo/g or /foo/y). They store a lastIndex from the previous match

And the latIndex property is described as:

The lastIndex data property of a RegExp instance specifies the index at which to start the next match.

We have the global option in our regex - and that was the reason that it behaves in a not expected manner. Similar results will be if our test text were this is a pony and second pony - it will give us true for the first and the second search (as we have two ponies!) and false for the third one.

The solution for this issue was simply to reset the lastIndex property of regex if something was found

var search = function(regex, text) {
    if (regex && regex.test(text)) {
        // reset lastIndex property to make sure 
        // that next test will start from beginning
        regex.lastIndex = 0; 
        return true;
    }
    return false;
}
Enter fullscreen mode Exit fullscreen mode

That’s all folks! One line, resetting one property, is all that was needed to fix the issue.

What is the takeaway from this issue? When in doubt and especially when working with regexes - read documentation. It can save you a lot of time. :-)

💖 💪 🙅 🚩
elwin013
Kamil

Posted on February 15, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related