Can (a== 1 && a ==2 && a==3) ever evaluate to true

2019-01-01 09:48发布

问题:

Moderator note: Please resist the urge to edit the code or remove this notice. The pattern of whitespace may be part of the question and therefore should not be tampered with unnecessarily. If you are in the \"whitespace is insignificant\" camp, you should be able to accept the code as is.

Is it ever possible that (a== 1 && a ==2 && a==3) could evaluate to true in JavaScript?

This is an interview question asked by a major tech company. It happened two weeks back, but I\'m still trying to find the answer. I know we never write such code in our day-to-day job, but I\'m curious.

回答1:

If you take advantage of how == works, you could simply create an object with a custom toString (or valueOf) function that changes what it returns each time it is used such that it satisfies all three conditions.

const a = {
  i: 1,
  toString: function () {
    return a.i++;
  }
}

if(a == 1 && a == 2 && a == 3) {
  console.log(\'Hello World!\');
}


The reason this works is due to the use of the loose equality operator. When using loose equality, if one of the operands is of a different type than the other, the engine will attempt to convert one to the other. In the case of an object on the left and a number on the right, it will attempt to convert the object to a number by first calling valueOf if it is callable, and failing that, it will call toString. I used toString in this case simply because it\'s what came to mind, valueOf would make more sense. If I instead returned a string from toString, the engine would have then attempted to convert the string to a number giving us the same end result, though with a slightly longer path.



回答2:

I couldn\'t resist - the other answers are undoubtedly true, but you really can\'t walk past the following code:

var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
    console.log(\"Why hello there!\")
}

Note the weird spacing in the if statement (that I copied from your question). It is the half-width Hangul (that\'s Korean for those not familiar) which is an Unicode space character that is not interpreted by ECMA script as a space character - this means that it is a valid character for an identifier. Therefore there are three completely different variables, one with the Hangul after the a, one with it before and the last one with just a. Replacing the space with _ for readability, the same code would look like this:

var a_ = 1;
var a = 2;
var _a = 3;
if(a_==1 && a== 2 &&_a==3) {
    console.log(\"Why hello there!\")
}

Check out the validation on Mathias\' variable name validator. If that weird spacing was actually included in their question, I feel sure that it\'s a hint for this kind of answer.

Don\'t do this. Seriously.

Edit: It has come to my attention that (although not allowed to start a variable) the Zero-width joiner and Zero-width non-joiner characters are also permitted in variable names - see Obfuscating JavaScript with zero-width characters - pros and cons?.

This would look like the following:

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(\"Why hello there!\")
}



回答3:

IT IS POSSIBLE!

var i = 0;

with({
  get a() {
    return ++i;
  }
}) {
  if (a == 1 && a == 2 && a == 3)
    console.log(\"wohoo\");
}

This uses a getter inside of a with statement to let a evaluate to three different values.

... this still does not mean this should be used in real code...

Even worse, this trick will also work with the use of ===.

  var i = 0;

  with({
    get a() {
      return ++i;
    }
  }) {
    if (a !== a)
      console.log(\"yep, this is printed.\");
  }



回答4:

Example without getters or valueOf:

a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);

This works because == invokes toString which calls .join for Arrays.

Another solution, using Symbol.toPrimitive which is an ES6 equivalent of toString/valueOf:

let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)};

console.log(a == 1 && a == 2 && a == 3);



回答5:

If it is asked if it is possible (not MUST), it can ask \"a\" to return a random number. It would be true if it generates 1, 2, and 3 sequentially.

with({
  get a() {
    return Math.floor(Math.random()*4);
  }
}){
  for(var i=0;i<1000;i++){
    if (a == 1 && a == 2 && a == 3){
      console.log(\"after \" + (i+1) + \" trials, it becomes true finally!!!\");
      break;
    }
  }
}



回答6:

When you can\'t do anything without regular expressions:

var a = {
  r: /\\d/g, 
  valueOf: function(){
    return this.r.exec(123)[0]
  }
}

if (a == 1 && a == 2 && a == 3) {
    console.log(\"!\")
}

It works because of custom valueOf method that is called when Object compared with primitive (such as Number). Main trick is that a.valueOf returns new value every time because it\'s calling exec on regular expression with g flag, which causing updating lastIndex of that regular expression every time match is found. So first time this.r.lastIndex == 0, it matches 1 and updates lastIndex: this.r.lastIndex == 1, so next time regex will match 2 and so on.



回答7:

It can be accomplished using the following in the global scope. For nodejs use global instead of window in the code below.

var val = 0;
Object.defineProperty(window, \'a\', {
  get: function() {
    return ++val;
  }
});
if (a == 1 && a == 2 && a == 3) {
  console.log(\'yay\');
}

This answer abuses the implicit variables provided by the global scope in the execution context by defining a getter to retrieve the variable.



回答8:

This is possible in case of variable a being accessed by, say 2 web workers through a SharedArrayBuffer as well as some main script. The possibility is low, but it is possible that when the code is compiled to machine code, the web workers update the variable a just in time so the conditions a==1, a==2 and a==3 are satisfied.

This can be an example of race condition in multi-threaded environment provided by web workers and SharedArrayBuffer in JavaScript.

Here is the basic implementation of above:

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
    })
})

On my MacBook Air, it happens after around 10 billion iterations on the first attempt:

\"enter

Second attempt:

\"enter

As I said, the chances will be low, but given enough time, it\'ll hit the condition.

Tip: If it takes too long on your system. Try only a == 1 && a == 2 and change Math.random()*3 to Math.random()*2. Adding more and more to list drops the chance of hitting.



回答9:

This is also possible using a series of self-overwriting getters:

(This is similar to jontro\'s solution, but doesn\'t require a counter variable.)

(() => {
    \"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(\"Yes, it’s possible.\");
    }
})();



回答10:

I don\'t see this answer already posted, so I\'ll throw this one into the mix too. This is similar to Jeff\'s answer with the half-width Hangul space.

var a = 1;
var a = 2;
var а = 3;
if(a == 1 && a == 2 && а == 3) {
    console.log(\"Why hello there!\")
}

You might notice a slight discrepancy with the second one, but the first and third are identical to the naked eye. All 3 are distinct characters:

a - Latin lower case A
- Full Width Latin lower case A
а - Cyrillic lower case A

The generic term for this is \"homoglyphs\": different unicode characters that look the same. Typically hard to get three that are utterly indistinguishable, but in some cases you can get lucky. A, Α, А, and Ꭺ would work better (Latin-A, Greek Alpha, Cyrillic-A, and Cherokee-A respectively; unfortunately the Greek and Cherokee lower-case letters are too different from the Latin a: α,, and so doesn\'t help with the above snippet).

There\'s an entire class of Homoglyph Attacks out there, most commonly in fake domain names (eg. wikipediа.org (Cyrillic) vs wikipedia.org (Latin)), but it can show up in code as well; typically referred to as being underhanded (as mentioned in a comment, [underhanded] questions are now off-topic on PPCG, but used to be a type of challenge where these sorts of things would show up). I used this website to find the homoglyphs used for this answer.



回答11:

Alternatively, you could use a class for it and an instance for the check.

function A() {
    var value = 0;
    this.valueOf = function () { return ++value; };
}

var a = new A;

if (a == 1 && a == 2 && a == 3) {
    console.log(\'bingo!\');
}

EDIT

Using ES6 classes it would look like this

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(\'bingo!\');
}



回答12:

JavaScript

a == a +1

In JavaScript, there are no integers but only Numbers, which are implemented as double precision floating point numbers.

It means that if a Number a is large enough, it can be considered equal to three consecutive integers:

a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
  console.log(\"Precision loss!\");
}

True, it\'s not exactly what the interviewer asked (it doesn\'t work with a=0), but it doesn\'t involve any trick with hidden functions or operator overloading.

Other languages

For reference, there are a==1 && a==2 && a==3 solutions in Ruby and Python. With a slight modification, it\'s also possible in Java.

Ruby

With a custom ==:

class A
  def ==(o)
    true
  end
end

a = A.new

if a == 1 && a == 2 && a == 3
  puts \"Don\'t do this!\"
end

Or an increasing a:

def a
  @a ||= 0
  @a += 1
end

if a == 1 && a == 2 && a == 3
  puts \"Don\'t do this!\"
end

Python

class A:
    def __eq__(self, who_cares):
        return True
a = A()

if a == 1 and a == 2 and a == 3:
    print(\"Don\'t do that!\")

Java

It\'s possible to modify Java Integer cache:

package stackoverflow;

import java.lang.reflect.Field;

public class IntegerMess
{
    public static void main(String[] args) throws Exception {
        Field valueField = Integer.class.getDeclaredField(\"value\");
        valueField.setAccessible(true);
        valueField.setInt(1, valueField.getInt(42));
        valueField.setInt(2, valueField.getInt(42));
        valueField.setInt(3, valueField.getInt(42));
        valueField.setAccessible(false);

        Integer a = 42;

        if (a.equals(1) && a.equals(2) && a.equals(3)) {
            System.out.println(\"Bad idea.\");
        }
    }
}


回答13:

Yes, it is possible!