ti-enxame.com

Pode (a == 1 && a == 2 && a == 3) avaliar como verdadeiro?

Nota do moderador: Por favor, resista ao desejo de editar o código ou remover este aviso. O padrão de espaço em branco pode ser parte da questão e, portanto, não deve ser adulterado desnecessariamente. Se você está no campo "espaço em branco é insignificante", você deve ser capaz de aceitar o código como está.

É possível que (a== 1 && a ==2 && a==3) possa avaliar a true em JavaScript?

Esta é uma pergunta de entrevista feita por uma grande empresa de tecnologia. Aconteceu duas semanas atrás, mas ainda estou tentando encontrar a resposta. Eu sei que nunca escrevemos esse código em nosso trabalho do dia-a-dia, mas estou curioso.

2331
Dimpu Aravind Buddha

Se você tirar proveito de como == funciona , você poderia simplesmente criar um objeto com uma função toString (ou valueOf) personalizada que muda o que ele retorna toda vez que é usado, de forma que satisfaça todas as três condições.

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

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


A razão pela qual isso funciona é devido ao uso do operador de igualdade solto. Ao usar igualdade solta, se um dos operandos for de um tipo diferente do outro, o mecanismo tentará converter um para o outro. No caso de um objeto à esquerda e um número à direita, ele tentará converter o objeto em um número, chamando primeiro valueOf, se ele for chamado, e, na sua falta, ele chamará toString. Eu usei toString neste caso simplesmente porque é o que veio à mente, valueOf faria mais sentido. Se, em vez disso, eu retornasse uma string de toString, o mecanismo teria tentado converter a string em um número que nos desse o mesmo resultado final, embora com um caminho um pouco mais longo.

3185
Kevin B

Eu não pude resistir - as outras respostas são indubitavelmente verdadeiras, mas você realmente não pode passar pelo seguinte código:

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

Observe o espaçamento estranho na instrução if (que copiei da sua pergunta). É o hangul de meia largura (que é coreano para quem não é familiar), que é um caractere de espaço Unicode que não é interpretado pelo script ECMA como um caractere de espaço - isso significa que é um caractere válido para um identificador. Portanto, existem três variáveis ​​completamente diferentes, uma com o Hangul após o a, um com o anterior e o último com apenas um. Substituindo o espaço com _ para legibilidade, o mesmo código ficaria assim:

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

Check out a validação no validador de nome de variável do Mathias . Se esse espaçamento estranho foi realmente incluído em sua pergunta, tenho certeza que é uma dica para esse tipo de resposta.

Não faça isso. A sério.

Edit: Chegou ao meu conhecimento que (embora não seja permitido iniciar uma variável) o Marcador de largura zero e Zero-largura não-marceneiro caracteres também são permitidos em nomes de variáveis ​​- ver Ofuscação JavaScript com caracteres de largura zero - prós e contras? .

Isso seria parecido com o seguinte:

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

1969
Jeff

IT IS POSSÍVEL!

var i = 0;

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

Isso usa um getter dentro de uma instrução with para permitir que a avalie três valores diferentes.

... isso ainda não significa que isso deva ser usado em código real ...

Pior ainda, esse truque também funcionará com o uso de ===.

  var i = 0;

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

591
Jonas Wilms

Exemplo sem getters ou valueOf:

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

Isso funciona porque == invoca toString, que chama .join para Arrays.

Outra solução, usando Symbol.toPrimitive que é um equivalente ES6 de toString/valueOf

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

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

458
georg

Se for perguntado se é possível (não DEVE), pode pedir "a" para retornar um número aleatório. Seria verdade se gerasse 1, 2 e 3 seqüencialmente.

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

259
mmmaaa

Quando você não pode fazer nada sem expressões regulares:

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

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

Ele funciona devido ao método custom valueOf que é chamado quando Object é comparado com primitivo (como Number). O truque principal é que a.valueOf retorna um novo valor toda vez porque ele está chamando exec na expressão regular com g flag, o que causa a atualização de lastIndex dessa expressão regular toda vez que a correspondência é encontrada. Então, pela primeira vez this.r.lastIndex == 0, ele corresponde a 1 e atualiza lastIndex: this.r.lastIndex == 1, então o próximo regex de tempo corresponderá a 2 e assim por diante.

203
Kos

Isso pode ser feito usando o seguinte no escopo global. Para nodejs use global em vez de window no código abaixo.

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

Essa resposta abusa as variáveis ​​implícitas fornecidas pelo escopo global no contexto de execução definindo um getter para recuperar a variável.

186
jontro

Isso é possível no caso da variável a ser acessada por, digamos, 2 web workers através de um SharedArrayBuffer, assim como algum script principal. A possibilidade é baixa, mas é possível que quando o código é compilado para código de máquina, os web workers atualizem a variável a apenas no tempo, para que as condições a==1, a==2 e a==3 sejam satisfeitas.

Isso pode ser um exemplo de condição de corrida em ambiente multi-threaded fornecido por web workers e SharedArrayBuffer em JavaScript.

Aqui está a implementação básica de cima:

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

No meu MacBook Air, isso acontece após cerca de 10 bilhões de iterações na primeira tentativa:

enter image description here

Segunda tentativa:

enter image description here

Como eu disse, as chances serão baixas, mas, se houver tempo suficiente, ele atingirá a condição.

Dica: se demorar muito no seu sistema. Tente apenas a == 1 && a == 2 e mude Math.random()*3 para Math.random()*2. Adicionar mais e mais à lista diminui a chance de acertar.

182
mehulmpt

Isso também é possível usando uma série de getters de auto-substituição:

(Isso é semelhante à solução do jontro, mas não requer uma variável de contador.)

(() => {
    "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.");
    }
})();

145
Patrick Dark

Eu não vejo esta resposta já postada, então eu vou jogar essa aqui também. Isso é semelhante à resposta de Jeff com o espaço Hangul de meia largura.

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

Você pode notar uma pequena discrepância com o segundo, mas o primeiro e o terceiro são idênticos a olho nu. Todos os 3 são personagens distintos:

a - minúscula latina A
- Largura total em latim minúsculas A
а - minúsculas cirílicas A

O termo genérico para isso é "homoglifos": diferentes caracteres unicode que parecem os mesmos. Normalmente é difícil obter three que são totalmente indistinguíveis, mas em alguns casos você pode ter sorte. A, Α, e Ꭺ funcionariam melhor (Latim-A, Alfa Grego , Cirílico-A , e Cherokee-A respectivamente; infelizmente as letras minúsculas em grego e Cherokee) são muito diferentes do latim a: α, e, portanto, não ajuda com o snippet acima).

Existe uma classe inteira de Homoglyph Attacks, mais comumente em nomes de domínio falsos (por exemplo, wikipediа.org (cirílico) vs wikipedia.org (em latim)), mas também pode aparecer no código; normalmente chamado de underhanded (como mencionado em um comentário, [underhanded] questions estão fora do tópico em PPCG , mas costumava ser um tipo de desafio onde esses tipos de coisas mostrariam acima). Eu usei este site para encontrar os homoglifos usados ​​para esta resposta.

127
Draco18s

Alternativamente, você poderia usar uma classe para ela e uma instância para o cheque.

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

Usando classes ES6, ficaria assim

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

123
Nina Scholz

JavaScript

a == a +1

Em JavaScript, não há inteiros mas apenas Numbers, que são implementados como números de ponto flutuante de precisão dupla.

Isso significa que se um número a for grande o suficiente, ele pode ser considerado igual a três inteiros consecutivos:

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

É verdade que não é exatamente o que o entrevistador pediu (não funciona com a=0), mas não envolve truques com funções ocultas ou sobrecarga de operadores.

Outras línguas

Para referência, existem soluções a==1 && a==2 && a==3 em Ruby e Python. Com uma pequena modificação, também é possível em Java.

Rubi

Com um == personalizado:

class A
  def ==(o)
    true
  end
end

a = A.new

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

Ou um crescente 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

É possível modificar o 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.");
        }
    }
}
93
Eric Duminil

Sim, é possível! ????

»JavaScript

if‌=()=>!0;
var a = 9;

if‌(a==1 && a== 2 && a==3)
{
    document.write("<h1>Yes, it is possible!????</h1>")
}

O código acima é uma versão curta (graças a @Forivin por sua nota nos comentários) e o seguinte código é original:

var a = 9;

if‌(a==1 && a== 2 && a==3)
{
    //console.log("Yes, it is possible!????")
    document.write("<h1>Yes, it is possible!????</h1>")
}

//--------------------------------------------

function if‌(){return true;}

Se você acabou de ver o lado superior do meu código e executá-lo você diz WOW, como?

Então eu acho que é o suficiente para dizer Sim, é possível para alguém que disse para Você: Nada é impossível

Truque: Eu usei um caractere oculto após if para fazer uma função que seu nome é semelhante a if. Em JavaScript não podemos sobrescrever palavras-chave, então obriguei a usar dessa maneira. É um if falso, mas funciona para você neste caso!


»C #

Também escrevi uma versão em C # (com aumentar a técnica do valor da propriedade):

static int _a;
public static int a => ++_a;

public static void Main()
{
    if(a==1 && a==2 && a==3)
    {
        Console.WriteLine("Yes, it is possible!????");
    }
}

Demonstração ao vivo

90
RAM

Esta é uma versão invertida da resposta de @ Jeff * onde um caractere oculto (U + 115F, U + 1160 ou U + 3164) é usado para criar variáveis ​​que parecem 1, 2 e 3.

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

* Essa resposta pode ser simplificada usando o zero-joiner de largura (U + 200C) e o joiner de largura zero (U + 200D). Ambos os caracteres são permitidos dentro de identificadores, mas não no começo:

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

/****
var a = 1;
var a\u200c = 2;
var a\u200d = 3;
console.log(a == 1 && a\u200c == 2 && a\u200d == 3);
****/

Outros truques são possíveis usando a mesma ideia, por ex. usando seletores de variação Unicode para criar variáveis ​​exatamente iguais (a︀ = 1; a︁ = 2; a︀ == 1 && a︁ == 2; // true).

78
Salman A

Regra número um das entrevistas; nunca diga impossível.

Não há necessidade de truques de caracteres ocultos.

window.__defineGetter__( 'a', function(){
    if( typeof i !== 'number' ){
        // define i in the global namespace so that it's not lost after this function runs
        i = 0;
    }
    return ++i;
});

if( a == 1 && a == 2 && a == 3 ){
    alert( 'Oh dear, what have we done?' );
}

72
MonkeyZeus

Honestamente, se há uma maneira de avaliar ou não a verdade (e, como outros demonstraram, existem várias maneiras), a resposta que eu estaria procurando, falando como alguém que conduziu centenas de entrevistas, seria algo ao longo das linhas de:

"Bem, talvez sim sob algumas circunstâncias estranhas que não são imediatamente óbvias para mim ... mas se eu encontrasse isso em código real, eu usaria técnicas de depuração comuns para descobrir como e por que ele estava fazendo o que estava fazendo e então refatorie imediatamente o código para evitar essa situação ... mas mais importante: eu absolutamente NUNCA escreverei esse código em primeiro lugar porque essa é a própria definição de código complicado, e eu me esforço para nunca escrever um código complicado ".

Eu acho que alguns entrevistadores se ofenderiam por ter o que obviamente era uma pergunta muito complicada, mas eu não me importo com os desenvolvedores que têm uma opinião, especialmente quando eles podem apoiar isso com pensamento racional e podem encaixar minha pergunta em uma declaração significativa sobre si mesmos.

66
Frank W. Zammetti

Aqui está outra variação, usando uma matriz para exibir os valores desejados.

const a = {
  n: [3,2,1],
  toString: function () {
    return a.n.pop();
  }
}

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

41
Théophile

Se você tiver uma pergunta sobre essa entrevista (ou perceber algum comportamento igualmente inesperado em seu código), pense em que tipo de coisa poderia causar um comportamento que parece impossível à primeira vista:

  1. Encoding: Neste caso, a variável que você está vendo não é aquela que você pensa que é. Isso pode acontecer se você intencionalmente mexer em Unicode usando homoglyphs ou space caracteres para fazer com que o nome de uma variável pareça com outro, mas problemas de codificação também podem ser introduzidos acidentalmente, por exemplo, ao copiar e colar código da Web que contém pontos de código Unicode inesperados (por exemplo, porque um sistema de gerenciamento de conteúdo fez alguma "formatação automática", como substituir fl por Unicode 'LATIN SMALL LIGATURE FL' (U + FB02)).

  2. Condições da corrida: A condição de corrida pode ocorrer, ou seja, uma situação em que o código não está sendo executado na sequência esperada pelo desenvolvedor. As condições de corrida geralmente acontecem no código multi-thread, mas múltiplas threads não são necessárias para que condições de corrida sejam possíveis - a assincronia é suficiente (e não se confunda, async não significa que vários threads são usados ​​sob o capô ). 

    Observe que, portanto, o JavaScript também não está livre de condições de corrida apenas porque é single-threaded. Veja aqui para um exemplo simples de um único segmento - mas assíncrono. No contexto de uma única declaração, a condição de corrida seria difícil de acertar em JavaScript.

    JavaScript com web workers é um pouco diferente, já que você pode ter vários threads. @mehulmpt nos mostrou uma ótima prova de conceito usando web workers .

  3. Efeitos colaterais: Um efeito colateral da operação de comparação de igualdade (que não precisa ser tão óbvia quanto nos exemplos aqui, geralmente os efeitos colaterais são muito sutis). 

Esses tipos de problemas podem aparecer em muitas linguagens de programação, não apenas em JavaScript, então não estamos vendo um dos clássicos JavaScript WTFs aqui1

É claro que a pergunta da entrevista e as amostras aqui parecem muito artificiais. Mas eles são um bom lembrete de que:

  • Os efeitos colaterais podem ficar realmente desagradáveis ​​e um programa bem projetado deve estar livre de efeitos colaterais indesejados.
  • O estado multi-threading e mutável pode ser problemático.
  • Não fazer a codificação de caracteres e o processamento de strings direito pode levar a erros desagradáveis.

1 Por exemplo, você pode encontrar um exemplo em uma linguagem de programação totalmente diferente (C #) exibindo um efeito colateral (um óbvio) aqui .

37
Dirk Vollmar

Ok, outro hack com geradores:

const value = function* () {
  let i = 0;
  while(true) yield ++i;
}();

Object.defineProperty(this, 'a', {
  get() {
    return value.next().value;
  }
});

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

31
BaggersIO

Na verdade, a resposta para a primeira parte da pergunta é "sim" em todas as linguagens de programação. Por exemplo, isso é no caso do C/C++:

#define a   (b++)
int b = 1;
if (a ==1 && a== 2 && a==3) {
    std::cout << "Yes, it's possible!" << std::endl;
} else {
    std::cout << "it's impossible!" << std::endl;
}
27
Gustavo Rodríguez

Usando Proxies :

var a = new Proxy({ i: 0 }, {
    get: (target, name) => name === Symbol.toPrimitive ? () => ++target.i : target[name],
});
console.log(a == 1 && a == 2 && a == 3);

Proxies basicamente fingem ser um objeto de destino (o primeiro parâmetro), mas interceptam as operações no objeto de destino (nesse caso a operação "get property") para que haja uma oportunidade de fazer algo diferente do comportamento do objeto padrão. Nesse caso, a ação "get property" é chamada em a quando == coage seu tipo para compará-lo a cada número. Isto acontece:

  1. Criamos um objeto de destino, { i: 0 }, onde a propriedade i é nosso contador
  2. Criamos um proxy para o objeto de destino e o atribuímos a a
  3. Para cada comparação a ==, o tipo a é forçado para um valor primitivo
  4. Este tipo de coerção resulta em chamar a[Symbol.toPrimitive]() internamente
  5. O Proxy intercepta a função a[Symbol.toPrimitive] usando o "get handler"
  6. O "get manipulador" do Proxy verifica se a propriedade que está sendo obtida é Symbol.toPrimitive e, nesse caso, incrementa e retorna o contador do objeto de destino: ++target.i. Se uma propriedade diferente está sendo recuperada, nós apenas retornamos para retornar o valor da propriedade padrão, target[name]

Assim:

var a = ...; // a.valueOf == target.i == 0
a == 1 && // a == ++target.i == 1
a == 2 && // a == ++target.i == 2
a == 3    // a == ++target.i == 3

Como com a maioria das outras respostas, isso só funciona com uma verificação de igualdade solta (==), porque verificações de igualdade estritas (===) não fazem coerção de tipo que o proxy pode interceptar.

27
IceCreamYou

Mesmo, mas diferente, mas ainda é o mesmo (pode ser "testado" várias vezes):

const a = { valueOf: () => this.n = (this.n || 0) % 3 + 1}
    
if(a == 1 && a == 2 && a == 3) {
  console.log('Hello World!');
}

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

Minha ideia começou a partir de como a equação do tipo de objeto Number funciona.

26
Preda7or

Uma resposta do ECMAScript 6 que faz uso de símbolos:

const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));

Devido ao uso de ==, JavaScript deve forçar a em algo próximo ao segundo operando (1, 2, 3 neste caso). Mas antes do JavaScript tentar descobrir a coerção sozinho, ele tenta chamar Symbol.toPrimitive . Se você fornecer Symbol.toPrimitive, o JavaScript usaria o valor que sua função retorna. Caso contrário, o JavaScript chamaria valueOf .

23
Omar Alshaker

Eu acho que este é o código mínimo para implementá-lo:

i=0,a={valueOf:()=>++i}

if (a == 1 && a == 2 && a == 3) {
  console.log('Mind === Blown');
}

Criando um objeto fictício com uma valueOf personalizada que incrementa uma variável global i em cada chamada. 23 caracteres!

23
Gaafar

Este usa o defineProperty com um bom efeito colateral causando uma variável global!

var _a = 1

Object.defineProperty(this, "a", {
  "get": () => {
    return _a++;
  },
  configurable: true
});

console.log(a)
console.log(a)
console.log(a)

11
Ben Aubin

Sobrescrevendo valueOf em uma declaração de classe, isso pode ser feito:

class Thing {
    constructor() {
        this.value = 1;
    }

    valueOf() {
        return this.value++;
    }
}

const a = new Thing();

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

O que acontece é que valueOf é chamado em cada operador de comparação. No primeiro, a será igual a 1, no segundo, a será igual a 2, e assim por diante, porque cada vez que valueOf é chamado, o valor de a é incrementado.

Portanto, o console.log irá disparar e sair (no meu terminal de qualquer maneira) Thing: { value: 4}, indicando que a condicional era verdadeira.

0
Jonathan Kuhl