Slide 1

Slide 1 text

nocopy

Slide 2

Slide 2 text

自己紹介 [所属] 株式会社サイバーエージェント 株式会社Winticket [趣味] 服 旅行 プログラミング 自宅鯖

Slide 3

Slide 3 text

noCopy使ってますか、、???

Slide 4

Slide 4 text

構造体の値コピーをされると困る場合  ・sync系など  ・同じポインタなのに    各lockが非同期 type SafeCounter struct { mu sync.Mutex n *int } func (c *SafeCounter) Inc() { c.mu.Lock() *c.n++ c.mu.Unlock() } func main() { n := 0 c1 := SafeCounter{n: &n} c2 := c1 go c1.Inc() go c2.Inc() }

Slide 5

Slide 5 text

それを検知する手法: noCopy

Slide 6

Slide 6 text

type noCopy struct{} func (*noCopy) Lock() {} func (*noCopy) Unlock() {} noCopy sync.Lockerの interfaceを実装した 構造体

Slide 7

Slide 7 text

type WaitGroup struct { noCopy noCopy // Bits (high to low): // bits[0:32] counter // bits[33:64] wait count state atomic.Uint64 sema uint32 } noCopy WaitGroupなどには標準で搭載 他にも Cond, atomic, mutex, pool ……..

Slide 8

Slide 8 text

go vet Goの標準パッケージに含まれる静 的解析ツール var suite = []*analysis.Analyzer{ appends.Analyzer, asmdecl.Analyzer, assign.Analyzer, atomic.Analyzer, bools.Analyzer, buildtag.Analyzer, cgocall.Analyzer, composite.Analyzer, copylock.Analyzer, defers.Analyzer, directive.Analyzer, errorsas.Analyzer, // fieldalignment.Analyzer omitted: too noisy framepointer.Analyzer, httpresponse.Analyzer, hostport.Analyzer, ifaceassert.Analyzer, loopclosure.Analyzer, lostcancel.Analyzer, nilfunc.Analyzer, printf.Analyzer, // shadow.Analyzer omitted: too noisy shift.Analyzer, sigchanyzer.Analyzer, slog.Analyzer, stdmethods.Analyzer, stdversion.Analyzer, stringintconv.Analyzer, structtag.Analyzer, tests.Analyzer, testinggoroutine.Analyzer, timeformat.Analyzer, unmarshal.Analyzer, unreachable.Analyzer, unsafeptr.Analyzer, unusedresult.Analyzer, waitgroup.Analyzer, }

Slide 9

Slide 9 text

copylock Goの標準パッケージに含まれる静的解析ツール sync.Lockerの値コピーを検出する https://github.com/golang/tools/tree/master/go/analysis/passes/copylock

Slide 10

Slide 10 text

copylock inspect.WithStack(nodeFilter, func(node ast.Node, push bool, stack []ast.Node) bool { if !push { return false } switch node := node.(type) { case *ast.File: goversion = versions.FileVersion(pass.TypesInfo, node) case *ast.RangeStmt: checkCopyLocksRange(pass, node) case *ast.FuncDecl: checkCopyLocksFunc(pass, node.Name.Name, node.Recv, node.Type) case *ast.FuncLit: checkCopyLocksFunc(pass, "func", nil, node.Type) case *ast.CallExpr: checkCopyLocksCallExpr(pass, node) case *ast.AssignStmt: checkCopyLocksAssign(pass, node, goversion, parent(stack)) case *ast.GenDecl: checkCopyLocksGenDecl(pass, node) case *ast.CompositeLit: checkCopyLocksCompositeLit(pass, node) case *ast.ReturnStmt: checkCopyLocksReturnStmt(pass, node) } return true }) return 代入 変数宣言 などをそれぞれチェック

Slide 11

Slide 11 text

copylock // Construct a sync.Locker interface type. func init() { nullary := types.NewSignatureType(nil, nil, nil, nil, nil, false) // func() methods := []*types.Func{ types.NewFunc(token.NoPos, nil, "Lock", nullary), types.NewFunc(token.NoPos, nil, "Unlock", nullary), } lockerType = types.NewInterface(methods, nil).Complete() } init()でsync.LockerのInterface typeを生成 type.Implementsで確認

Slide 12

Slide 12 text

go versionとの関係 // In go1.10, sync.noCopy did not implement Locker. // (The Unlock method was added only in CL 121876.) // TODO(adonovan): remove workaround when we drop go1.10. if typesinternal.IsTypeNamed(typ, "sync", "noCopy") { return []string{typ.String()} } go 1.10ではsync.noCopyがUnlock()を実装してい なかったので、sync.noCopyなら条件を満たすよう になっている

Slide 13

Slide 13 text

go versionとの関係 if assign.Tok == token.DEFINE && versions.AtLeast(goversion, versions.Go1_22) { if parent, _ := parent.(*ast.ForStmt); parent != nil && parent.Init == assign { for _, l := range lhs { if id, ok := l.(*ast.Ident); ok && id.Name != "_" { if obj := pass.TypesInfo.Defs[id]; obj != nil && obj.Type() != nil { if path := lockPath(pass.Pkg, obj.Type(), nil); path != nil { pass.ReportRangef(l, "for loop iteration copies lock value to %v: %v", astutil.Format(pass.Fset, l), path) } } } } } } go 1.22以降ではforループ修正により、イテレートごと に新しく変数が宣言されるようになった。 1.22以降は新しくチェック

Slide 14

Slide 14 text

noCopyとGo本体の考え方

Slide 15

Slide 15 text

// noCopy may be added to structs which must not be copied // after the first use. // // See https://golang.org/issues/8005#issuecomment-190753527 // for details. // // Note that it must not be embedded, due to the Lock and Unlock methods. type noCopy struct{} // Lock is a no-op used by -copylocks checker from `go vet`. func (*noCopy) Lock() {} func (*noCopy) Unlock() {}

Slide 16

Slide 16 text

https://golang.org/issues/8005#issuecomment-190753527 2014-05-16 NoCopy構造体を追加したい

Slide 17

Slide 17 text

● Goにはコピーを禁止する明示的な仕組みはない ○ 昔(初期Goコンパイラ )にはあったらしい ● 現状維持とする (良い方法を待つ ) ○ go vetにバックドア的な言語変更を入れない ● どうしてもの場合は noCopyを使う

Slide 18

Slide 18 text

その後も議論は度々起き

Slide 19

Slide 19 text

https://github.com/golang/go/issues/23764 2018-02-10 NoCopyを言語仕様に追加したい ↓ 却下

Slide 20

Slide 20 text

https://github.com/golang/go/issues/70811 2024-12-11 NoCopyを明示的に構造体で組み込む →vetで検知 ポインタを使用してコピーされたら panicを起こすNoCopyCheck

Slide 21

Slide 21 text

議論中、、、今後の展望に期待

Slide 22

Slide 22 text

Thank You!