ES6 Quiz
- Q. https://gist.github.com/teramako/858c448cb76cb8d309b0
- A. https://gist.github.com/teramako/6317d057902b91c0dc1a
ECMAScript6th仕様も佳境に入っていますし、そろそろクイズ的なものが出てきても良いのでは? → あまりなさそう。 → 作ってみるか。 ということで作ってみた。
Firefox Nightly上で実行してみたりしながら作ったのだけれど、仕様から読み取れる結果と差異があったりしてなかなか辛い。ES6対応を謳っているAltJS系は、こういうところまでサポートしているのかな…?(←全く試していない)
ということで、回答の方は解答のつもりだけど自信がない部分がある。「お前、ここの仕様を読み飛ばしてるぞ!」ってのがあったら教えて欲しいなあ。
shellshock と sudo
CVE-2014-6271を発端とする bash の脆弱性、いわゆる ShellShock って呼ばれている奴。環境変数に仕込んだ任意のコマンドを実行できてしまうってことから、CGI との組み合わせが取り沙汰されている。
その頃 sudo の設定の勉強をしていたので、ふと気になったのが、sudoの設定で環境変数を持ち越して使用することができる env_keep の設定。sudo で root としてbashを実行させれば、任意のコマンドを特権昇格して実行できちゃうんじゃ? というもの。
早速試してみた。
普通に実行したもの
$ export ORACLE_SID='() { :;}; echo Vulnerability !!!' $ cat /usr/local/bin/testcmd #!/bin/bash -x id printenv ORACLE_SID $ /usr/local/bin/testcmd echo Vulnerability '!!!' Vulnerability !!! + id uid=2001(testid) gid=100(users) 所属グループ=100(users),102(gsudo) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 + printenv ORACLE_SID () { : } $
環境変数に ORACLE_SID を使用しているが、別に Oracle に恨みがあるわけではない。
まぁ、普通にコマンド実行がされている。
sudo経由で実行
攻撃コードを入れた場合
$ export ORACLE_SID=<span style="color: magenta">'() { :;}; echo Vulnerability !!!'</span> $ sudo /usr/local/bin/testcmd + id uid=0(root) gid=0(root) 所属グループ=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 + printenv ORACLE_SID $
値が何も出力されないので、やり方間違えたかな?と何度試しても同じだった。今度は普通の値を入れてみたところ、普通に出力される結果となった。
$ export ORACLE_SID=TESTDB $ sudo /usr/local/bin/testcmd + id uid=0(root) gid=0(root) 所属グループ=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 + printenv ORACLE_SID TESTDB $
どうやら、sudo経由だと攻撃コードが入っていると環境変数として設置されないようにサニタイズをしている様子。
2004年にはCVE-2004-1051として報告&修正されている。
特権昇格が問題だったというのもあると思うけど、bashの問題をsudoが肩代わりして修正してしまい、bash側は放置、という印象を受ける。今の問題で言えば、Apache 等のWebサーバ側で環境変数をサニタイズするみたいな。
何だかモヤっとする後味だったけれど、とりあえず、sudo の方は影響を受けないよってことで。
Firefox 34 で Method Definition 構文が実装された
var o = { method: function method () { return "OK" }, };
と書いていたところを
var o = { method() { return "OK"; }, };
こう書けるようになる。
Firefox 34 でES6の Object.assign() が実装されたが…
皆さん待望の、mixin をするメソッドである。
第一引数のtargetに、第二引数以降のプロパティの値を代入していく。
var obj = { foo: "FOO", }; var res = Object.assign(obj, { bar: "BAR" }, { piyo: "PIYO" }); obj.bar; // => "BAR"; obj.piyo; // => "PIYO" res === obj; // => true
代入されるプロパティは:
- ソースとなるオブジェクト自身のプロパティ(つまり、[[Prototype]]から継承したものは含まない)
- 列挙可能であること(enumerable属性がtrueであること)
簡易的には以下の様な実装
Object.assign = function(target, ...sources) { for (let source of sources) { let keys = Object.getOwnPropertyNames(source); for (let key of keys) { let desc = Object.getOwnPropertyDescriptor(source, key); if (desc && desc.enumerable) { target[key] = source[key]; } } } return target; }
大まかな話はこんな感じ。
次は細かい話。
デスクリプタのコピーではなく、代入(=)がされる
上記簡易コードで、target[key] = source[key];
と書いた。
本来、プロパティのコピーをするならObject.defineProperty(target, key, desc)
と書くところだが、仕様も実装もそうなっていない。(個人的には気に食わない点)
var obj = Object.assign({}, { get foo() { return "FOO"; }, });
というコードがあった場合、obj.fooはgetterではなく、ただプロパティとして代入される。
もっと酷い例を挙げると、
var obj = Object.assign({}, { get __proto__() { return null; }, });
これ、obj.__proto__ = null
しているのと同義になり、[[Prototype]&93;が書き換えられるというアレなことが起きる。
Symbolもあるんだよ
現状のFirefoxの実装( https://hg.mozilla.org/mozilla-central/diff/53769e48d35b/js/src/builtin/Object.js )では抜けているが、Symbol型のプロパティも代入の対象である。
上記の簡易実行コードは、下記のように書いたほうが正確である...(仕様が後で書き換わる可能性もあるが、現状では)
Object.assign = function(target, ...sources) { for (let source of sources) { let keys = [...Object.getOwnPropertyNames(source), ...Object.getOwnPropertySymbols(source)]; for (let key of keys) { let desc = Object.getOwnPropertyDescriptor(source, key); if (desc && desc.enumerable) { target[key] = source[key]; } } } return target; }
現状のFx34のコードは仕様に沿ってないので、 https://bugzilla.mozilla.org/show_bug.cgi?id=937855#c32 にコメントだけは残しておいた。何の反応もなく進んでしまうようなら、正式にバグ登録したい。
Firefox 34でES6構文の ComputedPropertyName が実装された
また、新しい構文。
オブジェクトリテラル中のプロパティ名部分に式を入れることができる様になる。
今まで
var prop = "foo"; var obj = {}; obj[prop] = "FOO Property";
今までは一旦オブジェクトを作ってから代入するしかなかったものが、
これから
var prop = "foo"; var obj = { [prop]: "FOO Property" };
とできるようになる。
プロパティ名の部分に、[expr]
と書く。
何が嬉しいの?
まぁ、普通に便利ですね。
と、これだけではアレなのでもう少し書くと、ECMAScript6thになってSymbolという型が登場し特殊なSymbol型のプロパティを定義できるわけだが、このComputedPropertyNameの構文がないとオブジェクトリテラルや将来的に実装されるであろうClass構文でSymbol型のプロパティを定義できるようにという図らいである。
より具体的には、Class構文でクラスを定義し、for-ofで回せるよう@@iterator
を定義したいとすると
class KeyValue { constructor() { // ... } set(key, value) { // ... } } KeyValue.prototype[Symbol.iterator] = function* () { for (var key of Object.keys(this).sort()) { yield [key, this[key]]; } };
などと書くとせっかくClass構文を使ってクラスっぽい書き方をしてラップしても内部のprototype
プロパティに直接アクセスとかしちゃってたら意味がなく馬鹿馬鹿しい。
どうせClass構文を使うなら、以下のように完全にクラスとして書けたほうが良い。
class KeyValue { constructor() { // ... } set(key, value) { // ... } *[Symbol.iterator]() { for (var key of Object.keys(this).sort()) { yield [key, this[key]]; } } }
ま、この辺りの考察は個人的なものだから実際はどうだか知らないけどね :P
Firefox 34(Nightly)で String.raw が実装された件とTaggedTemplateについて
TemplateLiteral の続き。
前回は、`
(バッククォート) でくくることで、ヒアドキュメント的な複数行に渡る文字列の生成ができる事、${expression}
で式の埋め込みができる事を書いた。
実は、func`...`
という構文の追加もあり、funcを文字列の素となる値を引数に呼び出す機能もある。String.raw
はそのためのメソッドだ。func`...`
みたいなコードはTaggedTemplateと呼ばれている。
String.raw`Title: ${document.title}` // => "Title: hogehoge@teramako" // === `Titlte: ${document.title}`
String.raw は、TaggedTemplateの超基本的な実装であり、ただのTemplateと同じである。
TaggedTemplateの関数に渡る引数
func`a\`${"B"}c${"D"}\``
と書いた時、func に渡される引数は以下のようになる。
- 各文字列部分の配列
["a`", "c", "`"]
- この配列には、
raw
プロパティが追加されていて、["a\`", "c", "\`"]
が入っている
${"B"}
の式を実行した結果- "B"
${"C"}
の式を実行した結果- "C"
第一引数の配列の間に後続の引数たちが埋まっていく感じだ。
何が楽しいの?
JSで書かれたRuby実装があれば、以下のように書けるかもしれない。
ruby` for num in 1..100 do if num % 15 == 0 puts "FizzBuzz" elsif num % 5 == 0 puts "Buzz" elseif num % 3 == 0 puts "Fizz" else puts num end end `
HTML生成が楽になるかもしれない
HTMLElement.prototype.appendHTML = function (...args) { this.insertAdjacentHTML("BeforeEnd", String.raw(...args)); } document.body.appendHTML` <p> Hello, <b>${User.name}</b>. </p>`
という感じで、個人的には面白い機能だと思ってる。あまり注目されてない気がするけど…
あ、そうそう、入力文字列をevalしたいけど、セキュリティ上関数コールはさせないみたいなフィルタリングをしている場合、func()
以外にもfunc``
もフィルタリングしないと思われるので気をつけてね。
Symbolの使い道 - 他に迷惑を掛けないプロトタイプ拡張
先日 Firefox Nightly で Symbol が実装されたわけだが、その使い道を考えていた。それで思いつたのが、Symbolを使えば迷惑を掛けずにプロトタイプ拡張ができるのでは、ということ。
こんな感じ。
"use strict"; { let keys = Symbol.for("teramako.Object.keys"), values = Symbol.for("teramako.Object.values"), entries = Symbol.for("teramako.Object.entries"); Object.prototype[keys] = function* () { yield * Object.keys(this); }; Object.prototype[values] = function* () { for (var key of this[keys]()) yield this[key]; }; Object.prototype[entries] = function* () { for (var key of this[keys]()) yield [key, this[key]]; }; }
- プロパティキーは文字列ではなく、シンボルなので、
Object.keys
,Object.getOwnPropertyNames
,for-in で出てくることはない。 - 他所からも呼び出せるように
Symbol.for()
でSymbolをつくる
他コンテキストのブロックからも以下の様な感じで呼び出せる。
let entries = Symbol.for("teramako.Object.entries") for (var [k, v] of obj[entries]()) { console.log(k + ": " + v); ...; }
この完全にプライベートというわけでもなく、かと言ってパブリックな感じでもない微妙な感じが堪らなく良く思えるのだが、どうだろう?