新人女子の書いたコードを直すだけの簡単なお仕事した

paizaという所で開催されている新人女子プログラマの書いたコードを直すだけの簡単なお仕事です! | paizaオンラインハッカソン(POH)をやってみた。

たくさんある商品の中から2つを選んで設定された合計金額を超えない範囲でなるべく近い金額にするという問題。
やることは

  1. 商品の価格を受け取る
  2. 目標とする合計金額を受け取る
  3. 商品の組み合わせの中から条件に近い値を探す
  4. 結果を出力

ソートしてから探す

組み合わせから値を探す方法は、価格をソートした後で、高い側と安い側から順に商品を選んで探すやり方を考えた。
価格の配列をp、商品の個数をN、目標金額をmとして、

  1. j=0, k=N-1から開始
  2. p[j] + p[k] > m ならkを減らす
  3. p[j] + p[k] == m なら終了
  4. p[j] + p[k] < m なら過去の最良値と比較し、良ければ更新
  5. jを1つ増やして2から繰り返す

実際に動かしてみると、最初にkを減らす処理が多いので二分探索に変更。

二分探索

  1. t=1, k=0とする
  2. tがN以上になるまで、tを2倍にする計算を繰り返す
  3. tを2で割る
  4. k+tがN以上なら3に戻る
  5. p[k+t] < m ならkにtを加える
  6. t > 1 なら3に戻る

kの上位ビットから順に立てられるかどうか調べていく感じ。
ここまでやって0.18秒

商品点数が多すぎる

商品点数を20万、価格と目標金額を10〜100万の範囲でランダムとして計算したとき、計算結果のjの値はあまり大きくならない。だいたい10以下で済む。目標金額と一致する組み合わせが早々に見つかって計算が打ち切られているということになる。
商品点数を半分にすればソートにかかる時間は半分以下になるし、商品点数を削っても計算後のjが大きくなるだけで目標金額=合計金額となる別の組み合わせが見つかるので計算結果は変わらない…はず。
もちろん目標金額=合計金額とならなかった場合のやりなおし処理は必要だけれども。

探索に時間がかかるようになった

商品点数を削って計算したら結果を維持したまま速くできた。
ソートは速くなるが、その後ソート済みの配列上でjとkを詰めていく処理に時間がかかるようになった。
こんな感じ。

商品点数 ソート 最良値の探索 二分探索
多い 遅い 速い 速い
少ない 速い 遅い 速い

jとkを詰める処理が意外と遅いので商品点数を削りすぎてもいけない。いくつが最適なんだろうか、わからない。

結果

#!/usr/bin/perl

use strict;

my @m;
my @p;
my @p_not_used;
my $ENOUGH_N = 40000;

my ($N, $D) = split(/ /, <STDIN>);
$N = int($N);
$D = int($D);

# read price
for(my $i=0; $i<$N; $i++){
	push @p, int(<STDIN>);
}
if(@p > $ENOUGH_N){
	@p_not_used = splice @p, $ENOUGH_N;
}

@p = sort { $a <=> $b } @p;

# read limit
for(my $i=0; $i<$D; $i++){
	push @m, int(<STDIN>);
}

# process each day
foreach my $m (@m){
	my $best = 0;

	my $j=0;
	my $k=0;

	# find upper limit
	my $t=1;
	$t *=2 while $t < @p;
	while($t > 1){
		$t /= 2;
		next if $k + $t >= @p;
		$k += $t if $p[$k+$t] + $p[0] <= $m;
	}

	# look for better result
	while($j < $k){
		if($p[$j] + $p[$k] > $m){
			$k--;
			next;
		}
		my $current = $p[$j] + $p[$k];
		if($best < $current){
			$best = $current;
			last if $best == $m;
		}
		$j++;
	}

	if($best != $m && @p_not_used > 0){
		# add unused items
		if(@p_not_used > $ENOUGH_N){
			push @p, splice @p_not_used, 0, $ENOUGH_N;
		}else{
			push @p, @p_not_used;
			@p_not_used = ();
		}

		@p = sort { $a <=> $b } @p;
		
		redo;
	}
	print "$best\n";
}

これで0.10秒
あとは最初の入力が遅い。

Googleリーダーのナビゲーションの幅を変える

Googleリーダーが新しくなって以前作ったスタイルが使えなくなってたので更新。
ナビゲーションの幅を広くするスタイルシート
FirefoxStylishプラグインで使用。

@-moz-document url-prefix(https://www.google.com/reader),
               url-prefix(http://www.google.com/reader),
               url-prefix(http://www.google.co.jp/reader),
               url-prefix(https://www.google.co.jp/reader) {
/* ナビゲーションの幅 */
#nav{width:360px !important;}

/* ナビゲーションの影の幅 */
#scrollable-sections-top-shadow,
#scrollable-sections-bottom-shadow
{width:360px !important;}

/* 登録フィード - フォルダ - アイテム */
.scroll-tree .folder .folder .name-text
{max-width:221px !important;}

/* 登録フィード - アイテム */
.scroll-tree .folder .name-text
{max-width:221px !important;}

/* 本文の左マージン */
#chrome{margin-left:360px !important;}
}

CentOS 5.7にnamazu-2.0.21を導入

CentOSで動かしているサーバーにNamazu全文検索を導入したときの手順

パッケージ管理はyumで行っているのでrpmパッケージを作成してからインストールしたいが、CentOS用のrpmパッケージが見当たらないのでFedoraからsrpmをもらってきてrpmパッケージをビルドする。

rpmbuildの作業ディレクトリを設定

作業ディレクトリを作る。何でもいいが、ホームディレクトリにrpmという名前で作ることにする。

$ mkdir ~/rpm
$ cd ~/rpm
$ mkdir BUILD SOURCES SPECS SRPMS RPMS
$ pwd
/home/yuyakko/rpm

pwd絶対パスを確認したらそれを ~/.rpmmacros に記入。

%_topdir /home/yuyakko/rpm

署名の用意

yumでインストールするには署名をつけないといけないらしいので、その設定をする。
rpmコマンドでインストールするのなら不要。
まず鍵を作成。

$ gpg --gen-key

設定はデフォルトで。

ご希望の鍵の種類を選択してください:
   (1) DSAとElgamal (既定)
   (2) DSA (署名のみ)
   (5) RSA (署名のみ)
選択は?
DSA keypair will have 1024 bits.
ELG-E keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
要求された鍵長は2048ビット
鍵の有効期限を指定してください。
         0 = 鍵は無期限
        = 鍵は n 日間で満了
      w = 鍵は n 週間で満了
      m = 鍵は n か月間で満了
      y = 鍵は n 年間で満了
鍵の有効期間は? (0)
Key does not expire at all
これで正しいですか? (y/N) y

名前とかは適当に。

あなたの鍵を同定するためにユーザーIDが必要です。
このソフトは本名、コメント、電子メール・アドレスから
次の書式でユーザーIDを構成します:
    "Heinrich Heine (Der Dichter) "

本名: yuyakko
電子メール・アドレス:
コメント:
次のユーザーIDを選択しました:
    “yuyakko”

パスフレーズを省略すると

パスフレーズが不必要なようですが、おそらくそれはろくでもない
考えです! いちおう続行します。パスフレーズは、このプログラム
の“--edit-key”オプションでいつでも変更できます。

などと罵られるので一応設定しておく。
途中でエントロピーが足りなくなった。

十分な長さの乱数が得られません。OSがもっと乱雑さを収集
できるよう、何かしてください! (あと283バイトいります)

よくあること。こういう時は別のSSHセッションから find / などでエントロピー(乱雑さ)を稼ぐ。
鍵が作成できたらrpmbuildでパッケージ作成時にその鍵を使えるように設定する。
man rpm によると

例えば、実行ファイルが /usr/bin/gpg で、鍵リングが /etc/rpm/.gpg にあり、その中のユーザ "John Doe " としてパッケージに GPGを使って署名する場合には


%_signature gpg
%_gpg_path /etc/rpm/.gpg
%_gpg_name John Doe
%_gpgbin /usr/bin/gpg


をマクロの設定ファイルに含めれば良い。システム全体の設定には /etc/rpm/macros を、ユーザごとの設定には ~/.rpmmacros を使用する。

ということなので ~/.rpmmacros に設定を追記する。

%_signature gpg
%_gpg_name yuyakko

キーリングのパスとgpgコマンドのパスは標準でいいので省略。
%_gpg_name には鍵の作成時に出てきたuidを設定。忘れたら gpg -K コマンドで表示。
以上でrpmパッケージ作成時に署名できるようになった。
次にその署名が信用できるということをrpmコマンドに教える。

$ gpg -a --export yuyakko > RPM-GPG-KEY.yuyakko.txt
$ sudo rpm --import RPM-GPG-KEY.yuyakko.txt

gpgの--exportオプションで公開鍵を取り出し、それをrpmの--importオプションでrpmのデータベースに追加する。
これでyumrpmに署名の確認をさせた時にOKとなる。

srpmの取得

なんとなくCentOSに似てそうなFedoraのパッケージデータベースに欲しいsrpmがあるのでそこからもらってくる。
srpmが必要なパッケージ

kakasiは使わないが、namazuのパッケージがkakasiのパッケージに依存しているので一応入れておく。
ここから検索してダウンロード。
https://admin.fedoraproject.org/pkgdb/

  1. パッケージ名を入力
  2. PACKAGESを押して検索
  3. 欲しいパッケージを選択
  4. Build Status
  5. Buildsの中から適当な物を選ぶ
  6. RPMsにあるsrcをダウンロード
$ cd ~/rpm
$ wget http://kojipkgs.fedoraproject.org/packages/namazu/2.0.19/5.fc15/src/namazu-2.0.19-5.fc15.src.rpm
$ wget http://kojipkgs.fedoraproject.org/packages/mecab/0.98/1.fc15/src/mecab-0.98-1.fc15.src.rpm
$ wget http://kojipkgs.fedoraproject.org/packages/mecab-ipadic/2.7.0.20070801/4.fc15.1/src/mecab-ipadic-2.7.0.20070801-4.fc15.1.src.rpm
$ wget http://kojipkgs.fedoraproject.org/packages/kakasi/2.3.4/30.fc15/src/kakasi-2.3.4-30.fc15.src.rpm
$ wget http://kojipkgs.fedoraproject.org/packages/perl-Text-Kakasi/2.04/15.fc15/src/perl-Text-Kakasi-2.04-15.fc15.src.rpm

Namazuは2.0.19までしかなかったのでそれを取得しておいた。

MeCabのインストール

まずソースを展開。

$ rpm -i --nomd5 mecab-0.98-1.fc15.src.rpm

--nomd5を指定しないとチェックサムが適合しないというエラーになる。CentOSに入っているrpmコマンドがsha256をサポートしていないのが原因らしい。仕方ないのでチェックサムの検査を行わないように指定している。
Namazuから使うならeucだけでいいけど、もし他の用途で使うことになったらたぶんUTF-8で使いたくなるのでUTF-8もサポートするように変えておく。SPECS/mecab.specを開いて編集。

%configure
↓
%configure --with-charset=utf8

ビルドしてインストールする。

$ rpmbuild -ba --sign SPECS/mecab.spec
$ sudo yum install RPMS/x86_64/mecab-0.98-1.x86_64.rpm
$ sudo yum install RPMS/x86_64/mecab-devel-0.98-1.x86_64.rpm

辞書も同じように展開・ビルド・インストールする。

$ rpm -i --nomd5 mecab-ipadic-2.7.0.20070801-4.fc15.1.src.rpm
$ rpmbuild -ba --sign SPECS/mecab-ipadic.spec
$ sudo yum install RPMS/x86_64/mecab-ipadic-2.7.0.20070801-4.1.x86_64.rpm
$ sudo yum install RPMS/x86_64/mecab-ipadic-EUCJP-2.7.0.20070801-4.1.x86_64.rpm

Kakasiのインストール

$ rpm -i --nomd5 kakasi-2.3.4-30.fc15.src.rpm
$ rpmbuild -ba --sign SPECS/kakasi.spec
$ sudo yum install RPMS/x86_64/kakasi-2.3.4-30.x86_64.rpm
$ sudo yum install RPMS/x86_64/kakasi-libs-2.3.4-30.x86_64.rpm
$ sudo yum install RPMS/x86_64/kakasi-devel-2.3.4-30.x86_64.rpm
$ sudo yum install RPMS/x86_64/kakasi-dict-2.3.4-30.x86_64.rpm
$ rpm -i --nomd5 perl-Text-Kakasi-2.04-15.fc15.src.rpm
$ rpmbuild -ba --sign SPECS/perl-Text-Kakasi.spec
$ sudo yum install RPMS/x86_64/perl-Text-Kakasi-2.04-15.x86_64.rpm

依存関係があるのでperl-Text-Kakasiをビルドする前にkakasiをインストール。

Namazuのインストール

srpmを展開する。

$ rpm -i --nomd5 namazu-2.0.19-5.fc15.src.rpm

最新の2.0.21を入れたいのでソースコードを取得してきて入れ替え。

$ wget http://www.namazu.org/stable/namazu-2.0.21.tar.gz
$ cp namazu-2.0.21.tar.gz SOURCES/
$ rm SOURCES/namazu-2.0.19.tar.gz

それに合わせて SPECS/namazu.spec も変更。

Version:        2.0.19
Release:        5%{?dist}
↓
Version:        2.0.21
Release:        1%{?dist}

このソースからのパッケージ作成は1回目なのでReleaseは5から1に変更しておく。実際にリリースするわけではなく、自分で使うだけなので他の適当な数字でもいい。
他と同じようにビルド・インストールする。

$ rpmbuild -ba --sign SPECS/namazu.spec
$ sudo yum install RPMS/x86_64/namazu-libs-2.0.21-1.x86_64.rpm
$ sudo yum install RPMS/x86_64/namazu-2.0.21-1.x86_64.rpm
$ sudo yum install RPMS/x86_64/namazu-devel-2.0.21-1.x86_64.rpm
$ sudo yum install RPMS/x86_64/namazu-cgi-2.0.21-1.x86_64.rpm

できた。

ニコニコのマイリストに動画IDを出すNicoCache_nlフィルター

リク生でよくリクエストする動画を集めたマイリストがあるんですが、動画のIDを取り出すときに毎回URLをコピーして削るのが面倒だったのでIDを表示させるフィルターを書いた。

NicoCache_nlのnlFiltersディレクトリに新しいテキストファイルを作って次のように書く。

# nlフィルタ定義(文字コード判定用なのでこの行は削除しないこと)
# 自分用

[Replace]
Name = マイリストに動画ID表示
URL = www\.nicovideo\.jp/my/mylist(/|$)
Match<
<dt>マイ:</dt><dd>.*</dd>
>
Replace<
$0
	{if \$item.item_data.group_type == "default"}<dt>ID:</dt><dd><input type="text" size="7" value="{\$item.item_data.watch_id|escape:html}" readonly style="font-size: xx-small; margin: 0px; border: solid 1px silver; padding: 0px" onclick="this.select()"></dd>{/if}
>

マイページのマイリストで動画のIDが出るようになる。クリックで選択。

Windows 7でメニューが左側に表示されて気持ち悪い

いつの間にこんなことになってしまったかわからないんですが、プルダウンメニューが左側に表示されるようになってしまいました。

探してみると同じ問題で困っている人が結構いるようです。
どうやら

ということみたいです。タブレットに関係ありそうな設定を探しているとコントロールパネルに「Tablet PC 設定」というのがあったので開いてみる。

変なことが書いてありますね。試しに左ききにしてみると…

直りました。私、右利きですけど右にメニューを出したいです。

せっかくだから探検に行く

目標地点があると探検し甲斐がありますよね。

ソースコードを手に入れる

https://developer.mozilla.org/ja/Developer_Guide/Source_Code/Mercurial

# 数百メガバイトの履歴が .hg フォルダにダウンロードされるため、しばらく時間がかかります。

時間かかりすぎて諦めた。よく見たらアーカイブもあるらしい。
https://developer.mozilla.org/ja/Developer_Guide/Source_Code/Downloading_Source_Archives
今使ってるのは3.5.9なので…
ftp://ftp.mozilla.org/pub/mozilla.org/firefox/releases/3.5.9/source/

名前 サイズ 最終更新日時
firefox-3.5.9.source.tar.bz2 46350 KB 2010/03/15 7:46:00

ダウンロードする。大きいなあ。

ビルドする

説明があるのでこれを見ながら進める。
https://developer.mozilla.org/ja/Developer_Guide/Build_Instructions
Windows版のビルドに必要なものを説明した文書があるので読みながら道具をそろえる。
https://developer.mozilla.org/ja/Developer_Guide/Build_Instructions/Windows_Build_Prerequisites
親切だなあ。他はあるのでMozillaBuildだけダウンロードしてインストールする。
これで必要なものがそろったみたい。

ソースアーカイブを展開。ファイル数が4万近い。

標準オプションの状態で configure および make を実行しても、動作するビルドを作り上げることはできません。 .mozconfig ファイルを使って、相応のリリースビルドを入手してください。

まだ準備がいるらしい。
https://developer.mozilla.org/ja/Configuring_Build_Options
ここを見ながら .mozconfig を書いてソースディレクトリに入れる。

.mozconfig に次の行を追加することでオブジェクトディレクトリが有効になります。
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../obj-@CONFIG_GUESS@

Firefox以外ビルドする予定ないけど @TOPSRCDIR@/../mozilla-ff-obj-@CONFIG_GUESS@ にしておく。

ブラウザ (Firefox)
.mozconfig ファイルで Firefox の デフォルトの mozconfig ファイル を読み込んでください。
. $topsrcdir/browser/config/mozconfig

ここに書いてある通りに .mozconfig に書く。

ac_add_options --disable-optimize
最適化を無効にします。デバッガでの解析を簡易化します。

デバッガ使うかもしれないので --disable-optimize 入れる。

ac_add_options --enable-debug
デバッグマクロや他のデバッグ専用コードを有効にします。ビルドは著しく遅くなりますが、パッチを書く際に大変有用です。

ビルドの時間が長くなるのは嫌なので --enable-debug 入れない。

ac_add_options --disable-tests
デフォルトでは多くの補助的なテストアプリケーションがビルドされます。これらはデバッグmozilla のソースにパッチを当てるのに役立ちます。これらのテストアプリケーションを無効にすることでかなりビルド時間を短縮し、ディスクスペースを減らすことができます。

テストは無効にしておこう。あとはデフォルトのままにしておく。
こんな .mozconfig ファイルができた。

# My first mozilla config
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../mozilla-ff-obj-@CONFIG_GUESS@
. $topsrcdir/browser/config/mozconfig
ac_add_options --disable-optimize
ac_add_options --disable-tests
ac_add_options --disable-mochitest

準備完了。
2008はVC9らしいので C:\mozilla-build\start-msvc9.bat を起動。けっこう時間かかる。
ソースは D:\Projects\mozilla\mozilla-1.9.1 に入れたのでそこに移動してからビルド。

$ cd /d/Projects/mozilla/mozilla-1.9.1/
$ make -f client.mk build

はい、失敗。

nspr4.dllのビルドがうまくいってない。
どうやらバグらしい。
https://bugzilla.mozilla.org/show_bug.cgi?id=338224
--disable-optimize を指定していて --enable-debug を指定していないという場合にビルドできない。
.mozconfig を修正。

# My first mozilla config
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../mozilla-ff-obj-@CONFIG_GUESS@
. $topsrcdir/browser/config/mozconfig

# https://bugzilla.mozilla.org/show_bug.cgi?id=338224
ac_add_options --disable-optimize
ac_add_options --enable-debug

ac_add_options --disable-tests
ac_add_options --disable-mochitest

今度は成功。makeを動かしてから終了するまで一時間半ぐらいかかる。

プロファイルを作る

基本的には普段使っているFirefoxと同じものなのでそのまま動かしてもいいんですが、念のためにプロファイルを分けることにする。

"C:\Program Files\Mozilla Firefox\firefox.exe" -ProfileManager

これでプロファイルマネージャが起動できるので新しいプロファイルを作成し、起動時に毎回選択するように設定しておく。
普段使ってるFirefoxを起動したときはこう。

今回ビルドしたのを起動したときはこんな感じ。

もちろん文字化けもする。

探しに行こう

途方もない量のソースコードから目的の場所を見つけるため、適当な言葉でgrepする。
手がかりは…

  • 文字化けがおきているのはエラーコンソール
  • JavaScriptの例外は化けるけど、他のエラーは化けない
  • 上位バイトが0になっているということは途中でwchar_tからcharにキャストしてるんじゃないだろうか

"error console"という文字列を求めてxpconnectを歩き回ったりlayoutに迷い込んだり、どこをどう進んだかわからなくなった頃、js/src/jsexn.cppの下の方で遭遇。

JSBool
js_ReportUncaughtException(JSContext *cx)
{
    jsval exn;
    JSObject *exnObject;

jsというディレクトリはSpiderMonkeyと同じなのでここはJavaScriptのコード。「キャッチされなかった例外を報告」という関数名はエラーコンソールに例外を出力する処理のように思える。
ここからたどっていくと…

js_ReportUncaughtException
 └→ js_GetStringBytes
    └→ js_DeflateString

jsstr.cppの中のjs_DeflateString

char *
js_DeflateString(JSContext *cx, const jschar *chars, size_t nchars)
{
...
        for (i = 0; i < nbytes; i++)
            bytes[i] = (char) chars[i];
...
    return bytes;
}

キャストしているところを発見。実にあやしい。
jscharって何だろう。JavaScriptのcharってことかな。定義をたどっていくと…

// js/src/jspubtd.h
typedef uint16    jschar;

// js/src/jsotypes.h
typedef JSUint16 uint16;

// js/src/jstypes.h
typedef uint16_t JSUint16;

// VCにはuint16_tが無いみたいなので
// js/src/jsstdint.h
typedef unsigned __int16 uint16_t;

というわけで符号なし16ビット整数。
jsapi.hのコメントには"uint16/jschar ECMA uint16, Unicode char"という記述があるのでJavaScriptで文字列を作るときに使う16ビット値で間違いない。もし最初に書いた「やめてー!」がここを通ったら上位8ビットが0になって下位8ビットだけが残る形の文字化けをするはず。

ソースコードを書き換えてみる

js_DeflateStringが今回の文字化けに関係しているかどうか確認するためにちょっといじる。

char *
js_DeflateString(JSContext *cx, const jschar *chars, size_t nchars)
{
...
        for (i = 0; i < nbytes; i++) {
            if (chars[i] > 0xFF) {
                bytes[i] = 'E';
            } else {
                bytes[i] = (char) chars[i];
            }
        }
...
    return bytes;
}

これで文字化けのとき全部Eになるはず。

確定。

文字化け解消を目指して進む

文字化け解消にたどり着くための道筋が見えてきた。

  • js_DeflateStringのすぐ上に、これと対になりそうなjs_InflateStringという関数がある
  • charとjscharの文字列を相互に変換する関数のようだ
  • どちらもjs_CStringsAreUTF8という変数の真偽で動作が変わるようになっている
  • js_CStringsAreUTF8を真にすればcharの側がUTF-8じなって文字化けが解消できるんじゃないか

js_CStringsAreUTF8について調べる。
js/src/jsprvtd.h

#ifdef JS_C_STRINGS_ARE_UTF8
# define js_CStringsAreUTF8 JS_TRUE
#else
extern JSBool js_CStringsAreUTF8;
#endif

js/src/jsapi.cpp

#ifndef JS_C_STRINGS_ARE_UTF8
JSBool js_CStringsAreUTF8 = JS_FALSE;
#endif

JS_PUBLIC_API(JSBool)
JS_CStringsAreUTF8()
{
    return js_CStringsAreUTF8;
}

JS_PUBLIC_API(void)
JS_SetCStringsAreUTF8()
{
    JS_ASSERT(!js_NewRuntimeWasCalled);

#ifndef JS_C_STRINGS_ARE_UTF8
    js_CStringsAreUTF8 = JS_TRUE;
#endif
}

よくわからないけど、どうやらJS_C_STRINGS_ARE_UTF8を使って切り替えるみたいだ。JS_C_STRINGS_ARE_UTF8を#defineするconfigureオプションがあればいいんだけど。

ない。JS_C_STRINGS_ARE_UTF8を定義するとどうなるのかよく調べないといけない。
js_DeflateStringで化けた文字列がどこに行くのかデバッガでトレースして調べる。

js_ReportUncaughtException
 └→js_ReportErrorAgain
    └→JSContext::errorReporter
      errorReporterは関数ポインタ。NS_ScriptErrorReporterが入ってた。
       └→NS_ScriptErrorReporter
          └→nsAutoString::AssignWithConversion
             └→…(略)…→CopyASCIItoUTF16

js_InflateStringには行かない!
途中で他に気になる部分があったので調べる。js_CStringsAreUTF8は諦める。

void
NS_ScriptErrorReporter(JSContext *cx,
                       const char *message,
                       JSErrorReport *report)
{
...
      const PRUnichar *m = reinterpret_cast<const PRUnichar*>
                                             (report->ucmessage);
      if (m) {
        msg.Assign(m);
      }

      if (msg.IsEmpty() && message) {
        msg.AssignWithConversion(message);
      }

messageに化けた文字列が入ってる。report->ucmessageに何か入ってればそっちを使うんじゃないかな。ucってのがUnicodeっぽいので文字化けしないかも。
js/src/jsapi.h

struct JSErrorReport {
...
    const jschar    *ucmessage;     /* the (default) error message */

エラーメッセージ?これか?
js_ReportUncaughtExceptionの方を見ると、report->ucmessageは0になってる。ucmessageを設定してみる。

JSBool
js_ReportUncaughtException(JSContext *cx)
{
...
    if (!reportp &&
        exnObject &&
        OBJ_GET_CLASS(cx, exnObject) == &js_ErrorClass) {
        const char *filename;
        uint32 lineno;
        const jschar *chars; // 追加

        ok = JS_GetProperty(cx, exnObject, js_message_str, &roots[2]);
        if (!ok)
            goto out;
        if (JSVAL_IS_STRING(roots[2])) {
            bytes = js_GetStringBytes(cx, JSVAL_TO_STRING(roots[2]));
            chars = js_GetStringChars(cx, JSVAL_TO_STRING(roots[2])); // 追加
...
        reportp = &report;
        memset(&report, 0, sizeof report);
        report.filename = filename;
        report.lineno = (uintN) lineno;
        report.ucmessage = chars; // 追加
    }
...
        js_ReportErrorAgain(cx, bytes, reportp);
...

bytesは化けた文字列。charsは正しい文字列のはず。

文字化け直った。


目標地点に到着、終了。

最新版では既に直ってるみたい

Mercurialリポジトリは大きすぎるのでWeb上でソースを見てたんですが、関係しそうな変更を発見。
http://hg.mozilla.org/mozilla-central/rev/34eb552d42f5
Bug 557346
https://bugzilla.mozilla.org/show_bug.cgi?id=557346
3.6.4と3.5.10で修正されているみたいですね。
例外のメッセージは遠慮なく日本語で書くことにする。

FirefoxのエラーコンソールでJavaScriptの例外を見ると上位バイトが消えるみたい

こんなの。

throw Error("やめてー!");

Firefox 3.5.9


「やめてー!」はUnicodeのコードポイントで書くと

U+3084 U+3081 U+3066 U+30FC U+FF01

コンソールに出てきた文字は

U+0084 U+0081 U+0066 U+00FC U+0001

英語で書けということなのか。