Замечание модератора: Пожалуйста, сопротивляйтесь желанию изменить код или удалить это уведомление. Шаблон пробела может быть частью вопроса, и поэтому его не следует подделывать без необходимости. Если вы находитесь в лагере "пробелы - незначителен", вы должны иметь возможность принимать код как есть.
Возможно ли, что (a== 1 && a ==2 && a==3)
может оценить true
в JavaScript?
Это вопрос интервью, заданный крупной технологической компанией. Это произошло две недели назад, но я все еще пытаюсь найти ответ. Я знаю, что мы никогда не пишем такой код в нашей повседневной работе, но мне любопытно.
Если вы используете как работает ==
, вы можете просто создать объект с пользовательским toString
(или valueOf
), которая изменяет то, что она возвращает каждый раз, когда она используется так, что она удовлетворяет всем трем условиям.
const a = {
i: 1,
toString: function () {
return a.i++;
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Hello World!');
}
Причина, по которой это работает, связана с использованием свободного оператора равенства. При использовании свободного равенства, если один из операндов отличается от другого, движок будет пытаться преобразовать один в другой. В случае объекта слева и числа справа он попытается преобразовать объект в число, сначала вызвав valueOf
, если он является вызываемым, и в противном случае он вызовет toString
. В этом случае я использовал toString
просто потому, что это пришло в голову, valueOf
имело бы больше смысла. Если бы я вместо этого вернул строку из toString
, тогда движок попытался бы преобразовать строку в число, давая нам тот же конечный результат, хотя и с немного более длинным путем.
valueOf()
?
Я не мог удержаться - другие ответы, несомненно, верны, но вы действительно не можете пройти мимо следующего кода:
var aᅠ = 1;
var a = 2;
var ᅠa = 3;
if(aᅠ==1 && a== 2 &&ᅠa==3) {
console.log("Why hello there!")
}
Обратите внимание на странное расстояние в инструкции if
(которую я скопировал из вашего вопроса). Это полуширина Hangul (что корейский для тех, кто не знаком), который является символом пробела Unicode, который не интерпретируется ECMA script как символ пробела - это означает, что он является допустимым символом для идентификатора. Поэтому есть три совершенно разные переменные: одна с хангулом после a, одна с ней до и последняя с просто a. Заменяя пространство с помощью _
для удобства чтения, тот же код будет выглядеть так:
var a_ = 1;
var a = 2;
var _a = 3;
if(a_==1 && a== 2 &&_a==3) {
console.log("Why hello there!")
}
Отметьте подтверждение проверки валидатора переменных Mathias. Если бы этот странный интервал был фактически включен в их вопрос, я уверен, что это намек на такой ответ.
Не делай этого. Серьезно.
Изменить: мне пришло в голову, что (хотя и не разрешено запускать переменную) Zero-width joiner и Имена символов с нулевой шириной допускаются также в именах переменных - см. Обфускация JavaScript с символами нулевой ширины - профи и минусы?.
Это будет выглядеть следующим образом:
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!")
}
ЭТО ВОЗМОЖНО!
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a == 1 && a == 2 && a == 3)
console.log("wohoo");
}
При этом используется поглотитель внутри with
выражением, чтобы оценка три различных значений. a
... это все еще не означает, что это должно использоваться в реальном коде...
Хуже того, этот трюк также будет работать с использованием ===
.
var i = 0;
with({
get a() {
return ++i;
}
}) {
if (a !== a)
console.log("yep, this is printed.");
}
with
».
with
определенно ошибка дизайна.
Пример без геттеров или valueOf:
a = [1,2,3];
a.join = a.shift;
console.log(a == 1 && a == 2 && a == 3);
Это работает, потому что ==
вызывает toString
, который вызывает .join
для массивов.
Другое решение, используя Symbol.toPrimitive
, который является эквивалентом ES6 toString/valueOf
:
let a = {[Symbol.toPrimitive]: ((i) => () => ++i) (0)};
console.log(a == 1 && a == 2 && a == 3);
without valueOf
, ну ... это более косвенный, но в основном то же самое.
Если задано, возможно ли (НЕ ДОЛЖНО), он может попросить "а" вернуть случайное число. Было бы правдой, если бы он последовательно генерировал 1, 2 и 3.
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;
}
}
}
Если вы не можете ничего сделать без регулярных выражений:
var a = {
r: /\d/g,
valueOf: function(){
return this.r.exec(123)[0]
}
}
if (a == 1 && a == 2 && a == 3) {
console.log("!")
}
Он работает из-за пользовательского метода valueOf
, который вызывается, когда объект сравнивается с примитивным (например, Number). Главный трюк заключается в том, что a.valueOf
возвращает новое значение каждый раз, потому что он вызывает exec
в регулярном выражении с флагом g
, что вызывает обновление lastIndex
этого регулярного выражения каждый раз, когда найдено совпадение. Итак, в первый раз this.r.lastIndex == 0
он соответствует 1
и обновляет lastIndex
: this.r.lastIndex == 1
, поэтому следующее регулярное выражение будет соответствовать 2
и т.д.
exec
снова начнет поиск по этому индексу. МДН не очень понятно.
Это можно сделать, используя следующее в глобальной области. Для nodejs
используйте global
вместо window
в приведенном ниже коде.
var val = 0;
Object.defineProperty(window, 'a', {
get: function() {
return ++val;
}
});
if (a == 1 && a == 2 && a == 3) {
console.log('yay');
}
Этот ответ злоупотребляет неявными переменными, предоставляемыми глобальной областью в контексте выполнения, определяя получателя для извлечения переменной.
a
свойство является this
чем оно не является. Если бы a
было локальной переменной (как это выглядит), то это не сработало бы.
Это возможно в случае обращения к переменной a
, скажем, 2 веб-работникам через SharedArrayBuffer, а также некоторые основные script. Возможность низкая, но возможно, что когда код компилируется в машинный код, рабочие сети обновляют переменную a
как раз вовремя, поэтому выполняются условия a==1
, a==2
и a==3
.
Это может быть пример состояния гонки в многопоточной среде, предоставляемой веб-работниками и SharedArrayBuffer в JavaScript.
Вот базовая реализация выше:
main.js
// Main Thread
const worker = new Worker('worker.js')
const modifiers = [new Worker('modifier.js'), new Worker('modifier.js')] // Let 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
})
})
На моем MacBook Air это происходит после примерно 10 миллиардов итераций с первой попытки:
Вторая попытка:
Как я уже сказал, шансы будут низкими, но, учитывая достаточное время, он попадет в состояние.
Совет. Если в вашей системе слишком много времени. Попробуйте только a == 1 && a == 2
и измените Math.random()*3
на Math.random()*2
. Добавление все большего количества в список снижает вероятность удара.
Это также возможно с помощью серии самонастраиваемых геттеров:
(Это похоже на решение jontro, но не требует переменной счетчика.)
(() => {
"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.");
}
})();
===
, а не только с ==
.
Я не вижу, что этот ответ уже отправлен, поэтому я тоже заброшу его в микс. Это похоже на ответ Джеффа с пространством хангула полуширины.
var a = 1;
var a = 2;
var а = 3;
if(a == 1 && a == 2 && а == 3) {
console.log("Why hello there!")
}
Вы можете заметить небольшое несоответствие со вторым, но первое и третье идентичны невооруженным глазом. Все 3 - разные символы:
a
- латинский нижний регистр A a
- Полная ширина латинского нижнего регистра A а
- кириллический нижний регистр A
Общий термин для этого - "гомоглифы": разные символы юникода, которые выглядят одинаково. Обычно трудно получить три, которые совершенно неразличимы, но в некоторых случаях вам может повезти. A, A, А и Ꭺ будут работать лучше (Latin-A, Greek Alpha, Кириллица-A и Cherokee-A соответственно, к сожалению, буквы в нижнем регистре греческого и чероки слишком отличаются от Latin a
: α
, ꭺ
, и поэтому не помогает с приведенным выше фрагментом).
Там есть целый класс Homoglyph Attacks, чаще всего в поддельных доменных именах (например, wikipediа.org
(кириллица) vs wikipedia.org
(латинский)), но он также может отображаться в коде; обычно упоминаемый как подкупленный (как упоминалось в комментарии, [underhanded] вопросы теперь не соответствуют теме PPCG, но обычно это был тип проблемы, когда такие вещи появлялись). Я использовал этот сайт, чтобы найти гомоглифы, используемые для этого ответа.
В качестве альтернативы вы можете использовать для него класс и экземпляр для проверки.
function A() {
var value = 0;
this.valueOf = function () { return ++value; };
}
var a = new A;
if (a == 1 && a == 2 && a == 3) {
console.log('bingo!');
}
ИЗМЕНИТЬ
Используя классы ES6, это будет выглядеть как
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!');
}
function A() {value = 0;
в начале?
valueOf
переопределяется, this method is usually called automatically by JavaScript behind the scenes, and not explicitly in code
поэтому, когда мы сравниваем значение, оно фактически увеличивает ..
В JavaScript нет целых чисел, но только Number
s, которые реализованы как числа с плавающей запятой двойной точности.
Это означает, что если число a
достаточно велико, его можно считать равным трем целым целым числам:
a = 100000000000000000
if (a == a+1 && a == a+2 && a == a+3){
console.log("Precision loss!");
}
Правда, это не совсем то, о чем попросил интервьюер (он не работает с a=0
), но это не связано с трюком со скрытыми функциями или перегрузкой оператора.
Для справки, в Ruby и Python есть a==1 && a==2 && a==3
решения. С небольшой модификацией это также возможно на Java.
С пользовательским ==
:
class A
def ==(o)
true
end
end
a = A.new
if a == 1 && a == 2 && a == 3
puts "Don't do this!"
end
Или увеличение: 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!")
Возможно изменение кеша Java Integer
:
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>")
}
Вышеприведенный код является короткой версией (благодаря @Forivin для примечания в комментариях), и следующий код является оригинальным:
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;}
Если вы просто видите верхнюю часть моего кода и запускаете его, вы говорите WOW, как?
Поэтому я думаю, что достаточно сказать "Да", возможно, кто-то сказал вам: "Нет ничего невозможного"
Trick: Я использовал скрытый символ после
if
как сделать функцию, чтобы ее имя было похоже наif
. В JavaScript мы не можем переопределять ключевые слова, поэтому я вынужден использовать этот способ. Это подделка,if
, но она работает для вас в этом случае!
Также я написал версию С# (с повышением стоимости свойства):
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!");
}
}
function if<200c> { }
как она работает, почему не работает с function if<123c>{}
\u200c
(ZERO WIDTH NON-JOINER) в своем ответе, вы хотите, if
с \u123c
? OK, напишите, if
затем нажмите Alt
, нажмите +
нажмите 123c
отпустите Alt
сделайте это слово », ifሼ
Это инвертированная версия @Jeff answer *, где скрытый символ (U + 115F, U + 1160 или U + 3164) используется для создания переменных, которые выглядят как 1
, 2
и 3
.
var a = 1;
var ᅠ1 = a;
var ᅠ2 = a;
var ᅠ3 = a;
console.log( a ==ᅠ1 && a ==ᅠ2 && a ==ᅠ3 );
* Этот ответ может быть упрощен за счет использования нулевой ширины (U + 200C) и стопоров нулевой ширины (U + 200D). Оба этих символа допускаются внутри идентификаторов, но не в начале:
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);
****/
Другие трюки возможны с использованием той же идеи, например. с помощью селекторов вариаций Unicode для создания переменных, которые выглядят совершенно одинаково (a︀ = 1; a︁ = 2; a︀ == 1 && a︁ == 2; // true
).
Правило номер один из интервью; никогда не сказать невозможным.
Не нужно скрывать трюки персонажа.
window.__defineGetter__( 'a', function(){
if( typeof i !== 'number' ){
// define i in the global namespace so that it not lost after this function runs
i = 0;
}
return ++i;
});
if( a == 1 && a == 2 && a == 3 ){
alert( 'Oh dear, what have we done?' );
}
__defineGetter__
на самом деле не является частью языка js, это просто уродливая версия defineProperty
. typeof
не является функцией, и этот необъявленный i
просто ужасен. По-прежнему стоит 40 голосов: /
__defineGetter__
не рекомендуется для developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… но он явно выполняется в моем FireFox v 57.0.4, поэтому я решил показать это вместо defineProperty()
потому что унаследованный код является реальным и не может быть проигнорировано. Независимо от того безобразия, объявляющего i
так , как я сделал это хорошо известно / документированы поведение. Может быть, я был просто в настроении PCG ¯ \ _ (ツ) _ / ¯
Честно говоря, есть ли способ оценить его истинным или нет (и, как показали другие, существует несколько способов), ответ, который я бы искал, выступая как человек, который провел сотни интервью, было бы чем-то вроде:
"Ну, может быть, да при некоторых странных условиях, которые мне сразу не очевидны... но если бы я столкнулся с этим в реальном коде, я бы использовал общие методы отладки, чтобы выяснить, как и почему он делает то, что он делал, а затем сразу же рефакторировал код, чтобы избежать этой ситуации... но что более важно: я бы НИКОГДА не писал этот код в первую очередь, потому что это само определение свернутого кода, и я стараюсь никогда не писать сложный код".
Я думаю, что некоторые интервьюеры обиделись на то, что, очевидно, было вызвано очень сложным вопросом, но я не против разработчиков, у которых есть мнение, особенно когда они могут поддержать это соображениями и могут согласиться мой вопрос в содержательном заявлении о себе.
Вот еще один вариант, используя массив для выпадания любых значений, которые вы хотите.
const a = {
n: [3,2,1],
toString: function () {
return a.n.pop();
}
}
if(a == 1 && a == 2 && a == 3) {
console.log('Yes');
}
Если вы когда-нибудь зададите такой вопрос интервью (или обратите внимание на какое-то столь же неожиданное поведение в вашем коде), подумайте о том, какие вещи могут привести к поведению, которое выглядит на первый взгляд невозможным:
Кодировка: в этом случае переменная, на которую вы смотрите, не та, которую вы считаете. Это может произойти, если вы намеренно возитесь с Unicode с помощью гомоглифов или пробелов, чтобы сделать имя переменной похожим на другое, но проблемы с кодировкой также могут быть введены случайно, например, при копировании и вставке кода из Интернета, который содержит неожиданный код Unicode (например, из-за того, что система управления контентом выполняла некоторые "автоматическое форматирование", такие как замена fl
на Unicode "LATIN SMALL LIGATURE FL" (U + FB02)).
Условия гонки: может возникнуть состояние гонки, то есть ситуация, когда код не выполняется в последовательности, ожидаемой разработчиком. Условия гонки часто бывают в многопоточном коде, но несколько потоков не являются обязательным условием для условий гонки. Асинхронность достаточна (и не смущайтесь, асинхронность не означает, что под капотом используются несколько потоков).
Обратите внимание, что поэтому JavaScript также не свободен от условий гонки только потому, что он однопоточный. См. Здесь простой однопоточный, но асинхронный пример. В контексте одного заявления условие гонки, однако, было бы довольно трудно попасть в JavaScript.
JavaScript с веб-работниками немного отличается, так как вы можете иметь несколько потоков. @mehulmpt показал нам отличное доказательство концепции с использованием веб-работников.
Побочные эффекты: побочный эффект операции сравнения равенства (который не должен быть столь же очевидным, как в примерах здесь, часто побочные эффекты очень тонкие).
Такого рода вопросы может появляться во многих языках программирования, а не только JavaScript, поэтому мы не видим один из классического JavaScript WTFs здесь 1.
Конечно, вопрос интервью и образцы здесь все выглядят очень надуманными. Но они являются хорошим напоминанием о том, что:
1 Например, вы можете найти пример в совершенно другом языке программирования (С#) экспонирование побочного эффекта (очевидный) здесь.
Хорошо, еще один взлом с генераторами:
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!');
}
this
является ли это объектом окна)
На самом деле ответ на первую часть вопроса "Да" на каждом языке программирования. Например, это относится к C/С++:
#define a (b++)
int b = 1;
if (a ==1 && a== 2 && a==3) {
std::cout << "Yes, it possible!" << std::endl;
} else {
std::cout << "it impossible!" << std::endl;
}
&&
для логических «и».
То же, но другое, но все же одно (может быть "проверено" несколько раз):
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!');
}
Моя идея началась с того, как работает уравнение типа объекта типа.
Использование Прокси:
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 в основном претендуют на то, чтобы быть целевым объектом (первым параметром), но перехватывают операции над целевым объектом (в этом случае операция get get), так что есть возможность сделать что-то другое, кроме поведения объекта по умолчанию, В этом случае действие "get property" вызывается на a
, когда ==
заставляет его тип, чтобы сравнить его с каждым числом. Это происходит:
{ i: 0 }
, где свойство i
является нашим счетчикомa
a ==
тип a
принуждается к примитивному значениюa[Symbol.toPrimitive]()
внутреннеa[Symbol.toPrimitive]
с помощью "get handler" Symbol.toPrimitive
, и в этом случае оно увеличивается, а затем возвращает счетчик из целевого объекта: ++target.i
. Если извлекается другое свойство, мы просто возвращаемся к возврату значения свойства по умолчанию, target[name]
Итак:
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
Как и в большинстве других ответов, это работает только со свободной проверкой равенства (==
), так как проверка строгого равенства (===
) не вызывает тип принуждения, которое Proxy может перехватить.
Symbol.toPrimitive
таким же образом для объекта будет работать так же хорошо.
Ответ ECMAScript 6, в котором используются символы:
const a = {value: 1};
a[Symbol.toPrimitive] = function() { return this.value++ };
console.log((a == 1 && a == 2 && a == 3));
Благодаря ==
использования, JavaScript должен принуждать в нечто близкое к второму операнду (a
1
, 2
, 3
в данном случае). Но перед тем, как JavaScript попытается Symbol.toPrimitive
принуждение самостоятельно, он пытается вызвать Symbol.toPrimitive
. Если вы предоставите Symbol.toPrimitive
JavaScript, используйте значение, возвращаемое вашей функцией. Если нет, JavaScript будет вызывать valueOf
.
Я думаю, что это минимальный код для его реализации:
i=0,a={valueOf:()=>++i}
if (a == 1 && a == 2 && a == 3) {
console.log('Mind === Blown');
}
Создание фиктивного объекта с пользовательским valueOf
, который увеличивает глобальную переменную i
для каждого вызова. 23 символа!
Этот использует свойство defineProperty с хорошим побочным эффектом, вызывающим глобальную переменную!
var _a = 1
Object.defineProperty(this, "a", {
"get": () => {
return _a++;
},
configurable: true
});
console.log(a)
console.log(a)
console.log(a)
a
get: (a => () => ++a)(0),
нет необходимости глобального.
Основной причиной, почему это может быть равным, является оператор ==
. С ===
это не сработает.
const a = {
i: 0;
valueOf: function() {
return this.i++;
}
}
if(a == 1 && a == 2 && a == 3) // true
Поскольку методы valueOf
и toString
будут вызываться, когда используются операторы ==
.
if(a === 1 && a === 2 && a === 3) // false
Методы valueOf
и toString
не вызываются.
Это хороший вопрос. Я также хотел добавить реализацию Java, потому что обновление valueOf()
в основном переопределяет функцию базового класса. Мне потребовалось несколько минут, чтобы понять, как это работает в JS, поэтому я сопоставил его с Java для понимания.
public class TestClass {
int value;
TestClass() {
this.value = 0;
}
@Override
public String toString() {
System.out.println("current " + this.value);
this.value++;
return String.valueOf(this.value);
}
}
class CallingClass {
public static void main(String[] args) {
TestClass object = new TestClass();
if (object.toString().equals("1") && object.toString().equals("2") && object.toString().equals("3")) {
System.out.print("it here");
}
}
}
Переопределив значение valueOf
в объявлении класса, это можно сделать:
class Thing {
constructor() {
this.value = 1;
}
valueOf() {
return this.value++;
}
}
const a = new Thing();
if(a == 1 && a == 2 && a == 3) {
console.log(a);
}
Что происходит, так это то, что valueOf
вызывается в каждом операторе сравнения. В первом случае a
будет равно 1
, во втором a
2
, и так далее, и т.д., Потому что каждый раз, когда вызывается valueOf
, значение a
увеличивается.
Поэтому console.log будет запускать и выводить (в любом случае в моем терминале) Thing: { value: 4}
, указывая, что условие было истинным.
Даже без путаницы именования, перегрузки или случайных переменных a == 1 && a == 2 && a == 3
может возвращать true
в многопоточных средах, так как значение a
может меняться между каждым сравнением, если оно не является потокобезопасным.
(aᅠ== 1 && a == 2 &&ᅠa == 3)
(вы просто чередуетесь с реальными пробелами)