x/tools/go/analysis/passes/unsafeptr: report Header misuse
This CL updates unsafeptr to report about *x and &x expressions where
the pointed-to variable has type reflect.SliceHeader or
reflect.StringHeader.
- Disallowing *x means that reflect.{Slice,String}Header.Data can only
be accessed using field selection via a *reflect.{Slice,String}Header
value.
- Disallowing &x means that a *reflect.{Slice,String}Header value can
only be created by converting from an unsafe.Pointer.
Well, almost only. There are still tricks that can be played to
workaround both of these. For example, a pointer can be dereferenced
via reflection, or a user could write a conversion like:
type T reflect.SliceHeader
_ = (*reflect.SliceHeader)(&T{})
But presumably this at least raises the bar enough that someone is
likely to pause to figure out the correct way to use
reflect.{Slice,String}Header.
Notably, disallowing *x and &x does *not* emit warnings for code that
uses reflect.{Slice,String}Header purely as values. For example, the
tests in internal/unsafeheader. Such code is arguably still a
violation of the unsafe.Pointer safety rules ("reflect.SliceHeader and
reflect.StringHeader should be used [...] never as plain structs"),
but is benign.
Updates golang/go#40701.
Change-Id: Id21996bfee07acc0d927a525797dca344bc804d8
Reviewed-on: https://go-review.googlesource.com/c/tools/+/248192
Reviewed-by: Michael Matloob <matloob@golang.org>
Trust: Matthew Dempsky <mdempsky@google.com>
diff --git a/go/analysis/passes/unsafeptr/testdata/src/a/issue40701.go b/go/analysis/passes/unsafeptr/testdata/src/a/issue40701.go
new file mode 100644
index 0000000..1f87697
--- /dev/null
+++ b/go/analysis/passes/unsafeptr/testdata/src/a/issue40701.go
@@ -0,0 +1,44 @@
+// Copyright 2020 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package a
+
+import (
+ "reflect"
+ "unsafe"
+)
+
+// Explicitly allocating a variable of type reflect.SliceHeader.
+func _(p *byte, n int) []byte {
+ var sh reflect.SliceHeader
+ sh.Data = uintptr(unsafe.Pointer(p))
+ sh.Len = n
+ sh.Cap = n
+ return *(*[]byte)(unsafe.Pointer(&sh)) // want "possible misuse of reflect.SliceHeader"
+}
+
+// Implicitly allocating a variable of type reflect.SliceHeader.
+func _(p *byte, n int) []byte {
+ return *(*[]byte)(unsafe.Pointer(&reflect.SliceHeader{ // want "possible misuse of reflect.SliceHeader"
+ Data: uintptr(unsafe.Pointer(p)),
+ Len: n,
+ Cap: n,
+ }))
+}
+
+// Use reflect.StringHeader as a composite literal value.
+func _(p *byte, n int) []byte {
+ var res []byte
+ *(*reflect.StringHeader)(unsafe.Pointer(&res)) = reflect.StringHeader{ // want "possible misuse of reflect.StringHeader"
+ Data: uintptr(unsafe.Pointer(p)),
+ Len: n,
+ }
+ return res
+}
+
+func _() {
+ // don't crash when obj.Pkg() == nil
+ var err error
+ _ = &err
+}
diff --git a/go/analysis/passes/unsafeptr/unsafeptr.go b/go/analysis/passes/unsafeptr/unsafeptr.go
index 5594149..ed86e5e 100644
--- a/go/analysis/passes/unsafeptr/unsafeptr.go
+++ b/go/analysis/passes/unsafeptr/unsafeptr.go
@@ -37,16 +37,29 @@
nodeFilter := []ast.Node{
(*ast.CallExpr)(nil),
+ (*ast.StarExpr)(nil),
+ (*ast.UnaryExpr)(nil),
}
inspect.Preorder(nodeFilter, func(n ast.Node) {
- x := n.(*ast.CallExpr)
- if len(x.Args) != 1 {
- return
- }
- if hasBasicType(pass.TypesInfo, x.Fun, types.UnsafePointer) &&
- hasBasicType(pass.TypesInfo, x.Args[0], types.Uintptr) &&
- !isSafeUintptr(pass.TypesInfo, x.Args[0]) {
- pass.ReportRangef(x, "possible misuse of unsafe.Pointer")
+ switch x := n.(type) {
+ case *ast.CallExpr:
+ if len(x.Args) == 1 &&
+ hasBasicType(pass.TypesInfo, x.Fun, types.UnsafePointer) &&
+ hasBasicType(pass.TypesInfo, x.Args[0], types.Uintptr) &&
+ !isSafeUintptr(pass.TypesInfo, x.Args[0]) {
+ pass.ReportRangef(x, "possible misuse of unsafe.Pointer")
+ }
+ case *ast.StarExpr:
+ if t := pass.TypesInfo.Types[x].Type; isReflectHeader(t) {
+ pass.ReportRangef(x, "possible misuse of %s", t)
+ }
+ case *ast.UnaryExpr:
+ if x.Op != token.AND {
+ return
+ }
+ if t := pass.TypesInfo.Types[x.X].Type; isReflectHeader(t) {
+ pass.ReportRangef(x, "possible misuse of %s", t)
+ }
}
})
return nil, nil
@@ -144,7 +157,7 @@
// isReflectHeader reports whether t is reflect.SliceHeader or reflect.StringHeader.
func isReflectHeader(t types.Type) bool {
if named, ok := t.(*types.Named); ok {
- if obj := named.Obj(); obj.Pkg().Path() == "reflect" {
+ if obj := named.Obj(); obj.Pkg() != nil && obj.Pkg().Path() == "reflect" {
switch obj.Name() {
case "SliceHeader", "StringHeader":
return true