Slide 1

Slide 1 text

© 2025 ANDPAD All Rights Reserved. about #74462 go/token#FileSet invalid array length -delta * delta (constant -256 of type int64) tomtwinkle techlead@andpad

Slide 2

Slide 2 text

© 2025 ANDPAD All Rights Reserved. ANDPADとは 社内 現場 営業 / 監督 / 設計 事務 / 管理職 職人 / 業者 メーカー / 流通 現場の効率化から経営改善まで一元管理できる クラウド型建設プロジェクト管理サービス 案件管理 資料 工程表 写真 報告 チャット 黒板 図面 受発注 ● ● ● 
 


Slide 3

Slide 3 text

© 2025 ANDPAD All Rights Reserved. Go を間接的に使っているプロダクト ANDPAD の Go のプロダクト Go がメインのプロダクト The Go gopher was designed by Renée French. 施工管理 図面 引合粗利管理 検査 黒板 受発注 ボード 資料承認 おうちノート … 歩掛管理 請求管理

Slide 4

Slide 4 text

© 2025 ANDPAD All Rights Reserved. Speaker tomtwinkle tomoki.harada(tsuchiya) 山登り界隈 techlead@andpad

Slide 5

Slide 5 text

Copyright © 2023 ANDPAD Inc. go/token

Slide 6

Slide 6 text

© 2025 ANDPAD All Rights Reserved. Goのソースコードを静的解析する際に、コードを構成する最小単位の要素 (トークン)を定義し、管理する役割を担います。 細かい話は時間足りないのでしません。 プログラミング言語Go完全入門 by tenntenn https://tenn.in/analysis Goで作る静的解析ツール開発入門 by H.Saki https://zenn.dev/hsaki/books/golang-static-analysis 辺りを見ると分かりやすいと思います。 go/token

Slide 7

Slide 7 text

Copyright © 2023 ANDPAD Inc. invalid array length -delta * delta (constant -256 of type int64)

Slide 8

Slide 8 text

Copyright © 2023 ANDPAD Inc. Go 1.25 Release Note https://go.dev/doc/go1.25#gotokenpkggotoken

Slide 9

Slide 9 text

© 2025 ANDPAD All Rights Reserved. https://go.dev/doc/go1.25#gotokenpkggotoken What changed go/token#FileSet in Go 1.25? 新しいFileSet.AddExistingFilesメソッドにより、既存のファイルをFileSetに追加したり、任 意のファイル群からFileSetを構築したりできるようになり、長寿命アプリケーションにおける 単一のグローバルFileSetに関連する問題を軽減します。

Slide 10

Slide 10 text

Copyright © 2023 ANDPAD Inc. つまり・・・ どういうことだってばよ

Slide 11

Slide 11 text

Copyright © 2023 ANDPAD Inc. 第一章 Go1.25以前の go/token#FileSet の issueを振り返る

Slide 12

Slide 12 text

Copyright © 2023 ANDPAD Inc. #53200 http://go.dev/issue/53200

Slide 13

Slide 13 text

© 2025 ANDPAD All Rights Reserved. http://go.dev/issue/53200 #53200

Slide 14

Slide 14 text

© 2025 ANDPAD All Rights Reserved. http://go.dev/issue/53200 #53200 [意訳] Goのコード解析で使われる token.FileSetは、一度追加したファイル情報を解放し ない設計になっています。 そのためgoplsのような常駐ツールでは、ファイルを解析する度にメモリ使用量が 無限に増え続け、メモリリークを引き起こします。 token.FileSetは事実上ユビキタスな単一のグローバル変数となっています。 これ自体はGo1.20で入った

Slide 15

Slide 15 text

Copyright © 2023 ANDPAD Inc. ユビキタスな単一のグローバル変数 a ubiquitous global variable

Slide 16

Slide 16 text

© 2025 ANDPAD All Rights Reserved. Go 1.25以前の go/token#FileSet の構造 type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } func (s *FileSet) AddFile(filename string, base, size int) *File { /* - - 以下抜粋 - - */ if base < s.base { panic(fmt.Sprintf("invalid base %d (should be >= %d)", …) } // base >= s.base && size >= 0 base += size + 1 // +1 because EOF also has a position // add the file to the file set s.base = base } 管理下にあるすべてのファイルに対し て、連続的で重複のない「アドレス空間」 を形成するというモデルを採用していま す。 FileSetに新たに追加されるファイルは、 そのbaseオフセットが、直前に追加され たファイルの範囲(base + size)よりも大 きい値でなければなりません。 ファイルは常にアドレス空間の末尾に追 加される、つまり追記専用の操作しか許 されません。 https://cs.opensource.google/go/go/+/refs/tags/go1.24.7:src/go/token/position.go;l=426

Slide 17

Slide 17 text

© 2025 ANDPAD All Rights Reserved. Go 1.25以前の go/token#FileSet の構造 type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } func (s *FileSet) AddFile(filename string, base, size int) *File { /* - - 以下抜粋 - - */ if base < s.base { panic(fmt.Sprintf("invalid base %d (should be >= %d)", …) } // base >= s.base && size >= 0 base += size + 1 // +1 because EOF also has a position // add the file to the file set s.base = base } 管理下にあるすべてのファイルに対し て、連続的で重複のない「アドレス空間」 を形成するというモデルを採用していま す。 FileSetに新たに追加されるファイルは、 そのbaseオフセットが、直前に追加され たファイルの範囲(base + size)よりも大 きい値でなければなりません。 ファイルは常にアドレス空間の末尾に追 加される、つまり追記専用の操作しか許 されません。 https://cs.opensource.google/go/go/+/refs/tags/go1.24.7:src/go/token/position.go;l=426 FileSet file_a.go (size=100) base=102 base+100+1 file_b.go (size=200) FileSet file_a.go (size=100) file_b.go (size=200) base=303 base+200+1 FileSet base=1 file_a.go (size=100)

Slide 18

Slide 18 text

© 2025 ANDPAD All Rights Reserved. Go 1.25以前の go/token#FileSet の構造 type File struct { base int // Pos value range for this file is [base...base+size] } func searchFiles(a []*File, x int) int { i, found := slices.BinarySearchFunc(a, x, func(a *File, x int) int { return cmp.Compare(a.base, x) }) if !found { i-- } return i } func (s *FileSet) file(p Pos) *File { /* - - 抜粋 - - */ if i := searchFiles(s.files, int(p)); i >= 0 { f := s.files[i] } } token.Posは、FileSet内で管理されるす べてのファイルを通じて一意な値を持つ ように設計されています。 例えば、token.Posからどの*Fileに属す るかを特定するFile(s Pos)は二分探索 で検索するためファイル数が nの場合、 探索の計算量は O(log n)となり、非常に 効率的。 FileSetにファイルを追加する際は単純 な末尾追記なので 計算量は O(1) で爆 速。 https://cs.opensource.google/go/go/+/refs/tags/ go1.24.7:src/go/token/position.go;l=539

Slide 19

Slide 19 text

© 2025 ANDPAD All Rights Reserved. Go 1.25以前の go/token#FileSet の課題 func main() { // 1. pkgA のコードを解析 fsetA := token.NewFileSet() filePathA := "pkgA/a.go" astA, _ := parser.ParseFile(fsetA, filePathA, readFile(filePathA), …) // 2. pkgB のコードが変更され、再解析 fsetB := token.NewFileSet() filePathB := "pkgB/b.go" astB, _ := parser.ParseFile(fsetB, filePathB, readFile(filePathB), …) // 3. 結果のマージを試みる //fsetA をマスターとし、astBのfsetBをそこに追加したい } コード生成ツールなど動的にソースコー ドが変更されるツールで変更差分だけ 解析してFileSetをマージしたいパターン pkgAを解析後にpkgBのコードが変更さ れたので再解析する例

Slide 20

Slide 20 text

© 2025 ANDPAD All Rights Reserved. Go 1.25以前の go/token#FileSet の課題 func main() { // 1. pkgA のコードを解析 fsetA := token.NewFileSet() filePathA := "pkgA/a.go" astA, _ := parser.ParseFile(fsetA, filePathA, readFile(filePathA), …) // 2. pkgB のコードが変更され、再解析 fsetB := token.NewFileSet() filePathB := "pkgB/b.go" astB, _ := parser.ParseFile(fsetB, filePathB, readFile(filePathB), …) // 3. 結果のマージを試みる //fsetA をマスターとし、astBのfsetBをそこに追加したい // しかし、直接マージするmethodはない // fsetA.Merge(fsetB) // <--- こういうのが欲しい } しかし、(簡単に)マージする事はできな い。 必然的に複数のFileSetを同時に扱うか 毎回新規にFileSetを構築する必要があ り多数ファイルを扱う場合はメモリ消費 量が尋常ではなくなる。 独立して解析されたソースコードの情報 を後から結合し、単一のコンテキストで 扱いたい場合にはFileSet同士のマージ が要件的に必須。

Slide 21

Slide 21 text

© 2025 ANDPAD All Rights Reserved. Go 1.25以前の go/token#FileSet の課題 ● Go Language Server(gopls) ○ エディターの裏で長期間起動し、ソースコード全体を監視 ○ コード補完、コードジャンプ、エラー検出など ● 大規模コードインクリメンタル(増分)解析を行う静的解析ツール ● コード生成ツール

Slide 22

Slide 22 text

© 2025 ANDPAD All Rights Reserved. http://go.dev/issue/53200 #53200

Slide 23

Slide 23 text

Copyright © 2023 ANDPAD Inc. goplsのための Go 1.25以前の go/token#FileSet の Hack

Slide 24

Slide 24 text

© 2025 ANDPAD All Rights Reserved. cl469858 gopls v0.7.0 (add an LRU parse cache) gopls v0.7.0 (2023-03-04 07:59) parse済みファイルと変更のあったファイルの差分解析のためのLRU cacheが追加 https://go.dev/cl/469858

Slide 25

Slide 25 text

© 2025 ANDPAD All Rights Reserved. cl476437 x/tools v0.8.0 (add AddExistingFiles helper) x/tools v0.8.0 (2023-03-16 00:30) internal/tokeninternal にFileSetのマージのための AddExistingFiles helperが追加 https://go.dev/cl/476437

Slide 26

Slide 26 text

© 2025 ANDPAD All Rights Reserved. x/tools/internal/tokeninternal#AddExistingFiles!? https://cs.opensource.google/go/x/tools/+/refs/tags/v0.8.0:internal/tokeninternal/tokeninternal.go;l=66;bpv=1;bpt=0 func AddExistingFiles(fset *token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int type uP = unsafe.Pointer var ptr *tokenFileSet *(*uP)(uP(&ptr)) = uP(fset) ptr.mutex.Lock() defer ptr.mutex.Unlock() } FileSetハック界(?)に流星の如く現 れたAddExistingFiles これは先ほどやりたかった FileSet のマージの為のhelperです。

Slide 27

Slide 27 text

© 2025 ANDPAD All Rights Reserved. x/tools/internal/tokeninternal#AddExistingFiles!? https://cs.opensource.google/go/x/tools/+/refs/tags/v0.8.0:internal/tokeninternal/tokeninternal.go;l=66;bpv=1;bpt=0 func AddExistingFiles(fset *token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int type uP = unsafe.Pointer var ptr *tokenFileSet *(*uP)(uP(&ptr)) = uP(fset) ptr.mutex.Lock() defer ptr.mutex.Unlock() … go.token#FileSetのクローンのオ レオレFileSet structを用意 unsafe.Pointer を利用して引数 のtoken.FileSetからオレオレ FileSetに強制的に型変換 これでカプセル化されていた FileSetの非公開フィールドに直接 アクセス可能にし、base, filesを 弄ってFileSetのマージを可能にし た黒魔術

Slide 28

Slide 28 text

© 2025 ANDPAD All Rights Reserved. x/tools/internal/tokeninternal#AddExistingFiles!? https://cs.opensource.google/go/x/tools/+/refs/tags/v0.8.0:internal/tokeninternal/tokeninternal.go;l=66;bpv=1;bpt=0 type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } func AddExistingFiles(fset *token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } type uP = unsafe.Pointer var ptr *tokenFileSet *(*uP)(uP(&ptr)) = uP(fset) } unsafe.Pointerを使った型変換 は、メモリ上のデータ配置(レイア ウト)が同一であるという強い仮定 に基づいている。 つまり、元のFileSetの定義が変 更されれば壊れる。 やってんな!

Slide 29

Slide 29 text

Copyright © 2023 ANDPAD Inc. 黒魔術に対する防衛術

Slide 30

Slide 30 text

© 2025 ANDPAD All Rights Reserved. 黒魔術に対する防衛術 https://cs.opensource.google/go/x/tools/+/refs/tags/v0.8.0:internal/tokeninternal/tokeninternal.go;l=66;bpv=1;bpt=0 func AddExistingFiles(fset *token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } // If the size of token.FileSet changes, this will fail to compile. const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int }

Slide 31

Slide 31 text

© 2025 ANDPAD All Rights Reserved. 黒魔術に対する防衛術 https://pkg.go.dev/unsafe#Sizeof // このコード、何をしているかわかりますか? const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int

Slide 32

Slide 32 text

© 2025 ANDPAD All Rights Reserved. 黒魔術に対する防衛術 https://go.dev/play/p/z61PLs9JXmO func main() { type Foo struct { F1 int32 } type Bar struct { F1 int64 } fmt.Printf("Hoge = %d\n", int64(unsafe.Sizeof(Foo{}))) // Foo = 4 fmt.Printf("Fuga = %d\n", int64(unsafe.Sizeof(Bar{}))) // Bar = 8 delta := int64(unsafe.Sizeof(Foo{})) - int64(unsafe.Sizeof(Bar{})) fmt.Printf("Foo-Bar = %d\n", delta) // Foo-Bar = -4 delta = int64(unsafe.Sizeof(Bar{})) - int64(unsafe.Sizeof(Foo{})) fmt.Printf("Bar-Foo = %d\n", delta) // Bar-Foo = 4 } 定義が異なる2つのstructを unsafe.SizeOfで比較した例 定義が異なる場合、パディン グで調整しない限りdeltaは0 以外の数字になる

Slide 33

Slide 33 text

© 2025 ANDPAD All Rights Reserved. 黒魔術に対する防衛術 https://go.dev/play/p/pqVUNIv2jiE func main() { type Foo struct { F1 int32 } type Bar struct { F1 int64 } delta := int64(unsafe.Sizeof(Foo{})) - int64(unsafe.Sizeof(Bar{})) // delta = -4 fmt.Printf("-delta * delta = %d\n", -delta*delta) // -delta * delta = -16 delta = int64(unsafe.Sizeof(Bar{})) - int64(unsafe.Sizeof(Foo{})) // delta = 4 fmt.Printf("-delta * delta = %d\n", -delta*delta) // -delta * delta = -16 } deltaが0以外の場合 -delta * deltaの計算結果は必 ず負数になる

Slide 34

Slide 34 text

© 2025 ANDPAD All Rights Reserved. 黒魔術に対する防衛術 https://go.dev/ref/spec#Array_types func main() { var _ [0]int // コンパイルが通る } func main() { var _ [-1]int // invalid array length -1 (untyped int constant) } Goの言語仕様 Array typeの要素の数は決し て負数にならない https://go.dev/ref/spec#Arr ay_types length=0 コンパイルが通る length=-1 コンパイルエラー

Slide 35

Slide 35 text

© 2025 ANDPAD All Rights Reserved. 黒魔術(略)防御術〜コンパイルアサーション〜 https://cs.opensource.google/go/x/tools/+/refs/tags/v0.8.0:internal/tokeninternal/tokeninternal.go;l=66;bpv=1;bpt=0 func AddExistingFiles(fset *token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } // If the size of token.FileSet changes, this will fail to compile. const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int } 元のFileSetが変更された場合を 考慮し、unsafe.Pointerでの型変 換の暴発を防ぐためにコンパイル アサーションの仕組みを導入。 正常: 自作tokenFileSet とtoken.FileSet のサイズが同じなら、 delta は 0 になる。[0]int という配列 の宣言は有効なので、問題なくコンパイルが通る 異常: token.FileSet の構造が変わってサイズが変化すると、 delta は 0 以外になる。 -delta * delta は負の数になる。マイナスサイズの配列は作れないため、ここでコンパイルエラーが発 生

Slide 36

Slide 36 text

Copyright © 2023 ANDPAD Inc. 黒魔術の防御も出来た これでFileSet問題解決よかったよかった

Slide 37

Slide 37 text

Copyright © 2023 ANDPAD Inc. そして時は流れ・・・

Slide 38

Slide 38 text

Copyright © 2023 ANDPAD Inc. 第二章 Go 1.25 の登場

Slide 39

Slide 39 text

© 2025 ANDPAD All Rights Reserved. https://go.dev/issue/73205 #73205

Slide 40

Slide 40 text

© 2025 ANDPAD All Rights Reserved. https://go.dev/issue/73205 #73205 [意訳] goplsでは断片的に追加される FileSetの再構築が必須だ。 今まで「backdoor (unsafe) version」のx/tools版AddExistingFiles で頑張ってきたけど流石にそろそろどうにかしようぜ。

Slide 41

Slide 41 text

© 2025 ANDPAD All Rights Reserved. https://go.dev/doc/go1.25#gotokenpkggotoken go/token#FileSet.AddExistingFiles 爆誕 新しいFileSet.AddExistingFilesメソッドにより、既存のファイルをFileSetに追加したり、任 意のファイル群からFileSetを構築したりできるようになり、長寿命アプリケーションにおける 単一のグローバルFileSetに関連する問題を軽減します。 要するに先ほどの goplsの問題

Slide 42

Slide 42 text

© 2025 ANDPAD All Rights Reserved. Go 1.25 の go/token#FileSet.AddExistingFiles実装 https://cs.opensource.google/go/go/+/refs/tags/go1.25.0:src/go/token/position.go;l=404 type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file tree tree // tree of files in ascending base order last atomic.Pointer[File] // cache of last file looked up } func (s *FileSet) AddExistingFiles(files ...*File) { s.mutex.Lock() defer s.mutex.Unlock() for _, f := range files { s.tree.add(f) s.base = max(s.base, f.Base()+f.Size()+1) } } []*token.Fileが、より効率的に データを扱えるTreeに代わり #73205 の要望にある AddExistingFilesも追加。 パフォーマンスも向上して unsafe なx/toolsを使わなくて良くなっ た! 万々歳!

Slide 43

Slide 43 text

© 2025 ANDPAD All Rights Reserved. Go 1.25 の go/token#FileSet.AddExistingFiles実装 https://cs.opensource.google/go/go/+/refs/tags/go1.24.7:src/go/token/position.go;l=426 https://cs.opensource.google/go/go/+/refs/tags/go1.25.0:src/go/token/position.go;l=404 // Go 1.24 までのFileSet type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file files []*File // list of files in the order added to the set last atomic.Pointer[File] // cache of last file looked up } // Go 1.25 のFileSet type FileSet struct { mutex sync.RWMutex // protects the file set base int // base offset for the next file tree tree // tree of files in ascending base order last atomic.Pointer[File] // cache of last file looked up } []*token.Fileが、より効率的に データを扱えるTreeに代わり #73205 の要望にある AddExistingFilesも追加。 パフォーマンスも向上して unsafe なx/toolsを使わなくて良くなっ た! 万々歳!

Slide 44

Slide 44 text

Copyright © 2023 ANDPAD Inc. お分かりいただけただろうか

Slide 45

Slide 45 text

Copyright © 2023 ANDPAD Inc. 第三章 google/wireが静止する日

Slide 46

Slide 46 text

© 2025 ANDPAD All Rights Reserved. Go 1.25.0 環境で発生する google/wire の謎のエラー 0.068 go install github.com/google/wire/cmd/wire@latest 0.208 go: downloading github.com/google/wire v0.6.0 0.645 go: downloading github.com/google/subcommands v1.2.0 0.647 go: downloading github.com/pmezard/go-difflib v1.0.0 0.653 go: downloading golang.org/x/tools v0.17.0 0.974 go: downloading golang.org/x/mod v0.14.0 13.07 # golang.org/x/tools/internal/tokeninternal 13.07 /go/pkg/mod/golang.org/x/[email protected]/internal/tokeninternal/tokeninternal.go:78:9: invalid array length -delta * delta (constant -256 of type int64) 13.67 make: *** [Makefile:73: get-google-wire] Error 1

Slide 47

Slide 47 text

© 2025 ANDPAD All Rights Reserved. Go 1.25.0 環境で発生する google/wire の謎のエラー 0.068 go install github.com/google/wire/cmd/wire@latest 0.208 go: downloading github.com/google/wire v0.6.0 0.645 go: downloading github.com/google/subcommands v1.2.0 0.647 go: downloading github.com/pmezard/go-difflib v1.0.0 0.653 go: downloading golang.org/x/tools v0.17.0 0.974 go: downloading golang.org/x/mod v0.14.0 13.07 # golang.org/x/tools/internal/tokeninternal 13.07 /go/pkg/mod/golang.org/x/[email protected]/internal/tokeninternal/tokeninternal.go:78:9: invalid array length -delta * delta (constant -256 of type int64) 13.67 make: *** [Makefile:73: get-google-wire] Error 1

Slide 48

Slide 48 text

Copyright © 2023 ANDPAD Inc. invalid array length -delta * delta (constant -256 of type int64)

Slide 49

Slide 49 text

© 2025 ANDPAD All Rights Reserved. 黒魔術(略)防御術〜コンパイルアサーション〜 https://cs.opensource.google/go/x/tools/+/refs/tags/v0.8.0:internal/tokeninternal/tokeninternal.go;l=66;bpv=1;bpt=0 func AddExistingFiles(fset *token.FileSet, files []*token.File) { type tokenFileSet struct { // This type remained essentially consistent from go1.16 to go1.21. mutex sync.RWMutex base int files []*token.File _ *token.File // changed to atomic.Pointer[token.File] in go1.19 } // If the size of token.FileSet changes, this will fail to compile. const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{})) var _ [-delta * delta]int } 元のFileSetが変更された場合を 考慮し、unsafe.Pointerでの型変 換の暴発を防ぐためにコンパイル アサーションの仕組み 正常: 自作tokenFileSet とtoken.FileSet のサイズが同じなら、 delta は 0 になる。[0]int という配列 の宣言は有効なので、問題なくコンパイルが通る 異常: token.FileSet の構造が変わってサイズが変化すると、delta は 0 以外になる。 -delta * delta は負の数になる。マイナスサイズの配列は作れないため、ここでコンパイルエラーが 発生

Slide 50

Slide 50 text

© 2025 ANDPAD All Rights Reserved. #74462 今回の主題 https://go.dev/issue/74462 issueの中身自体は x/tools AddExistingFiles のコンパイルアサーションと その対策の話

Slide 51

Slide 51 text

© 2025 ANDPAD All Rights Reserved. #74462 コンパイルアサーション暴発の原因 https://go.dev/issue/74462#issuecomment-3192864978

Slide 52

Slide 52 text

© 2025 ANDPAD All Rights Reserved. #74462 コンパイルアサーション暴発の原因 https://go.dev/issue/74462#issuecomment-3192864978 [意訳] x/toolsに、goplsパフォーマンス対策用のコード (AddExistingFiles)が含まれたtokeninternal パッケージがpublicな gcimporter パッケージによって使用され意図せず公開されてしまうバグ がありました。 このバグは既に修正済みですが、その影響で、バグがあった古いバージョンに依存しているプロ ジェクトで問題が発生しています。 今回の問題は我々のミスです。ご迷惑をおかけし、大変申し訳ございません。

Slide 53

Slide 53 text

© 2025 ANDPAD All Rights Reserved. cl464301 x/tools v0.6.0 https://go.dev/cl/464301 tokeninternalにAddExistingFiles helperが追加されたのは x/tools v0.8.0 なのでこの時点ではまだコンパイルアサーションは存在しない

Slide 54

Slide 54 text

© 2025 ANDPAD All Rights Reserved. cl464301 x/tools v0.6.0 https://go.dev/cl/464301 グローバルなFileSetへの依存を減らすために token.Fileの情報だけ使ってコード整形する FormatNodeWithFileを gopls/internal/lsp/source/util.goに追加したよ。 その際にinternal/gcimporterパッケージの中に あったGetLinesヘルパー関数を使いたいから tokeninternalという共通パッケージに移動させまし た。

Slide 55

Slide 55 text

© 2025 ANDPAD All Rights Reserved. cl464301 x/tools v0.6.0 https://go.dev/cl/464301

Slide 56

Slide 56 text

© 2025 ANDPAD All Rights Reserved. cl476437 x/tools v0.8.0 (add AddExistingFiles helper) x/tools v0.8.0 (2023-03-16 00:30) internal/tokeninternal にFileSetのマージのための AddExistingFiles helperが追加 https://go.dev/cl/476437

Slide 57

Slide 57 text

© 2025 ANDPAD All Rights Reserved. Go1.25.0 での x/tools 影響バージョンまとめ ❌コンパイルアサーションの影響あり ● x/tools v0.8.0〜v0.24.0 ● x/tools v0.25.0 ⭕コンパイルアサーションの(おそらく)影響なし ● x/tools v0.24.1 ● x/tools v0.25.1 ● x/tools v0.26.0 以降 今回の件でbackport対応されたバージョン 今回の件でbackport対応されたバージョン

Slide 58

Slide 58 text

© 2025 ANDPAD All Rights Reserved. #74462 Why backport? https://go.dev/issue/74462#issuecomment-3193451687

Slide 59

Slide 59 text

© 2025 ANDPAD All Rights Reserved. Why backport?

Slide 60

Slide 60 text

© 2025 ANDPAD All Rights Reserved. Will Go 1.25.x support? https://go.dev/issue/74462#issuecomment-3197774637

Slide 61

Slide 61 text

© 2025 ANDPAD All Rights Reserved. 影響のあったライブラリ(の一部) ● google/wire https://github.com/google/wire/issues/431 ● go-swagger/go-swagger https://github.com/go-swagger/go-swagger/issues/3220 ● uber-go/mock https://github.com/uber-go/mock/issues/274 ● go-gorm/gen https://github.com/go-gorm/gen/issues/1366 ● gotestyourself/gotestsum https://github.com/gotestyourself/gotestsum/issues/504 ● terraform-docs/terraform-docs https://github.com/terraform-docs/terraform-docs/issues/870

Slide 62

Slide 62 text

© 2025 ANDPAD All Rights Reserved. まとめ & 感想 ● x/tools使ったライブラリ公開している人は要注意 ● unsafe packageを利用したライブラリをpublicに触れるAPI の形で提供する時は十分に検討してからにしよう ● internal な util が本当にinternalなのかよく確認しよう ● 優秀なGoogleのGoチームでさえたまには間違える、ミスは あるものと思って前向きに生きよう

Slide 63

Slide 63 text

© 2025 ANDPAD All Rights Reserved. 復習記事 & 宣伝 「†黒魔術†に対する防衛術」を詳しく知りたい人向け記事 “Goの†黒魔術†に対する防衛術 ~Defence Against the Go's dark Arts~” https://tech.andpad.co.jp/entry/2025/09/23/100000 「go:linknameディレクティブ」を詳しく知りたい人向け記事 “Go界隈で巻き起こった go:linkname 騒動について ” https://tech.andpad.co.jp/entry/2024/06/20/140000