Hatena::Groupjavascript

JavaScriptで遊ぶよ

 | 

2011-03-23

Constellation さんのスライドを読む

16:27

↑うう、難しいスライドなのよ…

というわけで、気になったところを深追いしてみます。


parse time error

  • ECMAScriptparse時errorがやってきた!(compile time error)
  • early errorはProgramの評価前にreportする必要がある
  • 代表的なearly error: SyntaxError
  • strict modeのraiseするerrorの多くはSyntaxError

ES3 には compile time error は無かったのか?→不明。シンタックスエラーはあるけど…

early error はどんなものか?→16節にリスト化してある。

  • シンタックスエラー
  • 複数の同じ名前のゲッター・セッター {get x(){}, get x(){}}
  • オブジェクトのキーと同じ名前のゲッター・セッター {x: 1, get x(){}}
  • 未定義の正規表現のシンタックス
  • strict mode でオブジェクトに複数の同じキー {x: 1, x: 2}
  • strict mode で with
  • strict mode で同じ名前の仮引数 (function(a,a){alert(a)}(1,2))
  • return, break, continue の使い方がおかしい(よくわからない)
  • Reference でないものに代入 3 = 4

これだけ。eval への代入とかはシンタックスエラー。


restrictionの一貫性

  • ES5 / strict modeのrestriction
    • with禁止
    • eval / argumentsへの代入不可
    • arguments restriction
      • caller / callee
      • 環境連携の削除
    • indirect call to eval
function foo() {
  "use strict";
  with(this){}; // SyntaxError: strict mode code may not contain 'with' statements
  eval = function(){}; // SyntaxError: assignment to eval is deprecated
  arguments = {}; // SyntaxError: assignment to arguments is deprecated
  arguments.callee; // undefined
  foo.caller; // undefined
}

環境連携の削除というのは (function(a){arguments[0]='NO!'; alert(a);}('yes')) これが 'NO!' になってしまうアレを解消。


  • eval / argumentsはES3でstaticに解析できない
  • そして, 唯一環境への影響力がある

ex

function test(script) {
  eval(script);
  console.log(typeof i);  // not undefined
}
test("var i = 20;");
  • localの環境に対してdynamicに影響を与える
  • strict modeではdynamic call to evalも一層環境を下げることで, 元環境に新規変数を作るといった広範囲な変更をさせない(変更できるとすれば変数の内容を書き換えるくらい)

上のコードは "number" と出力。

function test(script) {
  "use strict";
  eval(script);
  console.log(typeof i);
}
test("var i = 20;");

これだと undefined

strict mode のことなら Annex C に訊け。

Strict mode eval code cannot instantiate variables or functions in the variable environment of the caller to eval. Instead, a new variable environment is created and that environment is used for declaration binding instantiation for the eval code (10.4.2).

なるほど。eval(script) は、今まではその場に script が挿入される感じだったが、strict mode では無名関数の中身みたいになった。(最後に評価した値を return したり、細かな違いはある)

「環境に対して影響を与える」というのは「スコープチェーンに影響を与える」または「名前解決に影響を与える」という意味。今まで無かった変数がいきなり出来たら困る。

評価されたスコープに元々存在している変数は書き換えられる。

function test(script) {
  "use strict";
  var i = 10;
  eval(script);
  console.log(i); // 20
}
test("i = 20;");

これは別にいいのか…?(たぶんパフォーマンス的には消し去りたいけど絶賛放置中なのだろう)


indirect call to eval

  • var g = eval; g(script);
  • staticに解析できないでevalされる恐れ
  • 評価環境をglobalに追放することでlocal環境をdynamicに変更されるのを阻止

eval によってローカル変数が書き換えられるおそれのあるとき(さっきの例のように)は、最適化出来ない。なので、「eval が使われているコードである」ことを最初に知りたい。だから、暗黙に eval されているときは、最適化に影響を与えないものとする。


caller / callee

  • Function.argumentsが存在する場合, caller calleeから環境の呼び出し元に影響を与える
  • 呼び出し元はstrict modeでないかもしれない
  • strict mode codeからは他環境にdynamicな影響を与える / 与えられることを禁止

よくわからない。callee が使えるとどう不都合なのか。


strict modeの結実 1

  • strict mode下で今やすべてのlocal変数var ident形式でしか宣言できない
  • 全ての変数へのaccessはidentでしか許容されず, かつidentでのaccessは全て環境の変数にしかひもづけられない(with禁止効果)
  • 全ての変数の存在/非存在, その変数の確保領域がparse時に決定する
  • ある変数が参照されるかどうかですら解析可能
  • 例えば, あるstrict modeのscriptにevalによるdynamic call一切含まれない場合(これはkeywords化でparse時解析可能になりました), dynamicに環境から変数を引き出すことは一切ないことがparse時に解析可能(eval直接呼出し以外はindirect call to evalになるので)
    • 変数解決を一切しないで埋め込むことだってできますよね
    • さながら静的言語のように
    • さらにつきつめればconfigurable: false, writable: falseなら, もはや定数埋め込みだって(const)

そういうことですね。


strict modeの結実 2

  • delete restrictionはこの結実の結果
  • section 11.4.1
delete (target)
  • このとき(target)がstrict reference(strict mode下でのreference)で
  • UnresolvableReference
    • ReferenceでEnvironment Record(変数)にひもづいてる時
    • SyntaxError (parse時error)

delete restriction

  • そんなのparse時に解析可能なの?
  • strict mode化では変数へのaccessはidentしか許容されない
    • withが禁止になってるのでidentでのaccessは必ず変数にひもづいている
  • UnresolvableReferenceというのはglobalにあるかもしれないとき

delete restriction 結論

先程のrestrictionを実装しようとすれば,

"use strict"
delete ident;

を全てSyntaxErrorにすれば対処できる(strict mode restrictionのおかげ)

つまり、あったはずの変数が消えるのが避けられると。

delete a.b;

↑これは OK

delete a;

↑これはだめ。

delete window.a;

↑これは?よくわからない。


感想

どうして完全に eval をなくさなかったのだろうか。Function() も一緒になくさないと意味ない→それは無理、ということだったんだろうか。

ConstellationConstellation2011/03/23 21:34Function()はもともとthisがGlobalObject, LexicalEnv, VariableEnvともにGlobal Object Environmentになるので, local環境に影響を与えることはできないで, indirect call to evalと大体同じですー.

caller / calleeのやつはSESのスライドを見ていただけると面白いかもです.

evalが変数を書き換えられるのは, 実装者としては結構嫌な感じですが, それでも変数を作られるよりかはましという状況があります.
一般にJSのVMを考えれば,
function test() {
var a = 20;
print(a);
function inner() {
print(b); // このとき, test環境にはprint, bともに存在しないことが分かっているので, GlobalObjectの辞書だけ引けばよい. 具体的にはVMのcode levelでもう最初からLOAD_GLOBALとかの命令を埋め込んでしまえます.
}
}
となっていれば, aをindex 0(test functionに名前配列[Symbol(a)]を作り(これはparse時に確定する), それのindex0として割り当て, bytecode自体に埋め込んでしまう, LOAD_LOCAL 0といった風に)に割り当て, local変数領域を配列にすることで, var a = 20をlocal[0] = 20;, aはlocal[0]なのでそれをpush, そしてprintはlocal変数にないので明示的に検索して(この時, local環境にprintという宣言がないことが事前に判明していて, それは再帰的に上方の環境でも言えるので, 実際にhashを引くのはGlobalObjectに対してだけです) stackにpush, その後call命令を呼んでというふうに実装できます. 注目したいのは, aというidentifierがstaticに解析されるので, 事前にlocal[0]のように出来るところです. つまり, 変数解決にhash引きのcostがありません.

これが変数を作れるとなると,
function test() {
var a = 20;
eval(script);
function inner() {
print(b); // このprint, bもどちらもtestの辞書を引かないといけない. GlobalObjectの辞書だけ引けばよかったことを考えると非常に高cost
}
}
のとき, bを検索するときにもしかしたらlocalにある可能性があって(eval('var b = 20')される), Globalだけ検索する(test, innerともに環境の変数をstaticに解析すれば, もともとbという変数が宣言されていないことがわかる)のと違ってtestの環境も検索しないといけないですね. すると, testを検索するためにはlocal配列といった感じではなく変数領域とSymbol(b)を結びつけたような辞書を作る必要があります. どの道evalを実行するためにつくるとしても, test環境以降は, 変数解決の時にみんなこのtestの辞書を引かなければいけません. これは先程と考えると非常に高costです.

変数を作れないとすれば, evalの瞬間にlocalとSymbolのtableは必要になりますが, その瞬間に使われるだけで, 以降の変数解決時にすべて辞書を検索しないといけないということはありません. 要はすべてのidentifierが変数領域のどこに紐づいているのか(Globalに紐づいているとわかれば, 途中環境を検索せずに, ただただGlobalのhashだけ引けばいいです)が事前に分かるので, もう何も恐くない! という感じですー.

edvakfedvakf2011/03/24 02:12>SESのスライド
http://code.google.com/p/es-lab/downloads/detail?name=securing-es5.pdf

見てみました。中身のわからない関数を実行したときにローカル変数が書き換えられるのを防ぐ目的なら arguments だけ無くせばいいんですよね。callee と caller は単純にコストが高いということなのかなと思いました。

indirect eval や Function がグローバルなのは、そもそもグローバルで変数作るのを禁止しても抜け道はいくらでもあるということかな。window['a'] とかまで禁止できないだろうし。グローバル変数はそれこそいつでも書き換えられちゃいますし。

var x = 0;
var a = function(){ x = 1; };
var b = function(){"use strict"; a(); console.log(x); };
b();

使う変数はローカルに。超重要ですね。

いっそローカル eval を捨てちゃえば良かったんでしょうけど。(全部グローバルに)

あと、いきなり書き換えられて困るという意味では、prototype なんて厄介の極みなのかな。

var a = [];
a.push('first');
Array.prototype.push = Array.prototype.unshift;
a.push('second');

edvakfedvakf2011/03/24 02:15prototype に限るわけでもないや。

a = [];
a.push(0);
a.push = a.unshift;
a.push(1);

前途多難ですね。

トラックバック - http://javascript.g.hatena.ne.jp/edvakf/20110323
 |