Nota del moderador: Resista el impulso de editar el código o eliminar este aviso. El patrón de espacios en blanco puede ser parte de la pregunta y, por lo tanto, no debe ser manipulado innecesariamente. Si está en el campo "el espacio en blanco es insignificante", debería poder aceptar el código tal como está.
¿Es posible que (a== 1 && a ==2 && a==3)
pueda evaluar a true
en JavaScript?
Esta es una pregunta de entrevista realizada por una importante empresa de tecnología. Sucedió hace dos semanas, pero todavía estoy tratando de encontrar la respuesta. Sé que nunca escribimos ese código en nuestro trabajo diario, pero tengo curiosidad.
Si aprovecha cómo ==
funciona , simplemente podría crear un objeto con una función personalizada toString
(o valueOf
) que cambie lo que devuelve cada vez que se usa de tal manera que satisfaga las tres condiciones.
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
La razón por la que esto funciona es debido al uso del operador de igualdad suelta. Cuando se usa la igualdad suelta, si uno de los operandos es de un tipo diferente al otro, el motor intentará convertir uno a otro. En el caso de un objeto a la izquierda y un número a la derecha, intentará convertir el objeto en un número llamando primero a valueOf
si es llamable, y si falla, llamará a toString
. Utilicé toString
en este caso simplemente porque es lo que me vino a la mente, valueOf
tendría más sentido. Si, por el contrario, devolviera una cadena de toString
, el motor intentaría convertir la cadena en un número que nos proporcionara el mismo resultado final, aunque con una ruta ligeramente más larga.
No pude resistirme, las otras respuestas son indudablemente ciertas, pero realmente no puedes pasar por alto el siguiente código:
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
console.log("Why hello there!")
}
Tenga en cuenta el espaciado extraño en la declaración if
(que copié de su pregunta). Es un Hangul de ancho medio (que es coreano para los que no están familiarizados) el cual es un carácter de espacio Unicode que no es interpretado por el script ECMA como un carácter de espacio, esto significa que es un carácter válido para un identificador. Por lo tanto, hay tres variables completamente diferentes, una con el Hangul después de la a, una con la anterior y la última con solo una. Al reemplazar el espacio con _
para facilitar la lectura, el mismo código se vería así:
var a_ = 1;
var a = 2;
var _a = 3;
if(a_==1 && a== 2 &&_a==3) {
console.log("Why hello there!")
}
Echa un vistazo a la validación en el validador de nombre de variable de Mathias . Si ese espacio extraño se incluyó realmente en su pregunta, estoy seguro de que es una sugerencia para este tipo de respuesta.
No hagas esto Seriamente.
Edición: Ha llegado a mi atención que (aunque no se le permite iniciar una variable) el Combinación de ancho cero y Sin combinación de ancho de cero Los caracteres también están permitidos en los nombres de variables - ver Ofuscación JavaScript con caracteres de ancho cero - pros y contras? .
Esto se vería como el siguiente:
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!")
}
ES IS ¡POSIBLE!
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a == 1 && a == 2 && a == 3)
console.log("wohoo");
}
Esto utiliza un getter dentro de una instrucción with
para permitir que a
evalúe tres valores diferentes.
... esto todavía no significa que deba usarse en código real ...
Peor aún, este truco también funcionará con el uso de ===
.
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a !== a)
console.log("yep, this is printed.");
}
Ejemplo sin getters o valueOf:
a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);
Esto funciona porque ==
invoca toString
que llama a .join
para Arrays.
Otra solución, utilizando Symbol.toPrimitive
que es un equivalente ES6 de toString/valueOf
:
let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)};
console.log(a == 1 && a == 2 && a == 3);
Si se le pregunta si es posible (no DEBE), puede pedirle a "a" que devuelva un número aleatorio. Sería cierto si genera 1, 2 y 3 secuencialmente.
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;
}
}
}
Cuando no puedes hacer nada sin expresiones regulares:
var a = {
r: /\d/g,
valueOf: function(){
return this.r.exec(123)[0]
}
}
if (a == 1 && a == 2 && a == 3) {
console.log("!")
}
Funciona debido a que custom valueOf
method se llama cuando Object se compara con primitive (como Number). El truco principal es que a.valueOf
devuelve un nuevo valor cada vez porque llama a exec
en la expresión regular con el indicador g
, lo que provoca la actualización lastIndex
de esa expresión regular cada vez que se encuentra una coincidencia. Así que la primera vez que this.r.lastIndex == 0
coincide con 1
y actualiza lastIndex
: this.r.lastIndex == 1
, así que la próxima vez que regex coincidirá con 2
y así sucesivamente.
Se puede lograr utilizando lo siguiente en el ámbito global. Para nodejs
use global
en lugar de window
en el siguiente código.
var val = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++val;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('yay');
}
Esta respuesta abusa de las variables implícitas proporcionadas por el alcance global en el contexto de ejecución al definir un captador para recuperar la variable.
Esto es posible en el caso de que se acceda a la variable a
, por ejemplo, 2 trabajadores web a través de un SharedArrayBuffer, así como algunos scripts principales. La posibilidad es baja, pero es posible que cuando el código se compila a código de máquina, los trabajadores web actualicen la variable a
justo a tiempo para que se cumplan las condiciones a==1
, a==2
y a==3
.
Este puede ser un ejemplo de condición de carrera en un entorno multihilo proporcionado por los trabajadores web y SharedArrayBuffer en JavaScript.
Aquí está la implementación básica de lo anterior:
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
})
})
En mi MacBook Air, ocurre después de alrededor de 10 mil millones de iteraciones en el primer intento:
Segundo intento:
Como dije, las posibilidades serán bajas, pero si se da el tiempo suficiente, llegará a la condición.
Consejo: si toma demasiado tiempo en su sistema. Intente sólo a == 1 && a == 2
y cambie Math.random()*3
a Math.random()*2
. Agregar más y más a la lista deja caer la posibilidad de golpear.
Esto también es posible usando una serie de captadores de auto-sobrescritura:
(Esto es similar a la solución de jontro, pero no requiere una variable 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.");
}
})();
No veo esta respuesta ya publicada, así que la incluiré también en la mezcla. Esto es similar a la respuesta de Jeff con el espacio de Hangul de medio ancho.
var a = 1;
var a = 2;
var а = 3;
if(a == 1 && a == 2 && а == 3) {
console.log("Why hello there!")
}
Es posible que note una ligera discrepancia con la segunda, pero la primera y la tercera son idénticas a simple vista. Los 3 son caracteres distintos:
a
- minúscula latina Aa
- Ancho completo en latín, minúscula Aа
- minúscula cirílica A
El término genérico para esto es "homoglyphs": diferentes caracteres Unicode que se ven iguales. Por lo general, es difícil obtener tres que son absolutamente indistinguibles, pero en algunos casos puedes tener suerte. A, Α, А, y Ꭺ funcionarían mejor (Latin-A, Greek Alpha , Cyrillic-A , y Cherokee-A respectivamente; desafortunadamente las letras minúsculas griegas y cherokee son demasiado diferentes del latín a
: α
, ꭺ
, y por lo tanto no ayuda con el fragmento de código anterior).
Hay toda una clase de Ataques de Homoglyph por ahí, más comúnmente en nombres de dominio falsos (por ejemplo, wikipediа.org
(cirílico) vs wikipedia.org
(latino)), pero también puede aparecer en el código; por lo general, se lo denomina ser poco inteligente (como se menciona en un comentario, [malicioso] las preguntas están ahora fuera de tema en PPCG , pero solía ser un tipo de desafío donde este tipo de cosas se mostrarían arriba). Utilicé este sitio web para encontrar los homoglifos utilizados para esta respuesta.
Alternativamente, podría usar una clase para él y una instancia para la verificación.
function A() {
var value = 0;
this.valueOf = function () { return ++value; };
}
var a = new A;
if (a == 1 && a == 2 && a == 3) {
console.log('bingo!');
}
EDITAR
Usando clases de ES6 se vería así
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!');
}
En JavaScript, no hay enteros pero solo Number
s, que se implementan como números de coma flotante de doble precisión.
Significa que si un Número a
es lo suficientemente grande, puede considerarse igual a tres enteros consecutivos:
a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
console.log("Precision loss!");
}
Es cierto que no es exactamente lo que preguntó el entrevistador (no funciona con a=0
), pero no implica ningún truco con funciones ocultas o sobrecarga de operadores.
Para referencia, hay soluciones a==1 && a==2 && a==3
en Ruby y Python. Con una ligera modificación, también es posible en Java.
Con un ==
personalizado:
class A
def ==(o)
true
end
end
a = A.new
if a == 1 && a == 2 && a == 3
puts "Don't do this!"
end
O una creciente a
:
def a
@a ||= 0
@a += 1
end
if a == 1 && a == 2 && a == 3
puts "Don't do this!"
end
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!")
Es posible modificar 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.");
}
}
}
if=()=>!0;
var a = 9;
if(a==1 && a== 2 && a==3)
{
document.write("<h1>Yes, it is possible!????</h1>")
}
El código anterior es una versión corta (gracias a @Forivin por su nota en los comentarios) y el siguiente código es 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;}
Si solo ves la parte superior de mi código y lo ejecutas, dices WOW, ¿cómo?
Entonces, creo que es suficiente decir Sí, es posible a alguien que le dijo a Usted: (Nada es imposible
Truco: usé un carácter oculto después de
if
para hacer una función cuyo nombre sea similar aif
. En JavaScript no podemos anular las palabras clave, así que me obligo a usarlas de esta manera. Es unaif
falsa, ¡pero te funciona en este caso!
También escribí una versión de C # (con técnica de aumento de valor de propiedad):
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!????");
}
}
Esta es una versión invertida de @ la respuesta de Jeff * donde se usa un carácter oculto (U + 115F, U + 1160 o U + 3164) para crear variables que parecen 1
, 2
y 3
.
var a = 1;
var ᅠ1 = a;
var ᅠ2 = a;
var ᅠ3 = a;
console.log( a ==ᅠ1 && a ==ᅠ2 && a ==ᅠ3 );
* Esa respuesta se puede simplificar utilizando una combinación de ancho cero sin juntura (U + 200C) y una combinación de ancho cero (U + 200D). Ambos de estos caracteres están permitidos dentro de los identificadores pero no al principio:
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);
****/
Otros trucos son posibles usando la misma idea, p. Ej. utilizando los selectores de variación de Unicode para crear variables que se ven exactamente iguales (a︀ = 1; a︁ = 2; a︀ == 1 && a︁ == 2; // true
).
Regla número uno de las entrevistas; nunca digas imposible.
No hay necesidad de trucos ocultos de personajes.
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?' );
}
Sin embargo, honestamente, ya sea que haya una manera de evaluar si es verdadera o no (y, como han demostrado otros, hay varias formas), la respuesta que estaría buscando, hablando como alguien que ha realizado cientos de entrevistas, sería algo en la línea de:
"Bueno, tal vez sí bajo un extraño conjunto de circunstancias que no son inmediatamente obvias para mí ... pero si encontrase esto en un código real, usaría técnicas comunes de depuración para averiguar cómo y por qué estaba haciendo lo que estaba haciendo. y luego refactorice inmediatamente el código para evitar esa situación ... pero lo más importante es que NUNCA escribiría ese código en primer lugar porque esa es la definición misma de código enrevesado, y me esfuerzo por nunca escribir código enrevesado ".
Supongo que algunos entrevistadores se ofenderían por lo que obviamente significa una pregunta muy complicada, pero no me importa que los desarrolladores tengan una opinión, especialmente cuando pueden respaldarla con un pensamiento razonado y pueden unir mi pregunta. una declaración significativa sobre ellos mismos.
Aquí hay otra variación, utilizando una matriz para resaltar los valores que desee.
const a = {
n: [3,2,1],
toString: function () {
return a.n.pop();
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Yes');
}
Si alguna vez recibe una pregunta de esta entrevista (o nota algún comportamiento igualmente inesperado en su código), piense qué tipo de cosas podrían causar un comportamiento que parece imposible a primera vista:
Codificación : En este caso, la variable que está viendo no es la que cree que es. Esto puede suceder si intencionalmente juegas con Unicode usando homoglifos o caracteres del espacio para hacer que el nombre de una variable se parezca a otra, pero los problemas de codificación también se pueden introducir accidentalmente, por ejemplo. al copiar y pegar código de la Web que contiene puntos de código Unicode inesperados (por ejemplo, porque un sistema de administración de contenido realizó algunos "autoformatos", como reemplazar fl
con Unicode 'LATIN SMALL LIGATURE FL' (U + FB02)).
Condiciones de carrera : Puede producirse una condición de carrera , es decir, una situación en la que el código no se está ejecutando en la secuencia esperada por el desarrollador. Las condiciones de carrera a menudo ocurren en código de subprocesos múltiples, pero los subprocesos múltiples no son un requisito para que las condiciones de carrera sean posibles; la asincronicidad es suficiente (y no se confunda, asíncrono no significa que se usen múltiples hilos debajo del capó ).
Tenga en cuenta que, por lo tanto, JavaScript tampoco está exento de condiciones de carrera solo porque es de un solo hilo. Consulte aquí para ver un ejemplo simple de un solo hilo, pero asíncrono. En el contexto de una sola declaración, la condición de carrera, sin embargo, sería bastante difícil de alcanzar en JavaScript.
JavaScript con los trabajadores web es un poco diferente, ya que puedes tener múltiples hilos. @mehulmpt nos ha mostrado una gran prueba de concepto usando trabajadores web .
Efectos secundarios : Un efecto secundario de la operación de comparación de igualdad (que no tiene que ser tan obvio como en los ejemplos de este documento, a menudo los efectos secundarios son muy sutiles).
Este tipo de problemas pueden aparecer en muchos lenguajes de programación, no solo en JavaScript, por lo que no vemos uno de los clásicos JavaScript WTF aquí1.
Por supuesto, la pregunta de la entrevista y las muestras aquí parecen muy elaboradas. Pero son un buen recordatorio de que:
1 Por ejemplo, puede encontrar un ejemplo en un lenguaje de programación (C #) totalmente diferente que muestre un efecto secundario (uno obvio) aquí .
Bueno, otro hack con generadores:
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!');
}
En realidad, la respuesta a la primera parte de la pregunta es "Sí" en todos los lenguajes de programación. Por ejemplo, esto es en el caso de 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;
}
Utilizando 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);
Los proxies básicamente pretenden ser un objeto objetivo (el primer parámetro), pero interceptan operaciones en el objeto objetivo (en este caso, la operación "obtener propiedad") para que haya una oportunidad de hacer algo diferente al comportamiento predeterminado del objeto. En este caso, la acción "obtener propiedad" se llama en a
cuando ==
obliga a su tipo para compararlo con cada número. Esto pasa:
{ i: 0 }
, donde la propiedad i
es nuestro contadora
a ==
, el tipo a
es obligado a un valor primitivoa[Symbol.toPrimitive]()
internamentea[Symbol.toPrimitive]
usando el "controlador de obtención"Symbol.toPrimitive
, en cuyo caso se incrementa y luego devuelve el contador del objeto de destino: ++target.i
. Si se recupera una propiedad diferente, simplemente volvemos a devolver el valor de propiedad predeterminado, target[name]
Asi que:
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 con la mayoría de las otras respuestas, esto solo funciona con una verificación de igualdad suelta (==
), porque las verificaciones de igualdad estrictas (===
) no hacen coerción de tipo que el Proxy puede interceptar.
Igual, pero diferente, pero igual (puede "probarse" varias veces):
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!');
}
Mi idea comenzó a partir de cómo funciona la ecuación de tipo de objeto Número.
Una respuesta de ECMAScript 6 que hace uso de los Símbolos:
const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));
Debido al uso de ==
, se supone que JavaScript debe coaccionar a a
en algo cercano al segundo operando (1
, 2
, 3
en este caso). Pero antes de que JavaScript intente calcular la coacción por sí solo, intenta llamar a Symbol.toPrimitive
. Si proporciona Symbol.toPrimitive
JavaScript usaría el valor que devuelve su función. Si no, JavaScript llamaría valueOf
.
Creo que este es el código mínimo para implementarlo:
i=0,a={valueOf:()=>++i}
if (a == 1 && a == 2 && a == 3) {
console.log('Mind === Blown');
}
Crear un objeto ficticio con una valueOf
personalizada que incrementa una variable global i
en cada llamada 23 caracteres!
¡Este utiliza la propiedad defineProperty con una variable global que causa un efecto secundario de Niza!
var _a = 1
Object.defineProperty(this, "a", {
"get": () => {
return _a++;
},
configurable: true
});
console.log(a)
console.log(a)
console.log(a)
Al reemplazar valueOf
en una declaración de clase, se puede hacer:
class Thing {
constructor() {
this.value = 1;
}
valueOf() {
return this.value++;
}
}
const a = new Thing();
if(a == 1 && a == 2 && a == 3) {
console.log(a);
}
Lo que sucede es que valueOf
se llama en cada operador de comparación. En la primera, a
será igual a 1
, en la segunda, a
será igual a 2
, y así sucesivamente, porque cada vez que se llama a valueOf
, el valor de a
se incrementa.
Por lo tanto, console.log se activará y generará (en mi terminal de todos modos) Thing: { value: 4}
, indicando que el condicional era verdadero.