目次
はじめに
この度、私 id:snowman_mh は、FirefoxのJavaScriptエンジンである「SpiderMonkey」のコミッターになりました。
この記事では、その経緯やコントリビュートの方法などをまとめたいと思います。
何をしたか早く読みたい方は「やったこと」まで飛んでください。
自己紹介
僕は、「東京大学工学部電気電子学科」というところの学部3年生に所属しています。
大学の授業やインターンシップで日々プログラミングの勉強をしています。電気系の勉強もしています。
そして、僕が所属する電気電子学科および電子情報学科には「電気電子情報実験」という、欠席すると留年する授業があります。
今回は、その実験の一環でOSSにコントリビュートする経験ができましたので、実験報告書を兼ねたブログを書いている次第であります。
大規模ソフトウェアを手探る
実験というからには様々なテーマがあり、その中から自分の好きなテーマをある程度選択することができます。
今回僕が参加したのが、その中の1つである「大規模ソフトウェアを手探る」というテーマの実験です。
実験の目的
「『演習レベルの小さなプログラムが作れること』と『実用規模のプログラムが作れること』のギャップを埋める(ための知識と経験を得る)」ことを目的として実施されている実験です。
また、ソフトウェアを改良するためのアイデアを出し合ってみて、whatとhowを考える力を養うことも目的とされています。
実験の内容
超簡単に説明すると、この実験では「10日間(1日4時間程度)かけて、OSSとして公開されている大規模なソフトウェアに手を入れる作業を体験してみる」ということをします。
僕はFirefoxのJavaScriptエンジンである「SpiderMonkey」というソフトウェアを題材にしました。
他のチームは
- Slackライクなチャットサービス「Gitter」
- 仮想暗号通貨の「zcash」
- 無料オフィスソフトの「LibreOffice」
- 皆さんご存知「Google Chrome (Chromium)」
などを題材としていました。
実験の報告書
さらに、この実験でのいわゆる報告書、レポートは「ブログ」という形で提出することが認められています。
この実験を担当している田浦先生の、「『やったことを10日間も顔を合わせた先生やTAにだけ見てもらう』のではなく、『全世界の同じようなことをしたい人たちに見てもらう』ほうが有意義である」という考えによるものらしいです(かっこいい)。
SpiderMonkeyについて
WebブラウザであるMozilla FirefoxのJavaScriptエンジンの名前です。
ECMAScriptとは
JavaScriptは主にブラウザで動作する言語なので、処理系がブラウザに依存するという特徴があります。
しかし、ブラウザごとに書いたコードを動作が違うとプログラミングやめたくなります。
それを防ぐために(それだけではないが)、ECMAScriptという規格でJavaScriptの標準が定められています。
その標準に従ってそれぞれのブラウザがJavaScriptの処理系を実装しています。
JavaScriptエンジン
前述したように、JavaScriptエンジン(JavaScriptの処理系)はブラウザによって違います。
Google ChromeはV8という名前だったり、SafariはJavaScriptCoreという名前だったりします。
そして今回コントリビュートしたMozilla FirefoxのJavaScriptエンジンはSpiderMonkeyという名前です。
SpiderMonkeyでは、JavaScriptコードをコンパイラがバイトコードと呼ばれる中間言語に変換し、インタプリタが解釈し、CPUがネイティブコードを実行する、という順序で処理されます。
コミッターがいた...
この実験には学部生をサポートするTAが数名います。
なんとその中にSpiderMonkeyに日頃からコントリビュートしているコミッターの方がいました。
Mozillaからウチで一緒にやらないかと言われているとかいないとか。
今回この題材を選んだのも、そんな凄腕コミッターのサポートが受けられるから、というのが大きいです。
なんてったってそのTAさんがIssueをアサインすることも、コードをレビューすることも、マージすることもできますからね。
自分の力だけでは絶対にここまでの成果を出すことはできませんでした。
TAさんには感謝してもし切れません。
やったこと
この実験で実際に行ったことをまとめます。
チュートリアル
最初に全員の共通課題として「gnuplot」というグラフ描画ソフトに変更を加えることをチュートリアルとして行います。
gnuplotでは、
gnuplot> plot sin(x)
とするとsin(x)
が描画されますが、これを
gnuplot> sin(x)
とするだけで描画されるように変更するという内容でした。
詳しい手順はこちらにまとまっているので、体験したい方は読んでみてください。
SpiderMonkeyを手探る
さて、ここからやっと本題ですが、SpiderMonkeyのコードを手探っていこうかと思います。
まずは何よりも環境構築をする必要があります。
前述しましたが、この実験のTAにはSpiderMonkeyのコミッターがいます。
その方がまとめてくれている資料を参考にして環境構築を行いました。
そして、実験で僕が行ったコントリビュート手順の詳細は別記事にまとめました。
実験の背景とかはどうでも良いからコントリビュートする手順だけ知りたい人のためです。
エラーメッセージを改善する
僕は今回の実験のテーマとして「SpiderMonkeyのエラーメッセージを改善する」というのを選びました。
このページに改善すべきエラーメッセージがまとめられていて、比較的小さな変更から大きめの変更まで様々なBugがあり、小さなタスクでコントリビュートを体験してみて、最後に大きめのタスクに挑戦できそうというモチベーションで選びました。
結果として3つのIssueを解決することができたので、それぞれの詳細を紹介したいと思います。
その1 - クラスを再宣言したときのエラーメッセージ
JavaScriptでは、変数を定義するためのキーワードとしてvar, let, const
などがあります。
var
で定義された変数は同名変数を定義することができますが、let, const
は同名変数を定義することができません。
js> var a; var a; // 問題なし js> let b; let b; SyntaxError: redeclaration of let b js> const c = 1; const c = 1; SyntaxError: redeclaration of const c
同様にclass
も同名クラスを定義することができませんが、そのエラーメッセージを見てみましょう。
js> class Foo {}; class Foo {}; SyntaxError: redeclaration of let Foo
なんと、class
なのにlet
とか言っちゃってます。
これを、
SyntaxError: redeclaration of class Foo
って言ってくれるように変更しました。
このバグレポートページはこちらです。
最後に僕の名前が入ったコミットページへのリンクもコメントされています。
変更内容としては5行程度でした。
軽い変更でコントリビュートが体験できたとともに、これだけで解決するIssueがあるんだなと思いました。
誰にでもコントリビュートのチャンスってあるんですね。
詳しい変更内容や、どのように変更点を探したのかは別記事にまとめました。
その2 - super();を呼ばなかったときのエラーメッセージ
JavaScriptではクラスを継承することができます。
以下のJavaScriptコードを見てください。
class B {}; class A extends B { constructor() { this.someValue = 'value'; } } new A();
このコードはReferenceError: |this| used uninitialized in A class constructor
というエラーを吐きます。
これは継承先のコンストラクタで、継承元のコンストラクタを呼ぶ前にthis
にアクセスしていることによって起きているエラーですが、とても分かりにくいという問題が報告されていました。
以下のコードのようにsuper();
を呼ぶ必要があります。
class B {}; class A extends B { constructor() { super(); this.someValue = 'value'; } } new A();
これで正常に動作します。
同様に、継承元のコンストラクタでアローファンクション内でthis
にアクセスしても似たようなエラーを吐きます。
class B {}; class A extends B { constructor() { (() => { this.someValue = 'value'; })() } } new A();
ReferenceError: |this| used uninitialized in arrow function in class constructor
こちらも同様にアローファンクションの前にsuper();
を呼ぶと解決します。
これらのエラーメッセージを次のように表示されるように変更しました。
ReferenceError: must call super constructor before using |this| in A class constructor
ReferenceError: must call super constructor before using |this| in arrow function in derived class constructor
このバグレポートページはこちらです。
変更内容としてはこちらも5行程度でした。
っていうか1個目のIssueよりも簡単でした(文字列を変えただけなので…)。
しかし、1個目と違ってテストにも変更を加えたので、3個目に活きる体験ができました。
詳しい変更内容や、どのように変更点を探したのかは別記事にまとめました。
その3 - in演算子に関するエラーメッセージ
JavaScriptでは、in
演算子という二項演算子があります。
in
は、右辺のオブジェクトが左辺の名前のプロパティを持っているかどうかの真偽値を返します。
js> class A {}; js> let a = new A(); js> "hello" in a; false js> a.hello = "hello world"; js> "hello" in a; true
in
の右辺にオブジェクトでない値(文字列や真偽値など)を持ってきてしまった場合、次のようなエラーを吐きます。
js> 'hello' in 'hello world'; TypeError: invalid 'in' operand "hello world"
オペランドにオブジェクト以外を取ることができないので当たり前ですね。
しかし、Pythonを書いていた人から見ると、「なんで文字列をオペランドに取れないの!?」となります。
なぜならPythonでは、'hello' in 'hello world'
と書くとTrue
が返ってくるからです。
そう、Pythonでは、in
演算子というと、部分文字列が文字列内に含まれるかどうかの真偽値を返す演算子なのです。
ここでは、右辺にオブジェクト以外の値を持ってきたことが悪いと伝わりにくいエラーメッセージを出力していたことが問題となっていました。
そこで、次のようなエラーメッセージが出力されるように変更しました。
js> 'hello' in 'hello world'; TypeError: cannot use 'in' operator to search for 'hello' in 'hello world'
また、非常に長い文字列を両辺に取った場合、文字列が省略されて表示されるようにしました。
Google Chromeでは長い文字列のまま表示されているので、Firefoxの勝ちですね!!!
js> 'hello'.repeat(100) in 'hello'.repeat(100) TypeError: cannot use 'in' operator to search for 'hellohellohelloh...' in 'hellohellohelloh...'
このバグレポートページはこちらです。
変更内容としては90行程度でした。
比較的大きな変更を加えることができて嬉しいです。
詳しい変更内容や、どのように変更点を探したのかは別記事にまとめました。
おわりに
大学の授業やインターンシップでプログラミングを勉強していますが、ここまでガッツリOSSにコントリビュートしたのは初めてで、非常に刺激的で有意義な経験ができました。
今回の実験で得た大規模なソフトウェアを手探るコツのようなものはこれからも活かすことのできる貴重なスキルになっていくと思います。
今回僕が行ったことのすべては本記事およびリンク先の別記事にまとめてありますので、それを読めば誰でもFirefox、SpiderMonkeyのコミッターになれるはずです(?)。
また、ひよっこエンジニアの僕がここまでの成果を出すことができたのも、ひとえにSpiderMonkeyのコミッターであるTAさんが手取り足取り指導くださったおかげです。
本当にありがとうございました!
最後になりますが、このような貴重な体験のできる実験を用意してくださった先生にもとても感謝しています。
今までの実験レポートは前日に徹夜で書くことが多かったのですが、このレポートは実験終了前に書き始めたくらい楽しかったです。