LoginSignup
23
22

More than 5 years have passed since last update.

runtime.Caller(1)をなめて扱ったら危険かもしれない

Posted at

議題

Golangでよくやる GetCurrentFile() とか GetCurrentDir() みたいなのはもしかしたら、ちょっと使い方間違えるとヤバイかもしれないというお話です。

よくやるやつ... つまりこう!!

import (
  "runtime"
  "path"
)

// __FILE__
func GetCurrentFile() string {
  _, filename, _, _ := runtime.Caller(1)
  return filename
}

// __DIR__
func GetCurrentDir() string {
  _, filename, _, _ := runtime.Caller(1)
  return path.Dir(filename)
}

runtime.Caller(1)っていうのが問題で、挙動を見る限り、これは現在位置と言うよりビルドしたソースの位置らしいのです。なので、ビルドしてビルドしたディレクトリ構成の外で使うと挙動がおかしくなります。

__DIR__的な考え

ディレクトリ パス 結果
元のディレクトリ /Volumes/go/foo/bar/ /Volumes/go/foo/bar/
お引越し先 /Home/fazbizz/ /Home/fazbizz/

GetCurrentDir()

ディレクトリ パス 結果
元のディレクトリ /Volumes/go/foo/bar/ /Volumes/go/foo/bar/
お引越し先 /Home/fazbizz/ /Volumes/go/foo/bar/

調査

とりあえず、日本語訳のドキュメントを読んでみる。
http://golang.jp/pkg/runtime

Callerは、呼び出したゴルーチンスタック上で実行している関数のファイルと行番号情報をレポートします。引数skipは、引き上げるスタックフレーム数であり、これに0を指定したときはCallerを呼び出したスタックレームになります。戻り値は、プログラムカウンタ、ファイル名、ファイル内で呼び出した箇所の行番号をレポートします。情報を返せなかったときは論理値okにfalseが返ります。

Caller(1)の場合についてのことが書かれてない。

原文も呼んでみる
http://golang.org/pkg/runtime/#Caller

Caller reports file and line number information about function invocations on the calling goroutine's stack. The argument skip is the number of stack frames to ascend, with 0 identifying the caller of Caller. (For historical reasons the meaning of skip differs between Caller and Callers.) The return values report the program counter, file name, and line number within the file of the corresponding call. The boolean ok is false if it was not possible to recover the information.

う〜ん、おんなじか(´~`;)

海外の記事
http://blog.el-chavez.me/2013/05/17/golang-current-file-path/

Next you can use the Caller method and capture the filename. We need to give this function a 1 to tell it to skip up a caller.

訳す

Callerメソッドを使ってファイル名をキャプチャできる。このファンクションに1を渡すとcallerをスキップできる

振り返る

これに0を指定したときはCallerを呼び出したスタックレームになります。

もしかして0指定してないと実際に呼び出したところの情報は取れないのか!!
∑(゚Д゚)ガーン

(実際にやってみたは後でやってみる)

代替策を考える

考えたくないので、stackoverflowとかから引っ張る
http://stackoverflow.com/questions/18537257/golang-how-to-get-the-directory-of-the-currently-running-file

import (
    "fmt"
    "log"
    "os"
    "path/filepath"
)

func main() {
    dir, err := filepath.Abs(filepath.Dir(os.Args[0]))
    if err != nil {
            log.Fatal(err)
    }
    fmt.Println(dir)
}
import (
    "github.com/kardianos/osext"
    "fmt"
    "log"
)

func main() {
    folderPath, err := osext.ExecutableFolder()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(folderPath)
}
import (
    "fmt"
    "os"
)

func main() {
    pwd, err := os.Getwd()
    if err != nil {
        fmt.Println(err)
        os.Exit(1)
    }
    fmt.Println(pwd)
}

追記

Googleグループで似た話してました。
https://groups.google.com/forum/#!topic/golang-nuts/XiKfqDO3rKQ

23
22
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
22