疑問
JavaScriptのオブジェクトって、.
を使うことでプロパティの中身を見れますよね?
const sample = {
num: 10,
};
sample.num; // 10
そして、プロパティの中身がオブジェクトだった場合は、さらに.
を繋げるとそのプロパティが見れますよね?
const sample = {
obj: {
a: 'sampleA',
b: 'sampleB'
}
};
sample.obj.a; // 'sampleA'
また、メソッドの返り値がオブジェクトだった場合も繋げることができます。
document.getElementById('sample').innerText = 'Hello World!';
今まで出してきた例は.
の数が1つや2つでしたが、では一体 この呼び出すプロパティの数に上限はあるのか? と疑問に思ったため、調べてみることにしました。
環境
- OS:MacOS Sonoma 14.1 (Apple M1)
- 実行環境:Chrome 120.0.6099.234
- エディタ:Chrome デベロッパーツールのコンソールで作業
実験内容
結果だけ見たい方は、この節は読み飛ばして下の結果から読んでください。
実験に使ったコードはこちらです。
str = 'window.'.repeat(1166).slice(0, -1);
eval(str);
上のコードは、window.window...window
みたいな文字列を生成して、それを実行するものです。
関数とかwindowの解説
window
window
オブジェクトのwindow
プロパティはwindow
自身を返すみたいなので、これを利用することにしました。
この仕様を使えばwindow.window.window.window...
という感じでプロパティを呼び出すことができます。
詳しくはMDNを見てください。
string.prototype.repeat
こちらは呼び出された文字を指定した回数繰り返す関数です。
これを使うとホラーっぽい文字が作れたりします。
const help = 'たすけて'.repeat(100);
console.error(help); // たすけてたすけてたすけてたすけてた以下略
...あなたは今後こんな感じの文字を見た時にrepeat
を思い出す呪いにかかりました。
...嘘です。かかってないです。すみませんでした許してください。
詳しくはMDNを見てください。
slice(0, -1)
これは文字列から最後の文字を消すのに使っています。
上で解説したrepeat
メソッドは、実験に使ったコードだと下のように使っています。
'window.'.repeat(1166);
ただ、これだと生成された文字列がwindow.win以下略.window.
のように最後が.
で終わってしまいます。
これだと構文エラーが出てしまうので、slice(0, -1)
とすることで最後の.
を消すようにしています。
slice
の詳しい情報はMDNを見てください。
eval
eval()
関数は、文字列として表現された JavaScript コードを評価します。ソースはスクリプトとして解釈されます。
eval
は文字列をコードとして実行する関数です。
この関数を使うことで、大量のwindow
という文字をコードとして実行しています。
警告: 文字列から JavaScript を実行することは、非常に大きなセキュリティリスクを伴います。eval()
を使用すると、悪意のある者が任意のコードを実行することがあまりにも簡単になります。下記のeval() を使わないでください!を参照してください。
(MDNのコピペ)
詳しくはMDNを参照してください。
コード解説
もう一度コード全文を乗っけておきます。
str = 'window.'.repeat(1166).slice(0, -1);
eval(str);
処理はこんな感じです。
-
repeat
メソッドで大量のwindow.
を繋げた文字を出力する -
slice(0, -1)
で最後にある.
を消す -
str
変数に上で生成した文字列が格納される -
eval
関数でstr
を実行する
このコードは、repeat(ここ)
の数値を変えることによってwindow
を繋げる回数を変えることができます。
なので実験の時はここの数をいくつにしたらエラーが出るのか、というふうにします。
今思えば関数にした方が良かったような気もしますが、その辺は雑な実験だったので見なかったことにしてください...
という感じで、上のコードを手動で数値を変えながら実行しました。
...はい、自動化した方が良かったですね。
今ならこうすると思います。
for (let i = 0; true; i++) {
try {
str = 'window.'.repeat(i).slice(0, -1);
eval(str);
} catch (e) {
console.log(i); // エラーが出た回数を出力
console.error(e); // エラー内容を出力
break;
}
}
結果
試してみたところ、私の環境だと1165回まではエラーが出ませんでした。
結構たくさんいけるような、意外と少ないような...
追記 2024/3/4
Chromeを 122.0.6261.94 にアップデートしたら、1189回まではエラーが出なくなりました。
バージョンによって挙動が違うかもしれません。
そして、1166回以降だと以下のエラーが発生しました。
VM15896:2 Uncaught RangeError: Maximum call stack size exceeded
at <anonymous>:2:6
ちなみに上のエラーは一言で言うと「無限ループしてるよ」ということみたいです。
詳しくは以下を見てみてください。
これって環境によって変わるんでしょうかね?
少し気になります。
試したい方用に実験用コード(改善版)をもう一度乗っけておきます。
コピペして使ってください。
for (let i = 0; true; i++) {
try {
str = 'window.'.repeat(i).slice(0, -1);
eval(str);
} catch (e) {
console.log(i);
console.error(e);
break;
}
}
/* 私の環境で出力された結果:
1166 -> 1190(chrome 122.0.6261.94)
VM1248:7 RangeError: Maximum call stack size exceeded
at <anonymous>:4:14
*/
もし試してみた方がいたら、なんて数値が出力されたか教えて欲しいです。
また、この回数の仕様などに関する情報を持っている方がいましたら、ぜひコメントください。