From f4d9da17e9b2f153486134ee23b351335bff2d86 Mon Sep 17 00:00:00 2001 From: Jake Bailey <5341706+jakebailey@users.noreply.github.com> Date: Wed, 14 Oct 2026 10:19:57 -0700 Subject: [PATCH] Catch range and elided-literal writes to protected fields Range clauses can assign a protected field directly, and an elided literal inside a []*T or map[K]*T composite initializes a fresh header just like &T{...} does. Both bypassed the write check. --- tools/customlint/protectedfield.go | 15 ++++++++++++- .../protectedfield/consumer/consumer.go | 12 ++++++++++ .../consumer/consumer.go.golden | 22 +++++++++++++++++++ 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/tools/customlint/protectedfield.go b/tools/customlint/protectedfield.go index abd0cd9671b..0d8e414b281 100644 --- a/tools/customlint/protectedfield.go +++ b/tools/customlint/protectedfield.go @@ -38,6 +38,7 @@ func (p *protectedFieldPass) run() (any, error) { for c := range in.Root().Preorder( (*ast.AssignStmt)(nil), (*ast.IncDecStmt)(nil), + (*ast.RangeStmt)(nil), (*ast.UnaryExpr)(nil), (*ast.CompositeLit)(nil), (*ast.FuncType)(nil), @@ -50,6 +51,13 @@ func (p *protectedFieldPass) run() (any, error) { } case *ast.IncDecStmt: p.checkWrite(node.X, "write to") + case *ast.RangeStmt: + if node.Key != nil { + p.checkWrite(node.Key, "write to") + } + if node.Value != nil { + p.checkWrite(node.Value, "write to") + } case *ast.UnaryExpr: if node.Op == token.AND { p.checkWrite(node.X, "address of") @@ -107,7 +115,12 @@ func (p *protectedFieldPass) checkWrite(expr ast.Expr, what string) { } func (p *protectedFieldPass) checkLiteral(lit *ast.CompositeLit) { - st, ok := p.pass.TypesInfo.TypeOf(lit).Underlying().(*types.Struct) + typ := p.pass.TypesInfo.TypeOf(lit) + // Elided literals in []*T or map[K]*T composites have type *T. + if ptr, ok := typ.Underlying().(*types.Pointer); ok { + typ = ptr.Elem() + } + st, ok := typ.Underlying().(*types.Struct) if !ok { return } diff --git a/tools/customlint/testdata/protectedfield/consumer/consumer.go b/tools/customlint/testdata/protectedfield/consumer/consumer.go index 49c22227903..2611813bbd0 100644 --- a/tools/customlint/testdata/protectedfield/consumer/consumer.go +++ b/tools/customlint/testdata/protectedfield/consumer/consumer.go @@ -17,6 +17,14 @@ func writes(h *record.Header, s *record.Small, d *record.Defined) { h.Flags = 1 _ = &h.Flags _ = h.Kind + for _, h.Kind = range []record.Kind{record.KindLarge} { + } + for h.Kind = range record.Kind(2) { + } + var kinds []record.Kind + for _, kind := range kinds { + _ = kind + } } func literals() { @@ -25,6 +33,10 @@ func literals() { _ = &record.Header{Flags: 1} _ = &record.Small{} _ = &record.Alias{Kind: record.KindSmall} + _ = []*record.Header{{Kind: record.KindLarge}} + _ = []*record.Header{{record.KindLarge, 1}} + _ = map[int]*record.Header{0: {Kind: record.KindLarge}} + _ = []*record.Header{{Flags: 1}} } func copies(h *record.Header, s *record.Small, hs []record.Header) { diff --git a/tools/customlint/testdata/protectedfield/consumer/consumer.go.golden b/tools/customlint/testdata/protectedfield/consumer/consumer.go.golden index cd8db282a59..eba9bc85745 100644 --- a/tools/customlint/testdata/protectedfield/consumer/consumer.go.golden +++ b/tools/customlint/testdata/protectedfield/consumer/consumer.go.golden @@ -29,6 +29,18 @@ h.Flags = 1 _ = &h.Flags _ = h.Kind + for _, h.Kind = range []record.Kind{record.KindLarge} { + ~ +!!! protectedfield: write to protected field Kind + } + for h.Kind = range record.Kind(2) { + ~ +!!! protectedfield: write to protected field Kind + } + var kinds []record.Kind + for _, kind := range kinds { + _ = kind + } } func literals() { @@ -43,6 +55,16 @@ _ = &record.Alias{Kind: record.KindSmall} ~ !!! protectedfield: write to protected field Kind + _ = []*record.Header{{Kind: record.KindLarge}} + ~ +!!! protectedfield: write to protected field Kind + _ = []*record.Header{{record.KindLarge, 1}} + ~ +!!! protectedfield: write to protected field Kind + _ = map[int]*record.Header{0: {Kind: record.KindLarge}} + ~ +!!! protectedfield: write to protected field Kind + _ = []*record.Header{{Flags: 1}} } func copies(h *record.Header, s *record.Small, hs []record.Header) {