I came across this highly rated
can-a-1-a-2-a-3-ever-evaluate-to-true stackoverflow post about a month ago. It really opened my eyes on the number of possible solutions. Thus, I would like to share with you.
 |
StackOverflow Question |
Question
Is it ever possible that (a== 1 && a ==2 && a==3) could evaluate to true in JavaScript?
Possible Solutions
1) Using
Loose equality == and Object's prototype method
valueOf or
toString
const a = {
value: 1,
valueOf: function () {
return a.value++;
}
}
OR
const a = {
value: 1,
toString: function () {
return a.value++;
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('It works!');
}
How does this work?
When a (an object) is being compared to 1 (a number) on the right, JavaScript engine will try to convert the type of the operand(due to loose equality) so that they are comparable. First, valueOf will be called, if it fails, toString will be called. When the type of both operands are the same, it will do equality check.
In this case, whenever an equality check is done, a.value is being increased and thus resulting in a == 1, a == 2, a == 3 and so on.
Another variations of defining valueOf
function A() {
var value = 0;
this.valueOf = function () { return ++value; };
}
var a = new A();
if (a == 1 && a == 2 && a == 3) {
console.log('It works!');
}
ES6 class
class A {
constructor() {
this.value = 0;
this.valueOf();
}
valueOf() {
return this.value++;
};
}
let a = new A();
if (a == 1 && a == 2 && a == 3) {
console.log('It works!');
}
Symbol.toPrimitive
From MDN: It is a symbol that specifies a function valued property that is called to convert an object to a corresponding primitive value.
let a = {
[Symbol.toPrimitive]: ((i) => () => ++i) (0)
};
if (a == 1 && a == 2 && a == 3) {
console.log('It works!');
}
An example of Symbol.toPrimitive() taken from MDN
// An object with Symbol.toPrimitive property.
var obj2 = {
[Symbol.toPrimitive](hint) {
if (hint == 'number') {
return 10;
}
if (hint == 'string') {
return 'hello';
}
return true;
}
};
console.log(+obj2); // 10 -- hint is "number"
console.log(`${obj2}`); // "hello" -- hint is "string"
console.log(obj2 + ''); // "true" -- hint is "default"
2) Using
pop with an array and Object's prototype method
valueOf
Array.prototype.pop() removes and returns the last item from the array.
const a = {
arr: [3,2,1],
toString: function () {
return a.arr.pop();
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('It works!');
}
3) Using
Loose equality == and Overriding array
join function with array
shift function
In this case, when a is being compared, it will be converted to a string which is done via join. However, since join had been overrode by shift. It essentially removes and returns the first element in the array.
a = [1,2,3];
a.join = a.shift;
if (a == 1 && a == 2 && a == 3) {
console.log('It works!');
}
4)
Object.defineProperty with an
accessor descriptor
var value = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++value;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('It works!');
}
Using
with which is not recommended. with states the main object / scope of the enclosing statements
var value = 0;
with({
get a() {
return ++value;
}
}) {
if (a == 1 && a == 2 && a == 3)
console.log('It works!');
}
getter function of 'a' increases its value everytime its value is retrieved.
5) Using self-overwriting getter functions
In this case, it is essentially reset / overwrite its getter function the first three times variable 'a' is called.
(() => {
'use strict';
Object.defineProperty(this, 'a', {
'get': () => {
Object.defineProperty(this, 'a', {
'get': () => {
Object.defineProperty(this, 'a', {
'get': () => {
return 3;
}
});
return 2;
},
configurable: true
});
return 1;
},
configurable: true
});
if (a == 1 && a == 2 && a == 3) {
document.body.append('It works!');
}
})();
6)
Halfwidth and fullwidth forms:abc
Taken from Wikipedia:
In CJK (Chinese, Japanese and Korean) computing, graphic characters are traditionally classed into fullwidth and halfwidth characters. With fixed-width fonts, a halfwidth character occupies half the width of a fullwidth character, hence the name.
This is essentially saying these 3 looking alike 'a' variables are technically 3 different characters with space before, after and no space around the character 'a'.
var a_ = 1;
var a = 2;
var _a = 3;
if (a_==1 && a== 2 &&_a==3) {
console.log('It works!')
}
You can use this
string validator by Mathias to validate if a string is valid in JavaScript
7)
Zero-width joiner
In this case, they are essentially visually alike characters but they contain zero-width character(s) which are invisible.
var a= 1;
var a= 2; //one zero-width character
var a= 3; //two zero-width characters (or you can use the other one)
if (a==1 && a==2 && a==3) {
console.log('It works!')
}
Check out this
StackOverflow thread for another example
var ab, /* ab */
ab, /* a‍b */
ab; /* a‌b */
8)
Unicode Variant Selectors / homoglyphs
Taken from Wikipedia:
Variation Selectors is a Unicode block containing 16 Variation Selector format characters (designated VS1 through VS16). They are used to specify a specific glyph variant for a Unicode character.
A set of unicode character variations is essentially a set of looking very similar visually characters.
9) Using Regex with global flag
This is quite tricky as it works because of global flag. Every time a match is found, the
lastIndex of the regex match is updated. This causes the next search steps forward one digit after each match.
The first match is this.r.lastIndex == 0
var a = {
r: /\d/g,
valueOf: function(){
return this.r.exec(123)[0]
}
}
if (a == 1 && a == 2 && a == 3) {
console.log('It works!')
}
10) Using a
generator
Taken from MDN:
Generator: allows us to define an iterative algorithm by writing a single function which can maintain its own state.
const value = function* () {
let i = 0;
while(true) yield ++i;
}();
Object.defineProperty(window, 'a', {
get() {
return value.next().value;
}
});
if (a === 1 && a === 2 && a === 3) {
console.log('It works!');
}
11)
Multi-threading possible bug
The author mentioned that on one of his attempts, it happened after 10 billion iterations.
main.js
// Main Thread
const worker = new Worker('worker.js')
const modifiers = [new Worker('modifier.js'), new Worker('modifier.js')] // Let's use 2 workers
const sab = new SharedArrayBuffer(1)
modifiers.forEach(m => m.postMessage(sab))
worker.postMessage(sab)
worker.js
let array
Object.defineProperty(self, 'a', {
get() {
return array[0]
}
});
addEventListener('message', ({data}) => {
array = new Uint8Array(data)
let count = 0
do {
var res = a == 1 && a == 2 && a == 3
++count
} while(res == false) // just for clarity. !res is fine
console.log(`It happened after ${count} iterations`)
console.log('You should\'ve never seen this')
})
modifier.js
addEventListener('message' , ({data}) => {
setInterval( () => {
new Uint8Array(data)[0] = Math.floor(Math.random()*3) + 1
})
})
Summary
There was
this answer that summarized the mentioned solutions into
i) Encoding issue
ii) Multi-threading issue - race condition (using web workers) which was
this answer
iii) Side-effects of equality comparison operation
Thank you for reading!
Jun
Comments
Post a Comment