きりかノート 3冊め

おあそびプログラミング

MacPortsのport:ruby23を登録

例によってクリスマスに新しいバージョンがリリースされていたので対応しました。従来通り、MacPorts版は

  • ruby2.3, rake2.3, gem2.3などバージョンのsuffixがつく
  • port select ruby ruby23などselectを使うと、suffixなしのバージョンで使える

となっています。

Yosemite環境でのコンパイルエラー調査

portへのコミットはわりとすぐやってたんだけど、OS X 10.10 Yosemite環境のbuildbotがエラーはいてて、手元で再現できなかったりと調べてるうちに年が明けてしまった。。

buildbotのエラー

エラー箇所の抜粋。

   compiling regerror.c
   compiling regexec.c
   Stack dump:
   0.	Program arguments: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang -cc1 -triple x86_64-apple-macosx10.10.0 -emit-obj -disable-free -disable-llvm-verifier -main-file-name regexec.c -mrelocation-model pic -pic-level 2 -mdisable-fp-elim -masm-verbose -munwind-tables -target-cpu core2 -target-linker-version 242.2 -dwarf-column-info -coverage-file /opt/local/var/macports/build/_opt_mports_dports_lang_ruby23/ruby23/work/ruby-2.3.0/regexec.o -resource-dir /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/../lib/clang/6.1.0 -D _FORTIFY_SOURCE=2 -D RUBY_EXPORT -D _XOPEN_SOURCE -D _DARWIN_C_SOURCE -D _DARWIN_UNLIMITED_SELECT -D _REENTRANT -I /opt/local/include -I /opt/local/include -I . -I .ext/include/x86_64-darwin14 -I ./include -I . -I/opt/local/include -Os -fdebug-compilation-dir /opt/local/var/macports/build/_opt_mports_dports_lang_ruby23/ruby23/work/ruby-2.3.0 -ferror-limit 19 -fmessage-length 0 -fvisibility hidden -fwrapv -stack-protector 1 -mstackrealign -fblocks -fobjc-runtime=macosx-10.10.0 -fencode-extended-block-signature -fmax-type-align=16 -fno-common -fdiagnostics-show-option -vectorize-loops -vectorize-slp -o regexec.o -x c regexec.c
   1.	<eof> parser at end of file
   2.	Per-module optimization passes
   3.	Running pass 'CallGraph Pass Manager' on module 'regexec.c'.
   4.	Running pass 'Loop Pass Manager' on function '@match_at'
   5.	Running pass 'Loop Invariant Code Motion' on basic block '%4037'
   clang: error: unable to execute command: Segmentation fault: 11
   clang: error: clang frontend command failed due to signal (use -v to see invocation)
   Apple LLVM version 6.1.0 (clang-602.0.53) (based on LLVM 3.6.0svn)
   Target: x86_64-apple-darwin14.5.0
   Thread model: posix
   clang: note: diagnostic msg: PLEASE submit a bug report to http://developer.apple.com/bugreporter/ and include the crash backtrace, preprocessed source, and associated run script.
   clang: note: diagnostic msg:

regexec.cのコンパイルでエラーでて失敗してる。あんま見たことないケースだなあ。手元の環境で確認したときは問題なかったし、rubyciもgreenだし。

あれこれ試して、以下の場合で再現することがわかった。

  • Xcode 6.4 (自分は7.0で確認してた)
  • コンパイラの最適化フラグが "-Os" (MacPortsのデフォルト値。前は-O3だったような…?)

のときがアウトっぽい。なんかすごくコンパイラのバグっぽいのですけど。。。

とりあえずPortfileで上記に該当する場合は、最適化フラグを-Osから-O3に書き換えすることで対応することにした。

MacPortsのport:ruby22, ruby21, ruby20を更新

あたらしいバージョンがリリースされていたので対応しました。今回のバージョンはセキュリティ修正(CVE-2015-7551: Fiddle と DL における tainted 文字列使用時の脆弱性について)が含まれています。

以下自分ようのメモ。

2.2 Leopard以前にlibunwind.hがない?

2.2.3が古いOS X(10.5 Leopard以前)でコンパイルエラーがあるらしくパッチが当てられていたけれど、2.2.4ではupstreamで対応された(Bug #11591)ので削除。

2.2, 2.1 仮想マシン上のtest/ruby/test_io.rbでTypeError

先週ようやく10.11に上げたので、10.10の検証(インストールの確認とtest-all流す)はVMWare上の仮想マシンでやった。

2.2.4と2.1のtest-allでTestIOでエラーが4件出る。

     1) Error:
   TestIO#test_close_on_exec:
   TypeError: no implicit conversion from nil to integer

rubyciではOS X上の2.2/2.1はtest-allのエラーはないので、環境かMacPortsのビルドの問題かなあと考えて調べてみる。

まずはport使わずにふつーにconfigure && make。再現することを確認。2.2.3ではテストをパスすることも確認。

2.2.3と2.2.4でconfig.hを比較、上述のlibunwind.hの件だけで関係なさそう。

   --- .ext/include/x86_64-darwin14/ruby/config.h	2015-12-20 01:34:06.000000000 +0900
   +++ ../ruby-2.2.4/.ext/include/x86_64-darwin14/ruby/config.h	2015-12-20 01:07:06.000000000 +0900
   @@ -332,6 +332,7 @@
    #define SET_THREAD_NAME(name) pthread_setname_np(name)
    #define DEFINE_MCONTEXT_PTR(mc, uc) mcontext_t mc = (uc)->uc_mcontext
    #define HAVE_EXECINFO_H 1
   +#define HAVE_LIBUNWIND_H 1
    #define HAVE_BACKTRACE 1
    #define BROKEN_BACKTRACE 1
    #define DLEXT_MAXLEN 7

次にテストスクリプトを比較、RLIMITまわりの設定が追加されてるね。

   --- ../ruby-2.2.3/test/ruby/test_io.rb      2015-08-14 00:53:27.000000000 +0900
   +++ test/ruby/test_io.rb    2015-12-20 10:51:14.000000000 +0900
   @@ -1065,7 +1065,9 @@
      def ruby(*args)
        args = ['-e', '$>.write($<.read)'] if args.empty?
        ruby = EnvUtil.rubybin
   -    f = IO.popen([ruby] + args, 'r+')
   +    opts = {}
   +    opts[:rlimit_nproc] = 1024 if defined?(Process::RLIMIT_NPROC)
   +    f = IO.popen([ruby] + args, 'r+', opts)
        pid = f.pid
        yield(f)
      ensure
   @@ -1120,6 +1122,10 @@

      def test_dup_many
        ruby('-e', <<-'End') {|f|
   +      if defined?(Process::RLIMIT_NOFILE)
   +        lim = Process.getrlimit(Process::RLIMIT_NOFILE)[0]
   +        Process.setrlimit(Process::RLIMIT_NOFILE, [lim, 1024].min)
   +      end
          ok = 0
          a = []
          begin

試してみた感じだと

   opts[:rlimit_nproc] = 1024 if defined?(Process::RLIMIT_NPROC)

ここの"1024"が環境の最大値を超えてるとだめっぽい。

   # OS X 10.11.2 実機
   % ruby2.2 -e 'p Process.getrlimit(Process::RLIMIT_NPROC)'
   [709, 1064]
   # OS X 10.10.5 VMWare fusion
   % ruby2.2 -e 'p Process.getrlimit(Process::RLIMIT_NPROC)'
   [266, 532]

仮想のほうで1024を532にするとテストをパスするようになる。533だとエラーになる。

ruby本体の問題ではなさそうなのでとりあえず無視することに。ちゃんと調べたらbugs.r-l.oにレポートだそう。

(2015-12-22 追記)Bug #11852として報告して、送ったパッチもマージされた。ありがとうございます!

2.0.0 rubygems/test_gem_remote_fetcher.rbで"dh key too small"

例によってopensslのバージョンが上がったことで要求されるサイズが増えたみたい。

test_gem_remote_fetcher.rbの`OpenSSL::PKey::DH.new(128)`を256にしてみたけど、test-allで流すとエラーのぜんぶは解消できなくってめんどうになってあきらめた。

RubyCocoa 今日のコミット 2015-09-26

以前に見つかっていたバグがよくわからないけど直った。ひさしぶりにぜんぶテストが通るようになったぜ。。

  • NSArrayController#arragendObjects#eachするとruby-2.xだけNSGenericExceptionが起きるのを修正(beca6d4)

rubycocoa issue#2に書いたとおりなんだけど、

   require 'osx/cocoa'
   ctl = OSX::NSArrayController.alloc.init
   content = [1,2,3,4,5].map {|i| i.to_s}
   ctl.addObjects(content)

   p ctl.arrangedObjects
   # expects ["1", "2", "3", "4", "5"]
   # but ruby-2.0 fail with the following error:
   # /Library/Frameworks/RubyCocoa.framework/Resources/ruby/osx/objc/oc_wrapper.r
   # b:55:in `ocm_send': NSGenericException - *** Collection <_NSControllerArrayP
   # roxy: 0x7f8e04a66b00> was mutated while being enumerated. (OSX::OCException)

と、ruby-2.x上でObjective-C側の例外が起きてしまう。もちろんふつうのNSArrayは問題ない。

NSArray#eachは

   iter = self.objectEnumerator
   while obj = iter.nextObject
     yield obj
   end

といった感じで実装されていてenumeraterまわしてるだけなので、コレクションのオブジェクトを変更をしているとは思えない。そもそもruby-1.8のときは問題ないしねえ。。

あれこれ調べてたんだけどけっきょく原因はわからなくて、「動かないよりはいいか」とコレクションかイテレータのクラスかなんかで判定してダメそうな場合は

   self.count do |i|
     yield self[i - 1]
   end

みたいにぐるぐるすればよいかと思って試していたら、iter.classNameを呼び出すと元々の実装でも動くようになったことに気付いた。なんだかわからんけど、Objective-CのメソッドをnextObejctの前に呼び出してやればよさそうなことがわかったのでとりあえずそれで対応した。

RubyCocoa 今日のコミット 2015-09-19

10.11GM上で10.10と同じようにテスト通る(既知の1F)まで持ってきた。

作業はビルドまわりとテストスクリプト側の修正でコアの方は手をいれてない。

  • 10.11ではRubyCocoa.frameworkは@excecutable_pathなしをデフォルトに。 (ceb7962)
  • 上述のDYLD_環境変数の件に対応。(c0bd8ae, 095de97)
  • CoreFoundationのretain countのテストが通らないのを修正。てか元のテストの意図がびみょーだったので消した。(56647a4)
  • KeyValueObvervingのテストで観測対象の値を初期化するように。一部のケースでKVO発火時に現在の値を取得するようになったぽい。実際には@value1だけ初期値が必要なんだけどそれも気持ち悪いのでひととおり入れてくようにした。(20133e6)

最初の@excecutable_pathは、OS添付のrubyコマンドから使うときに問題起きないようにするために変更した。

`ruby`から実行するとき、require 'osx/cocoa'でrubycocoa.bundleが読み込まれ、そこからリンクしているRubyCocoa.frameworkがロードされるという順になるのだけれど、このときバイナリに記録されているパスが@excecutable_pathを含んでいると"unsafe use of @executable_path"とのメッセージが表示されロードが中止されてしまう。ということで、/Library/Frameworksがデフォルトになるようにした。

その影響で、今後リリースするだろうパッケージからインストールしたRubyCocoa.frameworkをアプリケーションに同梱するときは単に.frameworkを.appにほうりこむだけではダメで、install_name_toolでライブラリのパスを変更する必要がある。standaloneify.rbを使っている場合はスクリプト側で対処する予定なので、従来どおり使えば問題ない。

現在の構成もわかりづらいしこういう問題もあるので、将来的には

  • rubyコマンドから使う場合はrubycocoa.bundleだけをロードする。
  • .appからはRubyCocoa.frameworkをロードする。

というように住み分けするようにしたほうがよいと考えている。前者だけgemで配布できるようになるし。

OS Xの"DYLD_*"環境変数

先日のCocoa勉強会で話してきたので整理しとく。

まとめ

dyldと"DYLD_"ではじまる環境変数

dyldはOS X/iOSのダイナミックリンカで、ライブラリやフレームワークをロードするプログラムです。man DYLD(1)にあるように、"DYLD_"ではじまる環境変数で動作を変更することができます。UnixLinuxLDでいうところのLD_LIBRARY_PATHやLD_PRELOADの類ですね。

すっごく雑に分けると、これらの環境変数

  • ライブラリの探索方法を指示
    • DYLD_*_PATH: 探す場所を指示する。
    • DYLD_IMAGE_SUFFIX: デバッグ用などに用意したライブラリのsuffixを指示する。
  • 実行中の情報を表示
    • DYLD_PRINT_*: ロードしたものや実行環境の情報などを標準エラー出力に表示する。
  • その他
    • DYLD_INSERT_LIBRARIES: LD_PRELOAD
    • DYLD_FORCE_FLAT_NAMESPACE, DYLD_BIND_AT_LAUNCH: リンカの解決方法を指定する。

という感じになります。

RubyCocoaの開発ではテストスクリプトの実行時に

  • DYLD_FRAMEWORK_PATH: 開発中のRubyCocoa.frameworkの場所を指示する。
  • DYLD_PRINT_LIBRARIES_POST_LAUNCH: テストスクリプトが正しく開発中のRubyCocoa.frameworkやrubycocoa.bundleをロードしたかを確認する。

の2つを使っています。プログラム的には次のようなrubyスクリプトです。

   ENV['DYLD_FRAMEWORK_PATH'] = path_to_devel_framework
   ENV['DYLD_PRINT_LIBRARIES_POST_LAUNCH'] = '1'
   libs = Open3.popen3("ruby -rosx/cocoa -e ''") do |stdin, stdout, stderr|
     # read stderr
   end
   # test loaded rubycocoa framework/bundle paths from stderr

DYLD_PRINT_LIBRARIESとDYLD_PRINT_LIBRARIES_POST_LAUNCHのちがいは、プロセスの起動時にロードしたものを表示するか否かです。

   % DYLD_PRINT_LIBRARIES=YES ruby2.2 -e 'require "zlib"'
   dyld: loaded: /opt/local/bin/ruby2.2
   dyld: loaded: /System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation
   dyld: loaded: /opt/local/lib/libruby.2.2.0.dylib
   dyld: loaded: /usr/lib/libSystem.B.dylib
     :
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/encdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/trans/transdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/thread.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/date_core.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/zlib.bundle
   dyld: loaded: /opt/local/lib/libz.1.dylib
   % DYLD_PRINT_LIBRARIES_POST_LAUNCH=YES ruby2.2 -e 'require "zlib"'
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/encdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/enc/trans/transdb.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/thread.bundle
   dyld: loaded: /opt/local/lib/ruby2.2/2.2.0/x86_64-darwin14/zlib.bundle
   dyld: loaded: /opt/local/lib/libz.1.dylib
   %

OS X 10.11での制限

そろそろ10.11がリリースされそう(当時beta 8)なのでRubyCocoaの検証をしていたところ、上記のテストスクリプトフレームワークのパスの検証が通らないことに気付きました。どうもDYLD_FRAMEWORK_PATH環境変数が機能していないようです。

試してみると、

   % uname -r
   15.0.0 # 10.11 GM
   % ruby -e "system({'DYLD_AAA' => 'AAA', 'AAA_DYLD_BBB' => 'BBB'}, \
                 '/usr/bin/printenv | grep DYLD')"
   AAA_DYLD_BBB=BBB
   %

と、"DYLD_"ではじまる環境変数が子プロセスに反映されないようになっているようです。betaの無印のときはこんなことなかったと思うんだけどなあ。もちろんOS X 10.10では"DYLD_AAA"も出力されます。

仕方ないので、次のような一時ファイルのシェルスクリプトを生成して実行することを考えています。

   # temp shell script for testing with DYLD_ environments
   DYLD_FRAMEWORK_PATH=path ${RUBY} -rosx/cocoa -e ''

さすがにこれで解消するのですが、なんかすごく不毛な気持ちになります。もうちょっとスマートに書けるような…
よい方法があれば教えてください!だいぶ切実です。

(2015-09-19追記: ついったで @n0kada さんに`env`コマンドでいけることを教えてもらいました。ありがとうございます!)

また、WWDC 2015のセッション706:"Security and Your Apps"(ASCIIwwdc)で説明されているように、保護される領域(/Systemや/usr/binなど)にあるバイナリではDYLD_*環境変数は問答無用で無視されるようになっています。

例を示すと、

   % uname -r
   15.0.0 # 10.11 GM
   % cat dyld_env.rb
   # print DYLD_* environments
   ENV.each_pair do |k, v|
       puts "#{k}=#{v}" if k =~ /DYLD/
   end
   % DYLD_AAA=AAA AAA_DYLD_BBB=BBB /usr/bin/ruby dyld_env.rb
   AAA_DYLD_BBB=BBB
   % DYLD_AAA=AAA AAA_DYLD_BBB=BBB /opt/local/bin/ruby2.2 dyld_env.rb
   dyld: warning, unknown environment variable: DYLD_AAA
   DYLD_AAA=AAA
   AAA_DYLD_BBB=BBB
   %

のように、システム付属のrubyにはDYLD_ではじまる環境変数が渡されないようになっています。

感想

セキュリティの観点としてはいままでがざるざるだったこともあり、制限は仕方ないし正しい方向だとは思います。だけど、プログラム書く人間としてはめんどくさいなあという気持ちも隠せません。

人によってはぜんぜん影響ないかもしれませんが、思わぬワナにはまるかもなのでご注意を。

MacPortsのport:ruby22, ruby21, ruby20を更新

あたらしいバージョンがリリースされていたので対応しました。

RubyGemsのCVE-2015-3900のセキュリティ修正が含まれてたりします。詳しくは公式のリリースアナウンスをみてください。

また、以前に書いたように、port:ruby22にjemallocバリアントを追加しました。

rubyforgeがもうない件の対応

今はなきrubyforgeの記載がPortfileに大量に残ってるのマズくね?的な話が来てた。

知ってたけどさ!ファイルはmacports.org側で再配布のコピー持ってるしいいじゃん……

とりあえず自分が担当の分だけやってみて、わりといけそうな感触があったので他のも対処。たぶん全部で160 portsくらい直した。

だいたい作業はこんな感じ。

  • 新しいウェブサイトや配布サイトを探して変更
    • rubygems.orgにあるものはそこのhomepageを見て。homepageがなければrubygems.org上のページに。
    • githubから.tar.gz取得するものは、PortGroup githubを使う。便利!!
  • gemでなく、現在の配布サイトが見つからないものは依存してる他のportがないか確認して削除。
    • gemがあるものは依存portもまとめてgem化できれば、rubygems.orgをホームページに変更して存続。
  • インストールできるけれど実行できない、コア機能のテストが通らないものもあっても仕方ないので削除。

ぜんぜん現サイトがわからなくて検索した結果、同名のライブラリあって作者変わってる場合、forkなのか引き継ぎしたのか同名の別ライブラリなのかとかの判定がしんどかった。。

RubyCocoa 今日のコミット 2015-06-12

新しいOS Xのbetaが出たので検証中、っていうかSEGVしてテスト走らないじゃん。。

だいたいこういうレベルでおかしいときは、コンパイル時に警告出てたりするものなので確認。

   In file included from /Users/kimuraw/proj/rubycocoa/framework/src/objc/OverrideMixin.m:13:
   In file included from /Users/kimuraw/proj/rubycocoa/framework/src/objc/OverrideMixin.h:10:
   In file included from /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/objc/objc-class.h:1:
   /Applications/Xcode-beta.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/usr/include/objc/runtime.h:56:11: note: 'isa' has been explicitly marked deprecated here
       Class isa  OBJC_ISA_AVAILABILITY;
             ^
   /Users/kimuraw/proj/rubycocoa/framework/src/objc/OverrideMixin.m:492:22: warning: 'isa' is deprecated [-Wdeprecated-declarations]
     class_addMethod(c->isa, @selector(alloc), (IMP)imp_c_alloc, "@4@4:8");
                        ^

たぶんこれだね。クラッシュレポートもinit_ovmix() -> class_addMethod()のあたりで落ちてるし。クラスのメタクラスを取るのにisa直接見てるけど、だいぶ前にdeprecateされた方法。

objcでメタクラスを取る方法は

  • objc_getMetaClass()にクラス名を渡す。
  • object_getClass()にクラス(Class)を渡す。(ふつうのオブジェクトを渡すとそのクラスが得られる)

のどちらかで、今回はClassはもう用意してあるので後者のほうがよいね。ということで直した。

まあまだOS X 10.11上でぜんぶのテストは通らないんだけどね!

あとテスト流すと次のようなメッセージがでるので対処しといた。outletへのsetterがないから繋げないぜ、とのこと。

   2015-06-12 01:51:36.174 ruby2.2[1285:2455542] Failed to connect (tableView2) outlet from (TableViewNibOwner) to (NSScrollView): missing setter or instance variable

単に警告の出てたoutletに対してib_outletで宣言するようにした。

  • テスト時の"Failed to connect..."が出ないように。(c8fa6b2

RubyCocoa 今日のコミット 2015-05-31

半年ぶりくらいですね。。

rbenvの件で思い出したんだけど、RubyCocoaruby.dylibを要求するのってなんでなのでしょうね。昔は.aでもよかったし、動作させる上では技術的にも問題ないはずなのでライセンス的な話かなあ。。

RubyCocoaプロジェクトをgithubに移行

しました。

インストーラは最新の1.2.0 (2014年7月)だけgithubにも置きました。それ以前のはSourceForgeからゲットしてください。

import.github.com使って、そのまま持ってきたので昔の開発用のタグも混ざって"102 releases"とかワケわかんないことになってますね…

ウェブサイトはAPIリファレンスのついでにyardで作ってるんですが、.nojekyllファイルを置かないとIndexナビゲーションが機能しない(_index.html is not working for gh-pages · Issue #111 · lsegal/yard)とかにハマったりしました。

活発とは言えませんが、メンテナンスは続けるつもりなのでよろしくお願いします。